Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
188 lines
6.7 KiB
Erlang
188 lines
6.7 KiB
Erlang
-module(bootstrap).
|
|
-export([read_genesis/0, read_genesis/1,
|
|
read_section/2, sections/0, section_subdir/1,
|
|
default_base/0, ends_with_sx/1,
|
|
build_genesis/1, verify_genesis/2,
|
|
cidhash_path/1, write_cidhash/2, read_cidhash/1,
|
|
load_genesis/1, strip_sx_suffix/1]).
|
|
|
|
%% Genesis bundle reader per design §12.2.
|
|
%%
|
|
%% read_genesis/0,1 walks the seven canonical section subdirectories
|
|
%% under `next/genesis/`, filters .sx files, reads each file into a
|
|
%% binary, and returns a structured snapshot:
|
|
%%
|
|
%% {ok, [{Section :: atom,
|
|
%% [{FileName :: binary, FileBytes :: binary}, ...]},
|
|
%% ...]}
|
|
%%
|
|
%% Step 4d will compute the bundle CID by hashing the assembled
|
|
%% byte string across all entries; Step 4e will register the parsed
|
|
%% definitions in the kernel registry.
|
|
%%
|
|
%% Port note: this module does NOT parse the .sx contents. The
|
|
%% Erlang-on-SX port has no in-Erlang path from binary bytes to SX
|
|
%% structured terms (same substrate gap that parked Step 3b); the
|
|
%% bundle CID needs only the raw bytes, and registry registration
|
|
%% will happen via an SX-side helper that the kernel hands the
|
|
%% binary contents to. read_genesis/1 ignores its arg in v1 except
|
|
%% to swap the BasePath — `default_base/0` is "next/genesis".
|
|
%%
|
|
%% Port note 2: string-literal binary segments `<<"abc">>` truncate
|
|
%% to one byte in this port, so all path constants are hand-spelled
|
|
%% as integer-segment binaries.
|
|
|
|
%% ── Public API ──────────────────────────────────────────────────
|
|
|
|
%% "next/genesis"
|
|
default_base() ->
|
|
<<110,101,120,116,47,103,101,110,101,115,105,115>>.
|
|
|
|
read_genesis() ->
|
|
read_genesis(default_base()).
|
|
|
|
read_genesis(BasePath) ->
|
|
{ok, lists:map(
|
|
fun (S) -> {S, read_section(BasePath, S)} end,
|
|
sections())}.
|
|
|
|
sections() ->
|
|
[activity_types, object_types, projections,
|
|
validators, codecs, sig_suites, audience].
|
|
|
|
%% "activity-types"
|
|
section_subdir(activity_types) ->
|
|
<<97,99,116,105,118,105,116,121,45,116,121,112,101,115>>;
|
|
%% "object-types"
|
|
section_subdir(object_types) ->
|
|
<<111,98,106,101,99,116,45,116,121,112,101,115>>;
|
|
%% "projections"
|
|
section_subdir(projections) ->
|
|
<<112,114,111,106,101,99,116,105,111,110,115>>;
|
|
%% "validators"
|
|
section_subdir(validators) ->
|
|
<<118,97,108,105,100,97,116,111,114,115>>;
|
|
%% "codecs"
|
|
section_subdir(codecs) ->
|
|
<<99,111,100,101,99,115>>;
|
|
%% "sig-suites"
|
|
section_subdir(sig_suites) ->
|
|
<<115,105,103,45,115,117,105,116,101,115>>;
|
|
%% "audience"
|
|
section_subdir(audience) ->
|
|
<<97,117,100,105,101,110,99,101>>.
|
|
|
|
read_section(BasePath, Section) ->
|
|
SubDir = section_subdir(Section),
|
|
%% 47 = '/'
|
|
Path = <<BasePath/binary, 47, SubDir/binary>>,
|
|
case file:list_dir(Path) of
|
|
{ok, Names} ->
|
|
SxNames = lists:filter(fun (N) -> ends_with_sx(N) end, Names),
|
|
lists:map(fun (Name) -> read_one(Path, Name) end, SxNames);
|
|
{error, _} ->
|
|
[]
|
|
end.
|
|
|
|
%% Suffix check on the .sx extension. 46='.' 115='s' 120='x'.
|
|
ends_with_sx(<<46, 115, 120>>) -> true;
|
|
ends_with_sx(<<>>) -> false;
|
|
ends_with_sx(<<_, Rest/binary>>) -> ends_with_sx(Rest).
|
|
|
|
%% ── Internal ────────────────────────────────────────────────────
|
|
|
|
read_one(DirPath, Name) ->
|
|
Full = <<DirPath/binary, 47, Name/binary>>,
|
|
case file:read_file(Full) of
|
|
{ok, Bytes} -> {Name, Bytes};
|
|
{error, R} -> {Name, {error, R}}
|
|
end.
|
|
|
|
%% ── Step 4d: bundle CID compute + verify ────────────────────────
|
|
%%
|
|
%% The bundle CID is the canonical content-address of everything in
|
|
%% read_genesis/0's result. We delegate to the host `cid:to_string/1`
|
|
%% BIF (Step 1b substrate): it walks the term via `er-format-value`,
|
|
%% feeds the deterministic textual form into `cid-from-sx`, returns
|
|
%% a CIDv1 (raw codec, sha2-256 multihash) as a binary.
|
|
%%
|
|
%% Design §12.3: at startup the kernel computes this CID and
|
|
%% compares against a hardcoded value (here: a sibling `.cidhash`
|
|
%% file). A mismatch is a hard refuse-to-start.
|
|
|
|
build_genesis(ReadResult) ->
|
|
case ReadResult of
|
|
{ok, Sections} ->
|
|
Cid = cid:to_string({genesis_bundle, Sections}),
|
|
{ok, [{cid, Cid}, {sections, Sections}]};
|
|
Other ->
|
|
{error, {bad_read_result, Other}}
|
|
end.
|
|
|
|
verify_genesis(ReadResult, ExpectedCid) ->
|
|
case build_genesis(ReadResult) of
|
|
{ok, [{cid, Cid}, _]} ->
|
|
case Cid =:= ExpectedCid of
|
|
true -> ok;
|
|
false -> {error, {cid_mismatch, Cid, ExpectedCid}}
|
|
end;
|
|
Err -> Err
|
|
end.
|
|
|
|
%% Sibling-file CID storage. "/.cidhash" appended to BasePath as
|
|
%% an integer-segment binary (string-literal segments are broken).
|
|
|
|
%% "/.cidhash" — 47='/' 46='.' c i d h a s h
|
|
cidhash_path(BasePath) ->
|
|
<<BasePath/binary, 47, 46, 99, 105, 100, 104, 97, 115, 104>>.
|
|
|
|
write_cidhash(BasePath, Cid) ->
|
|
file:write_file(cidhash_path(BasePath), Cid).
|
|
|
|
read_cidhash(BasePath) ->
|
|
file:read_file(cidhash_path(BasePath)).
|
|
|
|
%% ── Step 4e: load_genesis → registry ────────────────────────────
|
|
%%
|
|
%% Walks the read_genesis result and registers each file as a
|
|
%% registry entry. The section atom is the registry kind directly
|
|
%% (both name spaces are identical — see Step 4c sections/0 and
|
|
%% Step 5a registry:kinds/0). The entry Name is the filename minus
|
|
%% the `.sx` suffix, kept as a binary; the entry value is the
|
|
%% file's raw bytes.
|
|
%%
|
|
%% Returns `{ok, RegistryState}` on success. Later steps (4f / the
|
|
%% SX-parser bridge) will replace the raw bytes with parsed forms;
|
|
%% the binary stand-in is enough to prove the bridge works.
|
|
|
|
load_genesis(ReadResult) ->
|
|
case ReadResult of
|
|
{ok, Sections} ->
|
|
{ok, load_sections(Sections, registry:new())};
|
|
Other ->
|
|
{error, {bad_read_result, Other}}
|
|
end.
|
|
|
|
load_sections([], State) -> State;
|
|
load_sections([{Kind, Entries} | Rest], State) ->
|
|
load_sections(Rest, load_entries(Kind, Entries, State)).
|
|
|
|
load_entries(_Kind, [], State) -> State;
|
|
load_entries(Kind, [{Name, Bytes} | Rest], State) ->
|
|
BaseName = strip_sx_suffix(Name),
|
|
{ok, NewState} = registry:register(Kind, BaseName, Bytes, State),
|
|
load_entries(Kind, Rest, NewState).
|
|
|
|
%% strip_sx_suffix(Binary) — drops the trailing ".sx" if present.
|
|
%% 46='.' 115='s' 120='x'.
|
|
strip_sx_suffix(B) when is_binary(B) ->
|
|
case ends_with_sx(B) of
|
|
false -> B;
|
|
true -> take_prefix(B, byte_size(B) - 3)
|
|
end.
|
|
|
|
take_prefix(_, 0) -> <<>>;
|
|
take_prefix(<<H, Rest/binary>>, N) when N > 0 ->
|
|
Tail = take_prefix(Rest, N - 1),
|
|
<<H, Tail/binary>>.
|