зеркало из https://github.com/microsoft/CCF.git
Don't store temporary pointers in JSValues (#5740)
This commit is contained in:
Родитель
144cbe06c0
Коммит
8f7afdb164
|
@ -1,4 +1,4 @@
|
|||
-^- ___ ___
|
||||
(- -) (= =) | Y & +--?
|
||||
( V ) / . \ | +---=---'
|
||||
/--x-m- /--n-n---xXx--/--yY------>>>----<<<>>]]{{}}
|
||||
/--x-m- /--n-n---xXx--/--yY------>>>----<<<>>]]{{}}--
|
||||
|
|
|
@ -1 +1 @@
|
|||
........
|
||||
..........
|
|
@ -16,6 +16,7 @@ namespace kv
|
|||
|
||||
virtual ccf::TxID get_txid() = 0;
|
||||
virtual kv::ReadOnlyTx create_read_only_tx() = 0;
|
||||
virtual std::unique_ptr<kv::ReadOnlyTx> create_read_only_tx_ptr() = 0;
|
||||
virtual kv::TxDiff create_tx_diff() = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -41,51 +41,43 @@ import { KvMap, ccf } from "./global.js";
|
|||
import { DataConverter } from "./converters.js";
|
||||
|
||||
export class TypedKvMap<K, V> {
|
||||
private _kv() {
|
||||
const kvMap =
|
||||
typeof this.nameOrMap === "string"
|
||||
? ccf.kv[this.nameOrMap]
|
||||
: this.nameOrMap;
|
||||
return kvMap;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private nameOrMap: string | KvMap,
|
||||
private kv: KvMap,
|
||||
private kt: DataConverter<K>,
|
||||
private vt: DataConverter<V>,
|
||||
) {}
|
||||
|
||||
has(key: K): boolean {
|
||||
return this._kv().has(this.kt.encode(key));
|
||||
return this.kv.has(this.kt.encode(key));
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
const v = this._kv().get(this.kt.encode(key));
|
||||
const v = this.kv.get(this.kt.encode(key));
|
||||
return v === undefined ? undefined : this.vt.decode(v);
|
||||
}
|
||||
|
||||
getVersionOfPreviousWrite(key: K): number | undefined {
|
||||
return this._kv().getVersionOfPreviousWrite(this.kt.encode(key));
|
||||
return this.kv.getVersionOfPreviousWrite(this.kt.encode(key));
|
||||
}
|
||||
|
||||
set(key: K, value: V): TypedKvMap<K, V> {
|
||||
this._kv().set(this.kt.encode(key), this.vt.encode(value));
|
||||
this.kv.set(this.kt.encode(key), this.vt.encode(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
delete(key: K): void {
|
||||
this._kv().delete(this.kt.encode(key));
|
||||
this.kv.delete(this.kt.encode(key));
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._kv().clear();
|
||||
this.kv.clear();
|
||||
}
|
||||
|
||||
forEach(callback: (value: V, key: K, table: TypedKvMap<K, V>) => void): void {
|
||||
let kt = this.kt;
|
||||
let vt = this.vt;
|
||||
let typedMap = this;
|
||||
this._kv().forEach(function (
|
||||
this.kv.forEach(function (
|
||||
raw_v: ArrayBuffer,
|
||||
raw_k: ArrayBuffer,
|
||||
table: KvMap,
|
||||
|
@ -95,7 +87,7 @@ export class TypedKvMap<K, V> {
|
|||
}
|
||||
|
||||
get size(): number {
|
||||
return this._kv().size;
|
||||
return this.kv.size;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +109,8 @@ export function typedKv<K, V>(
|
|||
kt: DataConverter<K>,
|
||||
vt: DataConverter<V>,
|
||||
) {
|
||||
return new TypedKvMap(nameOrMap, kt, vt);
|
||||
const kvMap = typeof nameOrMap === "string" ? ccf.kv[nameOrMap] : nameOrMap;
|
||||
return new TypedKvMap(kvMap, kt, vt);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -41,7 +41,7 @@ function delete_record(map, id) {
|
|||
return { body: true };
|
||||
}
|
||||
|
||||
export function get_private(request, scope) {
|
||||
export function get_private(request) {
|
||||
const parsedQuery = parse_request_query(request);
|
||||
const id = get_id_from_query(parsedQuery);
|
||||
return get_record(private_records(ccf.kv, parsedQuery.scope), id);
|
||||
|
|
|
@ -220,9 +220,9 @@ namespace ccfapp
|
|||
}
|
||||
request.set("params", std::move(params));
|
||||
|
||||
const auto& request_body = endpoint_ctx.rpc_ctx->get_request_body();
|
||||
auto body_ = ctx.new_obj_class(js::body_class_id);
|
||||
JS_SetOpaque(body_, (void*)&request_body);
|
||||
ctx.globals.current_request_body =
|
||||
&endpoint_ctx.rpc_ctx->get_request_body();
|
||||
request.set("body", std::move(body_));
|
||||
|
||||
request.set("caller", create_caller_obj(endpoint_ctx, ctx));
|
||||
|
@ -230,6 +230,11 @@ namespace ccfapp
|
|||
return request;
|
||||
}
|
||||
|
||||
void invalidate_request_obj_body(js::Context& ctx)
|
||||
{
|
||||
ctx.globals.current_request_body = nullptr;
|
||||
}
|
||||
|
||||
void execute_request(
|
||||
const ccf::js::JSDynamicEndpoint* endpoint,
|
||||
ccf::endpoints::EndpointContext& endpoint_ctx)
|
||||
|
@ -246,14 +251,10 @@ namespace ccfapp
|
|||
[this, endpoint](
|
||||
ccf::endpoints::EndpointContext& endpoint_ctx,
|
||||
ccf::historical::StatePtr state) {
|
||||
auto tx = state->store->create_read_only_tx();
|
||||
auto tx_id = state->transaction_id;
|
||||
auto receipt = state->receipt;
|
||||
assert(receipt);
|
||||
js::ReadOnlyTxContext historical_txctx{&tx};
|
||||
auto add_historical_globals = [&](js::Context& ctx) {
|
||||
js::populate_global_ccf_historical_state(
|
||||
&historical_txctx, tx_id, receipt, ctx);
|
||||
auto ccf = ctx.get_global_property("ccf");
|
||||
auto val = ccf::js::create_historical_state_object(ctx, state);
|
||||
ccf.set("historicalState", std::move(val));
|
||||
};
|
||||
do_execute_request(endpoint, endpoint_ctx, add_historical_globals);
|
||||
},
|
||||
|
@ -318,10 +319,8 @@ namespace ccfapp
|
|||
JS_SetModuleLoaderFunc(
|
||||
ctx.runtime(), nullptr, js::js_app_module_loader, &endpoint_ctx.tx);
|
||||
|
||||
js::TxContext txctx{&endpoint_ctx.tx};
|
||||
|
||||
js::register_request_body_class(ctx);
|
||||
js::populate_global_ccf_kv(&txctx, ctx);
|
||||
js::populate_global_ccf_kv(endpoint_ctx.tx, ctx);
|
||||
|
||||
js::populate_global_ccf_rpc(endpoint_ctx.rpc_ctx.get(), ctx);
|
||||
js::populate_global_ccf_host(
|
||||
|
@ -361,7 +360,12 @@ namespace ccfapp
|
|||
&endpoint_ctx.tx,
|
||||
ccf::js::RuntimeLimitsPolicy::NONE);
|
||||
|
||||
auto& rt = ctx.runtime();
|
||||
// Clear globals (which potentially reference locals like txctx), from
|
||||
// this potentially reused interpreter
|
||||
invalidate_request_obj_body(ctx);
|
||||
js::invalidate_globals(ctx);
|
||||
|
||||
const auto& rt = ctx.runtime();
|
||||
|
||||
if (JS_IsException(val))
|
||||
{
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
namespace ccf::js
|
||||
{
|
||||
static JSValue ccf_receipt_to_js(js::Context* jsctx, TxReceiptImplPtr receipt)
|
||||
static JSValue ccf_receipt_to_js(js::Context& jsctx, TxReceiptImplPtr receipt)
|
||||
{
|
||||
ccf::ReceiptPtr receipt_out_p = ccf::describe_receipt_v2(*receipt);
|
||||
auto& receipt_out = *receipt_out_p;
|
||||
auto js_receipt = jsctx->new_obj();
|
||||
auto js_receipt = jsctx.new_obj();
|
||||
JS_CHECK_EXC(js_receipt);
|
||||
|
||||
std::string sig_b64;
|
||||
|
@ -18,18 +18,18 @@ namespace ccf::js
|
|||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
return jsctx->new_internal_error(
|
||||
return jsctx.new_internal_error(
|
||||
"Failed to convert signature to base64: %s", e.what());
|
||||
}
|
||||
auto sig_string = jsctx->new_string(sig_b64.c_str());
|
||||
auto sig_string = jsctx.new_string(sig_b64.c_str());
|
||||
JS_CHECK_EXC(sig_string);
|
||||
JS_CHECK_SET(js_receipt.set("signature", std::move(sig_string)));
|
||||
|
||||
auto js_cert = jsctx->new_string(receipt_out.cert.str().c_str());
|
||||
auto js_cert = jsctx.new_string(receipt_out.cert.str().c_str());
|
||||
JS_CHECK_EXC(js_cert);
|
||||
JS_CHECK_SET(js_receipt.set("cert", std::move(js_cert)));
|
||||
|
||||
auto js_node_id = jsctx->new_string(receipt_out.node_id.value().c_str());
|
||||
auto js_node_id = jsctx.new_string(receipt_out.node_id.value().c_str());
|
||||
JS_CHECK_EXC(js_node_id);
|
||||
JS_CHECK_SET(js_receipt.set("node_id", std::move(js_node_id)));
|
||||
bool is_signature_transaction = receipt_out.is_signature_transaction();
|
||||
|
@ -40,18 +40,18 @@ namespace ccf::js
|
|||
{
|
||||
auto p_receipt =
|
||||
std::dynamic_pointer_cast<ccf::ProofReceipt>(receipt_out_p);
|
||||
auto leaf_components = jsctx->new_obj();
|
||||
auto leaf_components = jsctx.new_obj();
|
||||
JS_CHECK_EXC(leaf_components);
|
||||
|
||||
const auto wsd_hex =
|
||||
ds::to_hex(p_receipt->leaf_components.write_set_digest.h);
|
||||
|
||||
auto js_wsd = jsctx->new_string(wsd_hex.c_str());
|
||||
auto js_wsd = jsctx.new_string(wsd_hex.c_str());
|
||||
JS_CHECK_EXC(js_wsd);
|
||||
JS_CHECK_SET(leaf_components.set("write_set_digest", std::move(js_wsd)));
|
||||
|
||||
auto js_commit_evidence =
|
||||
jsctx->new_string(p_receipt->leaf_components.commit_evidence.c_str());
|
||||
jsctx.new_string(p_receipt->leaf_components.commit_evidence.c_str());
|
||||
JS_CHECK_EXC(js_commit_evidence);
|
||||
JS_CHECK_SET(
|
||||
leaf_components.set("commit_evidence", std::move(js_commit_evidence)));
|
||||
|
@ -61,26 +61,26 @@ namespace ccf::js
|
|||
const auto cd_hex =
|
||||
ds::to_hex(p_receipt->leaf_components.claims_digest.value().h);
|
||||
|
||||
auto js_cd = jsctx->new_string(cd_hex.c_str());
|
||||
auto js_cd = jsctx.new_string(cd_hex.c_str());
|
||||
JS_CHECK_EXC(js_cd);
|
||||
JS_CHECK_SET(leaf_components.set("claims_digest", std::move(js_cd)));
|
||||
}
|
||||
JS_CHECK_SET(
|
||||
js_receipt.set("leaf_components", std::move(leaf_components)));
|
||||
|
||||
auto proof = jsctx->new_array();
|
||||
auto proof = jsctx.new_array();
|
||||
JS_CHECK_EXC(proof);
|
||||
|
||||
uint32_t i = 0;
|
||||
for (auto& element : p_receipt->proof)
|
||||
{
|
||||
auto js_element = jsctx->new_obj();
|
||||
auto js_element = jsctx.new_obj();
|
||||
JS_CHECK_EXC(js_element);
|
||||
|
||||
auto is_left = element.direction == ccf::ProofReceipt::ProofStep::Left;
|
||||
const auto hash_hex = ds::to_hex(element.hash.h);
|
||||
|
||||
auto js_hash = jsctx->new_string(hash_hex.c_str());
|
||||
auto js_hash = jsctx.new_string(hash_hex.c_str());
|
||||
JS_CHECK_EXC(js_hash);
|
||||
JS_CHECK_SET(
|
||||
js_element.set(is_left ? "left" : "right", std::move(js_hash)));
|
||||
|
@ -94,7 +94,7 @@ namespace ccf::js
|
|||
std::dynamic_pointer_cast<ccf::SignatureReceipt>(receipt_out_p);
|
||||
const auto signed_root = sig_receipt->signed_root;
|
||||
const auto root_hex = ds::to_hex(signed_root.h);
|
||||
auto js_root_hex = jsctx->new_string(root_hex.c_str());
|
||||
auto js_root_hex = jsctx.new_string(root_hex.c_str());
|
||||
JS_CHECK_EXC(js_root_hex);
|
||||
JS_CHECK_SET(js_receipt.set("root_hex", std::move(js_root_hex)));
|
||||
}
|
||||
|
@ -102,13 +102,6 @@ namespace ccf::js
|
|||
return js_receipt.take();
|
||||
}
|
||||
|
||||
static void js_historical_state_finalizer(JSRuntime* rt, JSValue val)
|
||||
{
|
||||
auto* state_ctx =
|
||||
(HistoricalStateContext*)JS_GetOpaque(val, historical_state_class_id);
|
||||
delete state_ctx;
|
||||
}
|
||||
|
||||
static JSValue js_historical_get_state_range(
|
||||
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
|
@ -171,38 +164,16 @@ namespace ccf::js
|
|||
return ccf::js::constants::Null;
|
||||
}
|
||||
|
||||
ccf::js::Context* jsctx = (ccf::js::Context*)JS_GetContextOpaque(ctx);
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
|
||||
auto states_array = jsctx->new_array();
|
||||
auto states_array = jsctx.new_array();
|
||||
JS_CHECK_EXC(states_array);
|
||||
size_t i = 0;
|
||||
for (auto& state : states)
|
||||
{
|
||||
auto js_state = jsctx->new_obj_class(historical_state_class_id);
|
||||
JS_CHECK_EXC(js_state);
|
||||
|
||||
// Note: The state_ctx object is deleted by js_historical_state_finalizer
|
||||
// which is registered as the finalizer for historical_state_class_id.
|
||||
auto state_ctx = new HistoricalStateContext{
|
||||
state, state->store->create_read_only_tx(), ReadOnlyTxContext{nullptr}};
|
||||
state_ctx->tx_ctx.tx = &state_ctx->tx;
|
||||
JS_SetOpaque(js_state, state_ctx);
|
||||
|
||||
auto transaction_id =
|
||||
jsctx->new_string(state->transaction_id.to_str().c_str());
|
||||
JS_CHECK_EXC(transaction_id);
|
||||
JS_CHECK_SET(js_state.set("transactionId", std::move(transaction_id)));
|
||||
|
||||
auto js_receipt = (*jsctx)(ccf_receipt_to_js(jsctx, state->receipt));
|
||||
JS_CHECK_EXC(js_receipt);
|
||||
JS_CHECK_SET(js_state.set("receipt", std::move(js_receipt)));
|
||||
|
||||
auto kv = jsctx->new_obj_class(kv_read_only_class_id);
|
||||
JS_CHECK_EXC(kv);
|
||||
JS_SetOpaque(kv, &state_ctx->tx_ctx);
|
||||
JS_CHECK_SET(js_state.set("kv", std::move(kv)));
|
||||
|
||||
states_array.set_at_index(i++, std::move(js_state));
|
||||
auto js_state =
|
||||
jsctx(ccf::js::create_historical_state_object(jsctx, state));
|
||||
JS_CHECK_SET(states_array.set_at_index(i++, std::move(js_state)));
|
||||
}
|
||||
|
||||
return states_array.take();
|
||||
|
|
582
src/js/wrap.cpp
582
src/js/wrap.cpp
|
@ -27,6 +27,16 @@
|
|||
#include <quickjs/quickjs.h>
|
||||
#include <span>
|
||||
|
||||
#define JS_CHECK_HANDLE(h) \
|
||||
do \
|
||||
{ \
|
||||
if (h == nullptr) \
|
||||
{ \
|
||||
return JS_ThrowInternalError( \
|
||||
ctx, "Internal: Unable to access MapHandle"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
namespace ccf::js
|
||||
{
|
||||
// "mixture of designated and non-designated initializers in the same
|
||||
|
@ -39,7 +49,7 @@ namespace ccf::js
|
|||
using KVMap = kv::untyped::Map;
|
||||
|
||||
JSClassID kv_class_id = 0;
|
||||
JSClassID kv_read_only_class_id = 0;
|
||||
JSClassID kv_historical_class_id = 0;
|
||||
JSClassID kv_map_handle_class_id = 0;
|
||||
JSClassID body_class_id = 0;
|
||||
JSClassID node_class_id = 0;
|
||||
|
@ -52,9 +62,10 @@ namespace ccf::js
|
|||
|
||||
JSClassDef kv_class_def = {};
|
||||
JSClassExoticMethods kv_exotic_methods = {};
|
||||
JSClassDef kv_read_only_class_def = {};
|
||||
JSClassExoticMethods kv_read_only_exotic_methods = {};
|
||||
JSClassDef kv_historical_class_def = {};
|
||||
JSClassExoticMethods kv_historical_exotic_methods = {};
|
||||
JSClassDef kv_map_handle_class_def = {};
|
||||
JSClassDef kv_historical_map_handle_class_def = {};
|
||||
JSClassDef body_class_def = {};
|
||||
JSClassDef node_class_def = {};
|
||||
JSClassDef network_class_def = {};
|
||||
|
@ -189,14 +200,106 @@ namespace ccf::js
|
|||
JS_FreeRuntime(rt);
|
||||
}
|
||||
|
||||
static KVMap::Handle* _get_map_handle(
|
||||
js::Context& jsctx, JSValueConst _this_val)
|
||||
{
|
||||
JSWrappedValue this_val = jsctx(JS_DupValue(jsctx, _this_val));
|
||||
auto map_name_val = this_val["_map_name"];
|
||||
auto map_name = jsctx.to_str(map_name_val);
|
||||
|
||||
if (!map_name.has_value())
|
||||
{
|
||||
LOG_FAIL_FMT("No map name stored on handle");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto& handles = jsctx.globals.kv_handles;
|
||||
auto it = handles.find(map_name.value());
|
||||
if (it == handles.end())
|
||||
{
|
||||
it = handles.emplace_hint(it, map_name.value(), nullptr);
|
||||
}
|
||||
|
||||
if (it->second == nullptr)
|
||||
{
|
||||
kv::Tx* tx = jsctx.globals.tx;
|
||||
if (tx == nullptr)
|
||||
{
|
||||
LOG_FAIL_FMT("Can't rehydrate MapHandle - no transaction context");
|
||||
return nullptr;
|
||||
}
|
||||
it->second = tx->rw<KVMap>(map_name.value());
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
using HandleGetter =
|
||||
KVMap::ReadOnlyHandle* (*)(js::Context& jsctx, JSValueConst this_val);
|
||||
|
||||
static KVMap::ReadOnlyHandle* _get_map_handle_current(
|
||||
js::Context& jsctx, JSValueConst this_val)
|
||||
{
|
||||
// NB: This creates (and stores) a writeable handle internally, but converts
|
||||
// to the (subtype) ReadOnlyHandle* in return here. This means that if we
|
||||
// call has() and then put(), we'll correctly have a writeable handle for
|
||||
// the put() despite reading initially.
|
||||
return _get_map_handle(jsctx, this_val);
|
||||
}
|
||||
|
||||
static KVMap::ReadOnlyHandle* _get_map_handle_historical(
|
||||
js::Context& jsctx, JSValueConst _this_val)
|
||||
{
|
||||
JSWrappedValue this_val = jsctx(JS_DupValue(jsctx, _this_val));
|
||||
auto map_name_val = this_val["_map_name"];
|
||||
auto map_name = jsctx.to_str(map_name_val);
|
||||
|
||||
if (!map_name.has_value())
|
||||
{
|
||||
LOG_FAIL_FMT("No map name stored on handle");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto seqno = reinterpret_cast<ccf::SeqNo>(
|
||||
JS_GetOpaque(this_val, kv_map_handle_class_id));
|
||||
|
||||
// Handle to historical KV
|
||||
auto it = jsctx.globals.historical_handles.find(seqno);
|
||||
if (it == jsctx.globals.historical_handles.end())
|
||||
{
|
||||
LOG_FAIL_FMT(
|
||||
"Unable to retrieve any historical handles for state at {}", seqno);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto& handles = it->second.kv_handles;
|
||||
auto hit = handles.find(map_name.value());
|
||||
if (hit == handles.end())
|
||||
{
|
||||
hit = handles.emplace_hint(hit, map_name.value(), nullptr);
|
||||
}
|
||||
|
||||
if (hit->second == nullptr)
|
||||
{
|
||||
kv::ReadOnlyTx* tx = it->second.tx.get();
|
||||
if (tx == nullptr)
|
||||
{
|
||||
LOG_FAIL_FMT("Can't rehydrate MapHandle - no transaction");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
hit->second = tx->ro<KVMap>(map_name.value());
|
||||
}
|
||||
|
||||
return hit->second;
|
||||
}
|
||||
|
||||
template <HandleGetter handle_getter_>
|
||||
static JSValue js_kv_map_has(
|
||||
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
|
||||
auto handle = static_cast<KVMap::Handle*>(
|
||||
JS_GetOpaque(this_val, kv_map_handle_class_id));
|
||||
|
||||
if (argc != 1)
|
||||
{
|
||||
return JS_ThrowTypeError(
|
||||
|
@ -211,19 +314,20 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
|
||||
}
|
||||
|
||||
auto handle = handle_getter_(jsctx, this_val);
|
||||
JS_CHECK_HANDLE(handle);
|
||||
|
||||
auto has = handle->has({key, key + key_size});
|
||||
|
||||
return JS_NewBool(ctx, has);
|
||||
}
|
||||
|
||||
template <HandleGetter handle_getter_>
|
||||
static JSValue js_kv_map_get(
|
||||
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
|
||||
auto handle = static_cast<KVMap::Handle*>(
|
||||
JS_GetOpaque(this_val, kv_map_handle_class_id));
|
||||
|
||||
if (argc != 1)
|
||||
{
|
||||
return JS_ThrowTypeError(
|
||||
|
@ -238,6 +342,9 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
|
||||
}
|
||||
|
||||
auto handle = handle_getter_(jsctx, this_val);
|
||||
JS_CHECK_HANDLE(handle);
|
||||
|
||||
auto val = handle->get({key, key + key_size});
|
||||
|
||||
if (!val.has_value())
|
||||
|
@ -252,14 +359,12 @@ namespace ccf::js
|
|||
return buf.take();
|
||||
}
|
||||
|
||||
template <HandleGetter handle_getter_>
|
||||
static JSValue js_kv_get_version_of_previous_write(
|
||||
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
|
||||
auto handle = static_cast<KVMap::Handle*>(
|
||||
JS_GetOpaque(this_val, kv_map_handle_class_id));
|
||||
|
||||
if (argc != 1)
|
||||
{
|
||||
return JS_ThrowTypeError(
|
||||
|
@ -274,6 +379,9 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
|
||||
}
|
||||
|
||||
auto handle = handle_getter_(jsctx, this_val);
|
||||
JS_CHECK_HANDLE(handle);
|
||||
|
||||
auto val = handle->get_version_of_previous_write({key, key + key_size});
|
||||
|
||||
if (!val.has_value())
|
||||
|
@ -284,17 +392,22 @@ namespace ccf::js
|
|||
return JS_NewInt64(ctx, val.value());
|
||||
}
|
||||
|
||||
template <HandleGetter handle_getter_>
|
||||
static JSValue js_kv_map_size_getter(
|
||||
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst*)
|
||||
{
|
||||
auto handle = static_cast<KVMap::Handle*>(
|
||||
JS_GetOpaque(this_val, kv_map_handle_class_id));
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
|
||||
auto handle = handle_getter_(jsctx, this_val);
|
||||
JS_CHECK_HANDLE(handle);
|
||||
|
||||
const uint64_t size = handle->size();
|
||||
if (size > INT64_MAX)
|
||||
{
|
||||
return JS_ThrowInternalError(
|
||||
ctx, "Map size (%lu) is too large to represent in int64", size);
|
||||
}
|
||||
|
||||
return JS_NewInt64(ctx, (int64_t)size);
|
||||
}
|
||||
|
||||
|
@ -303,9 +416,6 @@ namespace ccf::js
|
|||
{
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
|
||||
auto handle = static_cast<KVMap::Handle*>(
|
||||
JS_GetOpaque(this_val, kv_map_handle_class_id));
|
||||
|
||||
if (argc != 1)
|
||||
{
|
||||
return JS_ThrowTypeError(
|
||||
|
@ -320,6 +430,9 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
|
||||
}
|
||||
|
||||
auto handle = _get_map_handle(jsctx, this_val);
|
||||
JS_CHECK_HANDLE(handle);
|
||||
|
||||
handle->remove({key, key + key_size});
|
||||
|
||||
return ccf::js::constants::Undefined;
|
||||
|
@ -330,9 +443,6 @@ namespace ccf::js
|
|||
{
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
|
||||
auto handle = static_cast<KVMap::Handle*>(
|
||||
JS_GetOpaque(this_val, kv_map_handle_class_id));
|
||||
|
||||
if (argc != 2)
|
||||
{
|
||||
return JS_ThrowTypeError(
|
||||
|
@ -350,6 +460,9 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(ctx, "Arguments must be ArrayBuffers");
|
||||
}
|
||||
|
||||
auto handle = _get_map_handle(jsctx, this_val);
|
||||
JS_CHECK_HANDLE(handle);
|
||||
|
||||
handle->put({key, key + key_size}, {val, val + val_size});
|
||||
|
||||
return JS_DupValue(ctx, this_val);
|
||||
|
@ -358,8 +471,7 @@ namespace ccf::js
|
|||
static JSValue js_kv_map_clear(
|
||||
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
auto handle = static_cast<KVMap::Handle*>(
|
||||
JS_GetOpaque(this_val, kv_map_handle_class_id));
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
|
||||
if (argc != 0)
|
||||
{
|
||||
|
@ -367,19 +479,20 @@ namespace ccf::js
|
|||
ctx, "Passed %d arguments, but expected 0", argc);
|
||||
}
|
||||
|
||||
auto handle = _get_map_handle(jsctx, this_val);
|
||||
JS_CHECK_HANDLE(handle);
|
||||
|
||||
handle->clear();
|
||||
|
||||
return ccf::js::constants::Undefined;
|
||||
}
|
||||
|
||||
template <HandleGetter handle_getter_>
|
||||
static JSValue js_kv_map_foreach(
|
||||
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
|
||||
auto handle = static_cast<KVMap::Handle*>(
|
||||
JS_GetOpaque(this_val, kv_map_handle_class_id));
|
||||
|
||||
if (argc != 1)
|
||||
return JS_ThrowTypeError(
|
||||
ctx, "Passed %d arguments, but expected 1", argc);
|
||||
|
@ -392,6 +505,9 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(ctx, "Argument must be a function");
|
||||
}
|
||||
|
||||
auto handle = handle_getter_(jsctx, this_val);
|
||||
JS_CHECK_HANDLE(handle);
|
||||
|
||||
bool failed = false;
|
||||
handle->foreach(
|
||||
[&jsctx, &obj, &func, &failed](const auto& k, const auto& v) {
|
||||
|
@ -517,9 +633,13 @@ namespace ccf::js
|
|||
JSContext* ctx, JSValueConst this_val, int, JSValueConst*) \
|
||||
{ \
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx); \
|
||||
auto handle = static_cast<KVMap::Handle*>( \
|
||||
JS_GetOpaque(this_val, kv_map_handle_class_id)); \
|
||||
const auto table_name = handle->get_name_of_map(); \
|
||||
const auto table_name = \
|
||||
jsctx.to_str(JS_GetPropertyStr(jsctx, this_val, "_map_name")) \
|
||||
.value_or(""); \
|
||||
if (table_name.empty()) \
|
||||
{ \
|
||||
return JS_ThrowTypeError(ctx, "Internal: No map name stored on handle"); \
|
||||
} \
|
||||
const auto permission = _check_kv_map_access(jsctx.access, table_name); \
|
||||
char const* table_kind = permission == MapAccessPermissions::READ_ONLY ? \
|
||||
"read-only" : \
|
||||
|
@ -551,27 +671,37 @@ namespace ccf::js
|
|||
JS_KV_PERMISSION_ERROR_HELPER(js_kv_map_get_version_denied, "get_version")
|
||||
#undef JS_KV_PERMISSION_ERROR_HELPER
|
||||
|
||||
static void _create_kv_map_handle(
|
||||
JSContext* ctx,
|
||||
JSPropertyDescriptor* desc,
|
||||
void* handle,
|
||||
template <HandleGetter HG>
|
||||
static JSValue _create_kv_map_handle(
|
||||
js::Context& ctx,
|
||||
const std::string& map_name,
|
||||
MapAccessPermissions access_permission)
|
||||
{
|
||||
// This follows the interface of Map:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
|
||||
// Keys and values are ArrayBuffers. Keys are matched based on their
|
||||
// contents.
|
||||
auto view_val = JS_NewObjectClass(ctx, kv_map_handle_class_id);
|
||||
JS_SetOpaque(view_val, handle);
|
||||
auto view_val = ctx.new_obj_class(kv_map_handle_class_id);
|
||||
JS_CHECK_EXC(view_val);
|
||||
|
||||
auto has_fn = js_kv_map_has;
|
||||
auto get_fn = js_kv_map_get;
|
||||
auto size_fn = js_kv_map_size_getter;
|
||||
// Store (owning) copy of map_name in a property on this JSValue
|
||||
auto map_name_val = ctx.new_string(map_name);
|
||||
JS_CHECK_EXC(map_name_val);
|
||||
JS_CHECK_SET(view_val.set("_map_name", std::move(map_name_val)));
|
||||
|
||||
// Add methods to handle object. Note that this is done once, when this
|
||||
// object is created, because jsctx.access is constant. If the access
|
||||
// restrictions could vary between invocations, then this object's
|
||||
// properties would need to be updated as well.
|
||||
|
||||
auto has_fn = js_kv_map_has<HG>;
|
||||
auto get_fn = js_kv_map_get<HG>;
|
||||
auto size_fn = js_kv_map_size_getter<HG>;
|
||||
auto set_fn = js_kv_map_set;
|
||||
auto delete_fn = js_kv_map_delete;
|
||||
auto clear_fn = js_kv_map_clear;
|
||||
auto foreach_fn = js_kv_map_foreach;
|
||||
auto get_version_fn = js_kv_get_version_of_previous_write;
|
||||
auto foreach_fn = js_kv_map_foreach<HG>;
|
||||
auto get_version_fn = js_kv_get_version_of_previous_write<HG>;
|
||||
|
||||
if (access_permission == MapAccessPermissions::ILLEGAL)
|
||||
{
|
||||
|
@ -591,39 +721,41 @@ namespace ccf::js
|
|||
clear_fn = js_kv_map_clear_denied;
|
||||
}
|
||||
|
||||
JS_SetPropertyStr(
|
||||
ctx, view_val, "has", JS_NewCFunction(ctx, has_fn, "has", 1));
|
||||
JS_SetPropertyStr(
|
||||
ctx, view_val, "get", JS_NewCFunction(ctx, get_fn, "get", 1));
|
||||
auto size_atom = JS_NewAtom(ctx, "size");
|
||||
JS_DefinePropertyGetSet(
|
||||
ctx,
|
||||
view_val,
|
||||
size_atom,
|
||||
JS_NewCFunction2(
|
||||
ctx, size_fn, "size", 0, JS_CFUNC_getter, JS_CFUNC_getter_magic),
|
||||
ccf::js::constants::Undefined,
|
||||
0);
|
||||
JS_FreeAtom(ctx, size_atom);
|
||||
auto has_fn_val = ctx.new_c_function(has_fn, "has", 1);
|
||||
JS_CHECK_EXC(has_fn_val);
|
||||
JS_CHECK_SET(view_val.set("has", std::move(has_fn_val)));
|
||||
|
||||
JS_SetPropertyStr(
|
||||
ctx, view_val, "set", JS_NewCFunction(ctx, set_fn, "set", 2));
|
||||
JS_SetPropertyStr(
|
||||
ctx, view_val, "delete", JS_NewCFunction(ctx, delete_fn, "delete", 1));
|
||||
JS_SetPropertyStr(
|
||||
ctx, view_val, "clear", JS_NewCFunction(ctx, clear_fn, "clear", 0));
|
||||
auto get_fn_val = ctx.new_c_function(get_fn, "get", 1);
|
||||
JS_CHECK_EXC(get_fn_val);
|
||||
JS_CHECK_SET(view_val.set("get", std::move(get_fn_val)));
|
||||
|
||||
JS_SetPropertyStr(
|
||||
ctx, view_val, "forEach", JS_NewCFunction(ctx, foreach_fn, "forEach", 1));
|
||||
auto get_size_fn_val = ctx.new_getter_c_function(size_fn, "size");
|
||||
JS_CHECK_EXC(get_size_fn_val);
|
||||
JS_CHECK_SET(view_val.set_getter("size", std::move(get_size_fn_val)));
|
||||
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
view_val,
|
||||
"getVersionOfPreviousWrite",
|
||||
JS_NewCFunction(ctx, get_version_fn, "getVersionOfPreviousWrite", 1));
|
||||
auto set_fn_val = ctx.new_c_function(set_fn, "set", 2);
|
||||
JS_CHECK_EXC(set_fn_val);
|
||||
JS_CHECK_SET(view_val.set("set", std::move(set_fn_val)));
|
||||
|
||||
desc->flags = 0;
|
||||
desc->value = view_val;
|
||||
auto delete_fn_val = ctx.new_c_function(delete_fn, "delete", 1);
|
||||
JS_CHECK_EXC(delete_fn_val);
|
||||
JS_CHECK_SET(view_val.set("delete", std::move(delete_fn_val)));
|
||||
|
||||
auto clear_fn_val = ctx.new_c_function(clear_fn, "clear", 0);
|
||||
JS_CHECK_EXC(clear_fn_val);
|
||||
JS_CHECK_SET(view_val.set("clear", std::move(clear_fn_val)));
|
||||
|
||||
auto foreach_fn_val = ctx.new_c_function(foreach_fn, "forEach", 1);
|
||||
JS_CHECK_EXC(foreach_fn_val);
|
||||
JS_CHECK_SET(view_val.set("forEach", std::move(foreach_fn_val)));
|
||||
|
||||
auto get_version_fn_val =
|
||||
ctx.new_c_function(get_version_fn, "getVersionOfPreviousWrite", 1);
|
||||
JS_CHECK_EXC(get_version_fn_val);
|
||||
JS_CHECK_SET(
|
||||
view_val.set("getVersionOfPreviousWrite", std::move(get_version_fn_val)));
|
||||
|
||||
return view_val.take();
|
||||
}
|
||||
|
||||
static int js_kv_lookup(
|
||||
|
@ -633,39 +765,50 @@ namespace ccf::js
|
|||
JSAtom property)
|
||||
{
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
const auto property_name = jsctx.to_str(property).value_or("");
|
||||
LOG_TRACE_FMT("Looking for kv map '{}'", property_name);
|
||||
const auto map_name = jsctx.to_str(property).value_or("");
|
||||
LOG_TRACE_FMT("Looking for kv map '{}'", map_name);
|
||||
|
||||
auto tx_ctx_ptr =
|
||||
static_cast<TxContext*>(JS_GetOpaque(this_val, kv_class_id));
|
||||
const auto access_permission = _check_kv_map_access(jsctx.access, map_name);
|
||||
auto handle_val = _create_kv_map_handle<_get_map_handle_current>(
|
||||
jsctx, map_name, access_permission);
|
||||
if (JS_IsException(handle_val))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto access_permission =
|
||||
_check_kv_map_access(jsctx.access, property_name);
|
||||
|
||||
auto handle = tx_ctx_ptr->tx->rw<KVMap>(property_name);
|
||||
|
||||
_create_kv_map_handle(ctx, desc, handle, access_permission);
|
||||
desc->flags = 0;
|
||||
desc->value = handle_val;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int js_read_only_kv_lookup(
|
||||
static int js_historical_kv_lookup(
|
||||
JSContext* ctx,
|
||||
JSPropertyDescriptor* desc,
|
||||
JSValueConst this_val,
|
||||
JSAtom property)
|
||||
{
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
const auto property_name = jsctx.to_str(property).value_or("");
|
||||
LOG_TRACE_FMT("Looking for read-only kv map '{}'", property_name);
|
||||
|
||||
auto tx_ctx_ptr = static_cast<ReadOnlyTxContext*>(
|
||||
JS_GetOpaque(this_val, kv_read_only_class_id));
|
||||
|
||||
auto handle = tx_ctx_ptr->tx->ro<KVMap>(property_name);
|
||||
const auto map_name = jsctx.to_str(property).value_or("");
|
||||
auto seqno = reinterpret_cast<ccf::SeqNo>(
|
||||
JS_GetOpaque(this_val, kv_historical_class_id));
|
||||
LOG_TRACE_FMT(
|
||||
"Looking for historical kv map '{}' at seqno {}", map_name, seqno);
|
||||
|
||||
// Ignore evaluated access permissions - all tables are read-only
|
||||
_create_kv_map_handle(ctx, desc, handle, MapAccessPermissions::READ_ONLY);
|
||||
const auto access_permission = MapAccessPermissions::READ_ONLY;
|
||||
auto handle_val = _create_kv_map_handle<_get_map_handle_historical>(
|
||||
jsctx, map_name, access_permission);
|
||||
if (JS_IsException(handle_val))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Copy seqno from kv to handle
|
||||
JS_SetOpaque(handle_val, reinterpret_cast<void*>(seqno));
|
||||
|
||||
desc->flags = 0;
|
||||
desc->value = handle_val;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -680,8 +823,13 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(
|
||||
ctx, "Passed %d arguments, but expected none", argc);
|
||||
|
||||
auto body = static_cast<const std::vector<uint8_t>*>(
|
||||
JS_GetOpaque(this_val, body_class_id));
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
auto body = jsctx.globals.current_request_body;
|
||||
if (body == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(ctx, "No request body set");
|
||||
}
|
||||
|
||||
auto body_ = JS_NewStringLen(ctx, (const char*)body->data(), body->size());
|
||||
return body_;
|
||||
}
|
||||
|
@ -696,8 +844,13 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(
|
||||
ctx, "Passed %d arguments, but expected none", argc);
|
||||
|
||||
auto body = static_cast<const std::vector<uint8_t>*>(
|
||||
JS_GetOpaque(this_val, body_class_id));
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
auto body = jsctx.globals.current_request_body;
|
||||
if (body == nullptr)
|
||||
{
|
||||
return JS_ThrowTypeError(ctx, "No request body set");
|
||||
}
|
||||
|
||||
std::string body_str(body->begin(), body->end());
|
||||
auto body_ = JS_ParseJSON(ctx, body_str.c_str(), body->size(), "<body>");
|
||||
return body_;
|
||||
|
@ -713,8 +866,13 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(
|
||||
ctx, "Passed %d arguments, but expected none", argc);
|
||||
|
||||
auto body = static_cast<const std::vector<uint8_t>*>(
|
||||
JS_GetOpaque(this_val, body_class_id));
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
auto body = jsctx.globals.current_request_body;
|
||||
if (body == nullptr)
|
||||
{
|
||||
return JS_ThrowTypeError(ctx, "No request body set");
|
||||
}
|
||||
|
||||
auto body_ = JS_NewArrayBufferCopy(ctx, body->data(), body->size());
|
||||
return body_;
|
||||
}
|
||||
|
@ -735,16 +893,9 @@ namespace ccf::js
|
|||
auto gov_effects = static_cast<ccf::AbstractGovernanceEffects*>(
|
||||
JS_GetOpaque(this_val, node_class_id));
|
||||
|
||||
auto global_obj = jsctx.get_global_obj();
|
||||
JS_CHECK_EXC(global_obj);
|
||||
auto ccf = global_obj["ccf"];
|
||||
JS_CHECK_EXC(ccf);
|
||||
auto kv = ccf["kv"];
|
||||
JS_CHECK_EXC(kv);
|
||||
auto tx_ptr = jsctx.globals.tx;
|
||||
|
||||
auto tx_ctx_ptr = static_cast<TxContext*>(JS_GetOpaque(kv, kv_class_id));
|
||||
|
||||
if (tx_ctx_ptr->tx == nullptr)
|
||||
if (tx_ptr == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(
|
||||
ctx, "No transaction available to rekey ledger");
|
||||
|
@ -752,7 +903,7 @@ namespace ccf::js
|
|||
|
||||
try
|
||||
{
|
||||
bool result = gov_effects->rekey_ledger(*tx_ctx_ptr->tx);
|
||||
bool result = gov_effects->rekey_ledger(*tx_ptr);
|
||||
if (!result)
|
||||
{
|
||||
return JS_ThrowInternalError(ctx, "Could not rekey ledger");
|
||||
|
@ -789,16 +940,9 @@ namespace ccf::js
|
|||
return JS_ThrowInternalError(ctx, "Node state is not set");
|
||||
}
|
||||
|
||||
auto global_obj = jsctx.get_global_obj();
|
||||
JS_CHECK_EXC(global_obj);
|
||||
auto ccf = global_obj["ccf"];
|
||||
JS_CHECK_EXC(ccf);
|
||||
auto kv = ccf["kv"];
|
||||
JS_CHECK_EXC(kv);
|
||||
auto tx_ptr = jsctx.globals.tx;
|
||||
|
||||
auto tx_ctx_ptr = static_cast<TxContext*>(JS_GetOpaque(kv, kv_class_id));
|
||||
|
||||
if (tx_ctx_ptr->tx == nullptr)
|
||||
if (tx_ptr == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(
|
||||
ctx, "No transaction available to open service");
|
||||
|
@ -841,7 +985,7 @@ namespace ccf::js
|
|||
identities.next = crypto::Pem(next_bytes, next_bytes_sz);
|
||||
GOV_DEBUG_FMT("next service identity: {}", identities.next.str());
|
||||
|
||||
gov_effects->transition_service_to_open(*tx_ctx_ptr->tx, identities);
|
||||
gov_effects->transition_service_to_open(*tx_ptr, identities);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
@ -986,16 +1130,9 @@ namespace ccf::js
|
|||
return JS_ThrowInternalError(ctx, "Network state is not set");
|
||||
}
|
||||
|
||||
auto global_obj = jsctx.get_global_obj();
|
||||
JS_CHECK_EXC(global_obj);
|
||||
auto ccf = global_obj["ccf"];
|
||||
JS_CHECK_EXC(ccf);
|
||||
auto kv = ccf["kv"];
|
||||
JS_CHECK_EXC(kv);
|
||||
auto tx_ptr = jsctx.globals.tx;
|
||||
|
||||
auto tx_ctx_ptr = static_cast<TxContext*>(JS_GetOpaque(kv, kv_class_id));
|
||||
|
||||
if (tx_ctx_ptr->tx == nullptr)
|
||||
if (tx_ptr == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(
|
||||
ctx, "No transaction available to fetch latest ledger secret seqno");
|
||||
|
@ -1006,7 +1143,7 @@ namespace ccf::js
|
|||
try
|
||||
{
|
||||
latest_ledger_secret_seqno =
|
||||
network->ledger_secrets->get_latest(*tx_ctx_ptr->tx).first;
|
||||
network->ledger_secrets->get_latest(*tx_ptr).first;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
@ -1027,9 +1164,7 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(ctx, "Passed %d arguments but expected 1", argc);
|
||||
}
|
||||
|
||||
auto rpc_ctx =
|
||||
static_cast<ccf::RpcContext*>(JS_GetOpaque(this_val, rpc_class_id));
|
||||
|
||||
auto rpc_ctx = jsctx.globals.rpc_ctx;
|
||||
if (rpc_ctx == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(ctx, "RPC context is not set");
|
||||
|
@ -1048,14 +1183,14 @@ namespace ccf::js
|
|||
JSValue js_rpc_set_claims_digest(
|
||||
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
|
||||
|
||||
if (argc != 1)
|
||||
{
|
||||
return JS_ThrowTypeError(ctx, "Passed %d arguments but expected 1", argc);
|
||||
}
|
||||
|
||||
auto rpc_ctx =
|
||||
static_cast<ccf::RpcContext*>(JS_GetOpaque(this_val, rpc_class_id));
|
||||
|
||||
auto rpc_ctx = jsctx.globals.rpc_ctx;
|
||||
if (rpc_ctx == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(ctx, "RPC context is not set");
|
||||
|
@ -1098,21 +1233,14 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(ctx, "Passed %d arguments but expected 3", argc);
|
||||
}
|
||||
|
||||
auto global_obj = jsctx.get_global_obj();
|
||||
JS_CHECK_EXC(global_obj);
|
||||
auto ccf = global_obj["ccf"];
|
||||
JS_CHECK_EXC(ccf);
|
||||
auto kv = ccf["kv"];
|
||||
JS_CHECK_EXC(kv);
|
||||
auto tx_ptr = jsctx.globals.tx;
|
||||
|
||||
auto tx_ctx_ptr = static_cast<TxContext*>(JS_GetOpaque(kv, kv_class_id));
|
||||
|
||||
if (tx_ctx_ptr->tx == nullptr)
|
||||
if (tx_ptr == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(ctx, "No transaction available");
|
||||
}
|
||||
|
||||
auto& tx = *tx_ctx_ptr->tx;
|
||||
auto& tx = *tx_ptr;
|
||||
|
||||
auto issuer = jsctx.to_str(argv[0]);
|
||||
if (!issuer)
|
||||
|
@ -1177,16 +1305,9 @@ namespace ccf::js
|
|||
return JS_ThrowTypeError(ctx, "Passed %d arguments but expected 1", argc);
|
||||
}
|
||||
|
||||
auto global_obj = jsctx.get_global_obj();
|
||||
JS_CHECK_EXC(global_obj);
|
||||
auto ccf = global_obj["ccf"];
|
||||
JS_CHECK_EXC(ccf);
|
||||
auto kv = ccf["kv"];
|
||||
JS_CHECK_EXC(kv);
|
||||
auto tx_ptr = jsctx.globals.tx;
|
||||
|
||||
auto tx_ctx_ptr = static_cast<TxContext*>(JS_GetOpaque(kv, kv_class_id));
|
||||
|
||||
if (tx_ctx_ptr->tx == nullptr)
|
||||
if (tx_ptr == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(ctx, "No transaction available");
|
||||
}
|
||||
|
@ -1199,7 +1320,7 @@ namespace ccf::js
|
|||
|
||||
try
|
||||
{
|
||||
auto& tx = *tx_ctx_ptr->tx;
|
||||
auto& tx = *tx_ptr;
|
||||
ccf::remove_jwt_public_signing_keys(tx, *issuer);
|
||||
}
|
||||
catch (std::exception& exc)
|
||||
|
@ -1226,16 +1347,9 @@ namespace ccf::js
|
|||
|
||||
auto gov_effects = static_cast<ccf::AbstractGovernanceEffects*>(
|
||||
JS_GetOpaque(this_val, node_class_id));
|
||||
auto global_obj = jsctx.get_global_obj();
|
||||
JS_CHECK_EXC(global_obj);
|
||||
auto ccf = global_obj["ccf"];
|
||||
JS_CHECK_EXC(ccf);
|
||||
auto kv = ccf["kv"];
|
||||
JS_CHECK_EXC(kv);
|
||||
auto tx_ptr = jsctx.globals.tx;
|
||||
|
||||
auto tx_ctx_ptr = static_cast<TxContext*>(JS_GetOpaque(kv, kv_class_id));
|
||||
|
||||
if (tx_ctx_ptr->tx == nullptr)
|
||||
if (tx_ptr == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(
|
||||
ctx, "No transaction available to open service");
|
||||
|
@ -1243,7 +1357,7 @@ namespace ccf::js
|
|||
|
||||
try
|
||||
{
|
||||
gov_effects->trigger_recovery_shares_refresh(*tx_ctx_ptr->tx);
|
||||
gov_effects->trigger_recovery_shares_refresh(*tx_ptr);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
@ -1265,23 +1379,16 @@ namespace ccf::js
|
|||
|
||||
auto gov_effects = static_cast<ccf::AbstractGovernanceEffects*>(
|
||||
JS_GetOpaque(this_val, node_class_id));
|
||||
auto global_obj = jsctx.get_global_obj();
|
||||
JS_CHECK_EXC(global_obj);
|
||||
auto ccf = global_obj["ccf"];
|
||||
JS_CHECK_EXC(ccf);
|
||||
auto kv = ccf["kv"];
|
||||
JS_CHECK_EXC(kv);
|
||||
auto tx_ptr = jsctx.globals.tx;
|
||||
|
||||
auto tx_ctx_ptr = static_cast<TxContext*>(JS_GetOpaque(kv, kv_class_id));
|
||||
|
||||
if (tx_ctx_ptr->tx == nullptr)
|
||||
if (tx_ptr == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(ctx, "No transaction available");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
gov_effects->trigger_ledger_chunk(*tx_ctx_ptr->tx);
|
||||
gov_effects->trigger_ledger_chunk(*tx_ptr);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
@ -1303,23 +1410,16 @@ namespace ccf::js
|
|||
|
||||
auto gov_effects = static_cast<ccf::AbstractGovernanceEffects*>(
|
||||
JS_GetOpaque(this_val, node_class_id));
|
||||
auto global_obj = jsctx.get_global_obj();
|
||||
JS_CHECK_EXC(global_obj);
|
||||
auto ccf = global_obj["ccf"];
|
||||
JS_CHECK_EXC(ccf);
|
||||
auto kv = ccf["kv"];
|
||||
JS_CHECK_EXC(kv);
|
||||
auto tx_ptr = jsctx.globals.tx;
|
||||
|
||||
auto tx_ctx_ptr = static_cast<TxContext*>(JS_GetOpaque(kv, kv_class_id));
|
||||
|
||||
if (tx_ctx_ptr->tx == nullptr)
|
||||
if (tx_ptr == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(ctx, "No transaction available");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
gov_effects->trigger_snapshot(*tx_ctx_ptr->tx);
|
||||
gov_effects->trigger_snapshot(*tx_ptr);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
@ -1385,16 +1485,9 @@ namespace ccf::js
|
|||
|
||||
auto gov_effects = static_cast<ccf::AbstractGovernanceEffects*>(
|
||||
JS_GetOpaque(this_val, node_class_id));
|
||||
auto global_obj = jsctx.get_global_obj();
|
||||
JS_CHECK_EXC(global_obj);
|
||||
auto ccf = global_obj["ccf"];
|
||||
JS_CHECK_EXC(ccf);
|
||||
auto kv = ccf["kv"];
|
||||
JS_CHECK_EXC(kv);
|
||||
auto tx_ptr = jsctx.globals.tx;
|
||||
|
||||
auto tx_ctx_ptr = static_cast<TxContext*>(JS_GetOpaque(kv, kv_class_id));
|
||||
|
||||
if (tx_ctx_ptr->tx == nullptr)
|
||||
if (tx_ptr == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(ctx, "No transaction available");
|
||||
}
|
||||
|
@ -1416,7 +1509,7 @@ namespace ccf::js
|
|||
opt_interfaces = interfaces;
|
||||
}
|
||||
|
||||
gov_effects->trigger_acme_refresh(*tx_ctx_ptr->tx, opt_interfaces);
|
||||
gov_effects->trigger_acme_refresh(*tx_ptr, opt_interfaces);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
@ -1623,25 +1716,18 @@ namespace ccf::js
|
|||
ctx, "Passed %d arguments but expected none", argc);
|
||||
}
|
||||
|
||||
auto global_obj = jsctx.get_global_obj();
|
||||
JS_CHECK_EXC(global_obj);
|
||||
auto ccf = global_obj["ccf"];
|
||||
JS_CHECK_EXC(ccf);
|
||||
auto kv = ccf["kv"];
|
||||
JS_CHECK_EXC(kv);
|
||||
auto tx_ptr = jsctx.globals.tx;
|
||||
|
||||
auto tx_ctx_ptr = static_cast<TxContext*>(JS_GetOpaque(kv, kv_class_id));
|
||||
|
||||
if (tx_ctx_ptr->tx == nullptr)
|
||||
if (tx_ptr == nullptr)
|
||||
{
|
||||
return JS_ThrowInternalError(ctx, "No transaction available");
|
||||
}
|
||||
|
||||
auto& tx = *tx_ctx_ptr->tx;
|
||||
auto& tx = *tx_ptr;
|
||||
|
||||
js::Context ctx2(js::TxAccess::APP);
|
||||
ctx2.runtime().set_runtime_options(
|
||||
tx_ctx_ptr->tx, js::RuntimeLimitsPolicy::NO_LOWER_THAN_DEFAULTS);
|
||||
tx_ptr, js::RuntimeLimitsPolicy::NO_LOWER_THAN_DEFAULTS);
|
||||
JS_SetModuleLoaderFunc(
|
||||
ctx2.runtime(), nullptr, js::js_app_module_loader, &tx);
|
||||
|
||||
|
@ -1701,16 +1787,16 @@ namespace ccf::js
|
|||
kv_class_def.class_name = "KV Tables";
|
||||
kv_class_def.exotic = &kv_exotic_methods;
|
||||
|
||||
JS_NewClassID(&kv_read_only_class_id);
|
||||
kv_read_only_exotic_methods.get_own_property = js_read_only_kv_lookup;
|
||||
kv_read_only_class_def.class_name = "Read-only KV Tables";
|
||||
kv_read_only_class_def.exotic = &kv_read_only_exotic_methods;
|
||||
JS_NewClassID(&kv_historical_class_id);
|
||||
kv_historical_exotic_methods.get_own_property = js_historical_kv_lookup;
|
||||
kv_historical_class_def.class_name = "Read-only Historical KV Tables";
|
||||
kv_historical_class_def.exotic = &kv_historical_exotic_methods;
|
||||
|
||||
JS_NewClassID(&kv_map_handle_class_id);
|
||||
kv_map_handle_class_def.class_name = "KV Map Handle";
|
||||
|
||||
JS_NewClassID(&body_class_id);
|
||||
body_class_def.class_name = "Body";
|
||||
body_class_def.class_name = "Current Request Body";
|
||||
|
||||
JS_NewClassID(&node_class_id);
|
||||
node_class_def.class_name = "Node";
|
||||
|
@ -1732,7 +1818,6 @@ namespace ccf::js
|
|||
|
||||
JS_NewClassID(&historical_state_class_id);
|
||||
historical_state_class_def.class_name = "HistoricalState";
|
||||
historical_state_class_def.finalizer = js_historical_state_finalizer;
|
||||
}
|
||||
|
||||
std::optional<std::stringstream> stringify_args(
|
||||
|
@ -2261,40 +2346,52 @@ namespace ccf::js
|
|||
}
|
||||
}
|
||||
|
||||
void populate_global_ccf_kv(TxContext* txctx, js::Context& ctx)
|
||||
void populate_global_ccf_kv(kv::Tx& tx, js::Context& ctx)
|
||||
{
|
||||
auto kv = JS_NewObjectClass(ctx, kv_class_id);
|
||||
JS_SetOpaque(kv, txctx);
|
||||
ctx.globals.tx = &tx;
|
||||
|
||||
auto ccf = ctx.get_global_property("ccf");
|
||||
JS_SetPropertyStr(ctx, ccf, "kv", kv);
|
||||
}
|
||||
|
||||
void populate_global_ccf_historical_state(
|
||||
ReadOnlyTxContext* historical_txctx,
|
||||
const ccf::TxID& transaction_id,
|
||||
ccf::TxReceiptImplPtr receipt,
|
||||
js::Context& ctx)
|
||||
JSValue create_historical_state_object(
|
||||
js::Context& jsctx, ccf::historical::StatePtr state)
|
||||
{
|
||||
// Historical queries
|
||||
if (receipt != nullptr)
|
||||
auto js_state = jsctx.new_obj_class(historical_state_class_id);
|
||||
JS_CHECK_EXC(js_state);
|
||||
|
||||
const auto transaction_id = state->transaction_id;
|
||||
auto transaction_id_s = jsctx.new_string(transaction_id.to_str());
|
||||
JS_CHECK_EXC(transaction_id_s);
|
||||
JS_CHECK_SET(js_state.set("transactionId", std::move(transaction_id_s)));
|
||||
|
||||
// NB: ccf_receipt_to_js returns a JSValue (unwrapped), due to its use of
|
||||
// macros. So we must rewrap it here, immediately after returning
|
||||
auto js_receipt = jsctx(ccf_receipt_to_js(jsctx, state->receipt));
|
||||
JS_CHECK_EXC(js_receipt);
|
||||
JS_CHECK_SET(js_state.set("receipt", std::move(js_receipt)));
|
||||
|
||||
auto kv = jsctx.new_obj_class(kv_historical_class_id);
|
||||
JS_CHECK_EXC(kv);
|
||||
JS_SetOpaque(kv, reinterpret_cast<void*>(transaction_id.seqno));
|
||||
JS_CHECK_SET(js_state.set("kv", std::move(kv)));
|
||||
|
||||
try
|
||||
{
|
||||
auto state = JS_NewObject(ctx);
|
||||
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
state,
|
||||
"transactionId",
|
||||
JS_NewString(ctx, transaction_id.to_str().c_str()));
|
||||
auto js_receipt = ccf_receipt_to_js(&ctx, receipt);
|
||||
JS_SetPropertyStr(ctx, state, "receipt", js_receipt);
|
||||
auto kv = JS_NewObjectClass(ctx, kv_read_only_class_id);
|
||||
JS_SetOpaque(kv, historical_txctx);
|
||||
JS_SetPropertyStr(ctx, state, "kv", kv);
|
||||
|
||||
auto ccf = ctx.get_global_property("ccf");
|
||||
JS_SetPropertyStr(ctx, ccf, "historicalState", state);
|
||||
// Create a tx which will be used to access this state
|
||||
auto tx = state->store->create_read_only_tx_ptr();
|
||||
// Extend lifetime of state and tx, by storing on the ctx
|
||||
jsctx.globals.historical_handles[transaction_id.seqno] = {
|
||||
state, std::move(tx)};
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
return JS_ThrowInternalError(
|
||||
jsctx, "Failed to create read-only historical tx: %s", e.what());
|
||||
}
|
||||
|
||||
return js_state.take();
|
||||
}
|
||||
|
||||
void populate_global_ccf_node(
|
||||
|
@ -2422,7 +2519,7 @@ namespace ccf::js
|
|||
void populate_global_ccf_rpc(ccf::RpcContext* rpc_ctx, js::Context& ctx)
|
||||
{
|
||||
auto rpc = JS_NewObjectClass(ctx, rpc_class_id);
|
||||
JS_SetOpaque(rpc, rpc_ctx);
|
||||
ctx.globals.rpc_ctx = rpc_ctx;
|
||||
auto ccf = ctx.get_global_property("ccf");
|
||||
JS_SetPropertyStr(ctx, ccf, "rpc", rpc);
|
||||
JS_SetPropertyStr(
|
||||
|
@ -2484,11 +2581,32 @@ namespace ccf::js
|
|||
ctx, js_historical_drop_cached_states, "dropCachedStates", 1));
|
||||
}
|
||||
|
||||
void invalidate_globals(js::Context& ctx)
|
||||
{
|
||||
// Reset any state that has been stored on the ctx object to implement
|
||||
// globals. This should be called at the end of any invocation where the
|
||||
// globals may point to locally-scoped memory, and the Context itself (the
|
||||
// interpreter) may live longer and be reused for future calls. Those calls
|
||||
// must re-populate the globals appropriately, pointing to their own local
|
||||
// instances of state as required.
|
||||
|
||||
ctx.globals.tx = nullptr;
|
||||
|
||||
// Any KV handles which have been created with reference to this tx should
|
||||
// no longer be accessed. Any future calls on these JSValues will
|
||||
// re-populate this map with fresh KVMap::Handle*s
|
||||
ctx.globals.kv_handles.clear();
|
||||
|
||||
ctx.globals.historical_handles.clear();
|
||||
|
||||
ctx.globals.rpc_ctx = nullptr;
|
||||
}
|
||||
|
||||
void Runtime::add_ccf_classdefs()
|
||||
{
|
||||
std::vector<std::pair<JSClassID, JSClassDef*>> classes{
|
||||
{kv_class_id, &kv_class_def},
|
||||
{kv_read_only_class_id, &kv_read_only_class_def},
|
||||
{kv_historical_class_id, &kv_historical_class_def},
|
||||
{kv_map_handle_class_id, &kv_map_handle_class_def},
|
||||
{body_class_id, &body_class_def},
|
||||
{node_class_id, &node_class_def},
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
namespace ccf::js
|
||||
{
|
||||
extern JSClassID kv_class_id;
|
||||
extern JSClassID kv_read_only_class_id;
|
||||
extern JSClassID kv_historical_class_id;
|
||||
extern JSClassID kv_map_handle_class_id;
|
||||
extern JSClassID body_class_id;
|
||||
extern JSClassID node_class_id;
|
||||
|
@ -57,15 +57,6 @@ namespace ccf::js
|
|||
extern JSClassID historical_class_id;
|
||||
extern JSClassID historical_state_class_id;
|
||||
|
||||
extern JSClassDef kv_class_def;
|
||||
extern JSClassExoticMethods kv_exotic_methods;
|
||||
extern JSClassDef kv_read_only_class_def;
|
||||
extern JSClassExoticMethods kv_read_only_exotic_methods;
|
||||
extern JSClassDef kv_map_handle_class_def;
|
||||
extern JSClassDef body_class_def;
|
||||
extern JSClassDef node_class_def;
|
||||
extern JSClassDef network_class_def;
|
||||
|
||||
const std::chrono::milliseconds default_max_execution_time{1000};
|
||||
const size_t default_stack_size = 1024 * 1024;
|
||||
const size_t default_heap_size = 100 * 1024 * 1024;
|
||||
|
@ -92,23 +83,6 @@ namespace ccf::js
|
|||
GOV_RW
|
||||
};
|
||||
|
||||
struct TxContext
|
||||
{
|
||||
kv::Tx* tx = nullptr;
|
||||
};
|
||||
|
||||
struct ReadOnlyTxContext
|
||||
{
|
||||
kv::ReadOnlyTx* tx = nullptr;
|
||||
};
|
||||
|
||||
struct HistoricalStateContext
|
||||
{
|
||||
ccf::historical::StatePtr state;
|
||||
kv::ReadOnlyTx tx;
|
||||
ReadOnlyTxContext tx_ctx;
|
||||
};
|
||||
|
||||
namespace constants
|
||||
{
|
||||
// "compound literals are a C99-specific feature"
|
||||
|
@ -192,6 +166,26 @@ namespace ccf::js
|
|||
return rc;
|
||||
}
|
||||
|
||||
int set_getter(const char* prop, JSWrappedValue&& getter) const
|
||||
{
|
||||
JSAtom size_atom = JS_NewAtom(ctx, prop);
|
||||
if (size_atom == JS_ATOM_NULL)
|
||||
{
|
||||
getter.val = ccf::js::constants::Null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// NB: Where other calls check the return code to determine whether they
|
||||
// are responsible for freeing, this call unconditionally frees the getter
|
||||
// arg, so we call .take() to always drop our local owning reference
|
||||
int rc = JS_DefinePropertyGetSet(
|
||||
ctx, val, size_atom, getter.take(), ccf::js::constants::Undefined, 0);
|
||||
|
||||
JS_FreeAtom(ctx, size_atom);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int set(const std::string& prop, JSWrappedValue&& value) const
|
||||
{
|
||||
return set(prop.c_str(), std::move(value));
|
||||
|
@ -271,12 +265,7 @@ namespace ccf::js
|
|||
void register_request_body_class(JSContext* ctx);
|
||||
|
||||
void init_globals(Context& ctx);
|
||||
void populate_global_ccf_kv(TxContext* txctx, js::Context& ctx);
|
||||
void populate_global_ccf_historical_state(
|
||||
ReadOnlyTxContext* historical_txctx,
|
||||
const ccf::TxID& transaction_id,
|
||||
ccf::TxReceiptImplPtr receipt,
|
||||
js::Context& ctx);
|
||||
void populate_global_ccf_kv(kv::Tx& tx, js::Context& ctx);
|
||||
void populate_global_ccf_node(
|
||||
ccf::AbstractGovernanceEffects* gov_effects, js::Context& ctx);
|
||||
void populate_global_ccf_gov_actions(js::Context& ctx);
|
||||
|
@ -289,6 +278,10 @@ namespace ccf::js
|
|||
ccf::NetworkState* network_state, js::Context& ctx);
|
||||
void populate_global_ccf_historical(
|
||||
ccf::historical::AbstractStateCache* historical_state, js::Context& ctx);
|
||||
void invalidate_globals(js::Context& ctx);
|
||||
|
||||
JSValue create_historical_state_object(
|
||||
js::Context& ctx, ccf::historical::StatePtr state);
|
||||
|
||||
JSValue js_print(JSContext* ctx, JSValueConst, int argc, JSValueConst* argv);
|
||||
std::pair<std::string, std::optional<std::string>> js_error_message(
|
||||
|
@ -377,6 +370,29 @@ namespace ccf::js
|
|||
bool implement_untrusted_time = false;
|
||||
bool log_execution_metrics = true;
|
||||
|
||||
// State which may be set by calls to populate_global_ccf_*. Likely
|
||||
// references transaction-scoped entries, so should be cleared between
|
||||
// calls. Retained handles to these globals must not access the previous
|
||||
// values.
|
||||
struct
|
||||
{
|
||||
kv::Tx* tx = nullptr;
|
||||
std::unordered_map<std::string, kv::untyped::Map::Handle*> kv_handles;
|
||||
|
||||
struct HistoricalHandle
|
||||
{
|
||||
ccf::historical::StatePtr state;
|
||||
std::unique_ptr<kv::ReadOnlyTx> tx;
|
||||
std::unordered_map<std::string, kv::untyped::Map::ReadOnlyHandle*>
|
||||
kv_handles = {};
|
||||
};
|
||||
std::unordered_map<ccf::SeqNo, HistoricalHandle> historical_handles;
|
||||
|
||||
ccf::RpcContext* rpc_ctx = nullptr;
|
||||
|
||||
const std::vector<uint8_t>* current_request_body = nullptr;
|
||||
} globals;
|
||||
|
||||
Context(TxAccess acc) : access(acc)
|
||||
{
|
||||
ctx = JS_NewContext(rt);
|
||||
|
@ -558,6 +574,13 @@ namespace ccf::js
|
|||
return W(JS_NewCFunction(ctx, func, name, length));
|
||||
}
|
||||
|
||||
JSWrappedValue new_getter_c_function(
|
||||
JSCFunction* func, const char* name) const
|
||||
{
|
||||
return W(JS_NewCFunction2(
|
||||
ctx, func, name, 0, JS_CFUNC_getter, JS_CFUNC_getter_magic));
|
||||
}
|
||||
|
||||
JSWrappedValue eval(
|
||||
const char* input,
|
||||
size_t input_len,
|
||||
|
|
|
@ -1231,6 +1231,11 @@ namespace kv
|
|||
return ReadOnlyTx(this);
|
||||
}
|
||||
|
||||
std::unique_ptr<ReadOnlyTx> create_read_only_tx_ptr() override
|
||||
{
|
||||
return std::make_unique<ReadOnlyTx>(this);
|
||||
}
|
||||
|
||||
TxDiff create_tx_diff() override
|
||||
{
|
||||
return TxDiff(this);
|
||||
|
|
|
@ -140,8 +140,7 @@ namespace ccf::gov::endpoints
|
|||
for (const auto& [mid, mb] : proposal_info.ballots)
|
||||
{
|
||||
js::Context js_context(js::TxAccess::GOV_RO);
|
||||
js::TxContext txctx{&tx};
|
||||
js::populate_global_ccf_kv(&txctx, js_context);
|
||||
js::populate_global_ccf_kv(tx, js_context);
|
||||
auto ballot_func = js_context.function(
|
||||
mb,
|
||||
"vote",
|
||||
|
@ -186,8 +185,7 @@ namespace ccf::gov::endpoints
|
|||
{
|
||||
{
|
||||
js::Context js_context(js::TxAccess::GOV_RO);
|
||||
js::TxContext txctx{&tx};
|
||||
js::populate_global_ccf_kv(&txctx, js_context);
|
||||
js::populate_global_ccf_kv(tx, js_context);
|
||||
auto resolve_func = js_context.function(
|
||||
constitution, "resolve", "public:ccf.gov.constitution[0]");
|
||||
|
||||
|
@ -274,7 +272,6 @@ namespace ccf::gov::endpoints
|
|||
{
|
||||
// Evaluate apply function
|
||||
js::Context js_context(js::TxAccess::GOV_RW);
|
||||
js::TxContext txctx{&tx};
|
||||
|
||||
auto gov_effects =
|
||||
context.get_subsystem<AbstractGovernanceEffects>();
|
||||
|
@ -284,7 +281,7 @@ namespace ccf::gov::endpoints
|
|||
"Unexpected: Could not access GovEffects subsytem");
|
||||
}
|
||||
|
||||
js::populate_global_ccf_kv(&txctx, js_context);
|
||||
js::populate_global_ccf_kv(tx, js_context);
|
||||
js::populate_global_ccf_node(gov_effects.get(), js_context);
|
||||
js::populate_global_ccf_network(&network, js_context);
|
||||
js::populate_global_ccf_gov_actions(js_context);
|
||||
|
@ -448,8 +445,7 @@ namespace ccf::gov::endpoints
|
|||
}
|
||||
|
||||
js::Context context(js::TxAccess::GOV_RO);
|
||||
js::TxContext txctx{&ctx.tx};
|
||||
js::populate_global_ccf_kv(&txctx, context);
|
||||
js::populate_global_ccf_kv(ctx.tx, context);
|
||||
|
||||
auto validate_func = context.function(
|
||||
constitution.value(),
|
||||
|
|
|
@ -149,8 +149,7 @@ namespace ccf
|
|||
for (const auto& [mid, mb] : pi_->ballots)
|
||||
{
|
||||
js::Context context(js::TxAccess::GOV_RO);
|
||||
js::TxContext txctx{&tx};
|
||||
js::populate_global_ccf_kv(&txctx, context);
|
||||
js::populate_global_ccf_kv(tx, context);
|
||||
auto ballot_func = context.function(
|
||||
mb,
|
||||
"vote",
|
||||
|
@ -191,8 +190,7 @@ namespace ccf
|
|||
|
||||
{
|
||||
js::Context js_context(js::TxAccess::GOV_RO);
|
||||
js::TxContext txctx{&tx};
|
||||
js::populate_global_ccf_kv(&txctx, js_context);
|
||||
js::populate_global_ccf_kv(tx, js_context);
|
||||
auto resolve_func = js_context.function(
|
||||
constitution, "resolve", "public:ccf.gov.constitution[0]");
|
||||
|
||||
|
@ -292,8 +290,6 @@ namespace ccf
|
|||
{
|
||||
js::Context apply_js_context(js::TxAccess::GOV_RW);
|
||||
|
||||
js::TxContext apply_txctx{&tx};
|
||||
|
||||
auto gov_effects =
|
||||
context.get_subsystem<AbstractGovernanceEffects>();
|
||||
if (gov_effects == nullptr)
|
||||
|
@ -302,7 +298,7 @@ namespace ccf
|
|||
"Unexpected: Could not access GovEffects subsytem");
|
||||
}
|
||||
|
||||
js::populate_global_ccf_kv(&apply_txctx, apply_js_context);
|
||||
js::populate_global_ccf_kv(tx, apply_js_context);
|
||||
js::populate_global_ccf_node(gov_effects.get(), apply_js_context);
|
||||
js::populate_global_ccf_network(&network, apply_js_context);
|
||||
js::populate_global_ccf_gov_actions(apply_js_context);
|
||||
|
@ -1161,8 +1157,7 @@ namespace ccf
|
|||
auto validate_script = constitution.value();
|
||||
|
||||
js::Context context(js::TxAccess::GOV_RO);
|
||||
js::TxContext txctx{&ctx.tx};
|
||||
js::populate_global_ccf_kv(&txctx, context);
|
||||
js::populate_global_ccf_kv(ctx.tx, context);
|
||||
|
||||
auto validate_func = context.function(
|
||||
validate_script, "validate", "public:ccf.gov.constitution[0]");
|
||||
|
|
|
@ -434,7 +434,7 @@ class LoggingTxs:
|
|||
expected_result = {"msg": msg}
|
||||
assert (
|
||||
rep.body.json() == expected_result
|
||||
), "Expected {}, got {}".format(expected_result, rep.body)
|
||||
), f"Expected {expected_result}, got {rep.body.json()}"
|
||||
found = True
|
||||
break
|
||||
elif rep.status_code == http.HTTPStatus.NOT_FOUND:
|
||||
|
|
|
@ -978,6 +978,26 @@ def test_caching_of_kv_handles(network, args):
|
|||
r = c.post("/app/increment")
|
||||
assert r.status_code == http.HTTPStatus.OK, r
|
||||
|
||||
LOG.info("Testing caching of ccf JS globals")
|
||||
|
||||
def make_body():
|
||||
return {str(uuid.uuid4()): str(uuid.uuid4())}
|
||||
|
||||
body = make_body()
|
||||
r = c.post("/app/globals", body)
|
||||
assert r.status_code == http.HTTPStatus.OK, r
|
||||
assert r.body.json() == body
|
||||
|
||||
body = make_body()
|
||||
r = c.post("/app/globals", body)
|
||||
assert r.status_code == http.HTTPStatus.OK, r
|
||||
assert r.body.json() == body
|
||||
|
||||
body = make_body()
|
||||
r = c.post("/app/globals", body)
|
||||
assert r.status_code == http.HTTPStatus.OK, r
|
||||
assert r.body.json() == body
|
||||
|
||||
return network
|
||||
|
||||
|
||||
|
|
|
@ -71,7 +71,20 @@
|
|||
"mode": "readwrite",
|
||||
"openapi": {},
|
||||
"interpreter_reuse": {
|
||||
"key": "increment"
|
||||
"key": "global_handle"
|
||||
}
|
||||
}
|
||||
},
|
||||
"/globals": {
|
||||
"post": {
|
||||
"js_module": "global_handle.js",
|
||||
"js_function": "globals",
|
||||
"forwarding_required": "never",
|
||||
"authn_policies": ["no_auth"],
|
||||
"mode": "readwrite",
|
||||
"openapi": {},
|
||||
"interpreter_reuse": {
|
||||
"key": "global_handle"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,4 +1,73 @@
|
|||
import * as ccfapp from "@microsoft/ccf-app";
|
||||
import { TransactionId, ccf } from "@microsoft/ccf-app/global";
|
||||
|
||||
function globals(request) {
|
||||
// ccf.rpc
|
||||
{
|
||||
if (!("rpc" in globalThis)) {
|
||||
globalThis.rpc = ccf.rpc;
|
||||
}
|
||||
|
||||
globalThis.rpc.setApplyWrites(false);
|
||||
globalThis.rpc.setApplyWrites(true);
|
||||
|
||||
const claims_digest = ccf.crypto.digest(
|
||||
"SHA-256",
|
||||
ccf.strToBuf("Hello world"),
|
||||
);
|
||||
globalThis.rpc.setClaimsDigest(claims_digest);
|
||||
}
|
||||
|
||||
// ccf.host
|
||||
{
|
||||
if (!("host" in globalThis)) {
|
||||
// ccf.host is not described in TypeScript, so work around the type system here
|
||||
let globalAny: any = ccf;
|
||||
globalThis.host = globalAny.host;
|
||||
}
|
||||
|
||||
globalThis.host.triggerSubprocess(["echo", '"Hello world"']);
|
||||
}
|
||||
|
||||
// ccf.consensus
|
||||
var txId: TransactionId;
|
||||
{
|
||||
if (!("consensus" in globalThis)) {
|
||||
globalThis.consensus = ccf.consensus;
|
||||
}
|
||||
|
||||
txId = globalThis.consensus.getLastCommittedTxId();
|
||||
globalThis.consensus.getStatusForTxId(txId.view, txId.seqno);
|
||||
globalThis.consensus.getViewForSeqno(txId.seqno);
|
||||
}
|
||||
|
||||
// ccf.historical
|
||||
{
|
||||
if (!("historical" in globalThis)) {
|
||||
globalThis.historical = ccf.historical;
|
||||
}
|
||||
|
||||
const handle: number = 1;
|
||||
globalThis.historical.getStateRange(handle, 1, txId.seqno, 180);
|
||||
globalThis.historical.dropCachedStates(handle);
|
||||
}
|
||||
|
||||
// request
|
||||
var body;
|
||||
{
|
||||
if (!("requestBody" in globalThis)) {
|
||||
// NB: Stashing the request like this is an extremely suspicious thing to do!
|
||||
// The semantics here are unexpected - body is actually a light wrapper around
|
||||
// whatever the _current_ request body is. If the original request is desired,
|
||||
// it must be extracted first (eg - stashed = request.body.arrayBuffer())
|
||||
globalThis.requestBody = request.body;
|
||||
}
|
||||
|
||||
body = globalThis.requestBody.json();
|
||||
}
|
||||
|
||||
return { body: body };
|
||||
}
|
||||
|
||||
function increment() {
|
||||
if (!("kvHandle" in globalThis)) {
|
||||
|
@ -22,4 +91,4 @@ function increment() {
|
|||
return { body: { value: v } };
|
||||
}
|
||||
|
||||
export { increment };
|
||||
export { globals, increment };
|
||||
|
|
Загрузка…
Ссылка в новой задаче