Don't store temporary pointers in JSValues (#5740)

This commit is contained in:
Eddy Ashton 2023-11-01 09:54:29 +00:00 коммит произвёл GitHub
Родитель 144cbe06c0
Коммит 8f7afdb164
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 575 добавлений и 367 удалений

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

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

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

@ -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 };