Skip to content

Commit

Permalink
Merge pull request #41 from permaweb/chore/rename-pam
Browse files Browse the repository at this point in the history
chore: rename PAM->Converge Protocol
  • Loading branch information
samcamwilliams authored Dec 9, 2024
2 parents 88c46b0 + bce0214 commit 15e8fe9
Show file tree
Hide file tree
Showing 19 changed files with 198 additions and 192 deletions.
26 changes: 13 additions & 13 deletions docs/permaweb-abstract-machine.md → docs/converge-protocol.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# The Permaweb Abstract Machine.
# The Converge Protocol.
## Status: DRAFT-1
## Authors: Sam Williams, Tom Wilson, Tyler Hall, James Pichota, Vince Juliano < {first}@arweave.org>

This document contains a rough specification, in `engineering note` form, for the Permaweb Abstract Machine (PAM). PAM is a method through which the permaweb can be interpreted as not only a flat, permanent ledger of data, but also as a mutable and reactive computation environment. Through this frame, the permaweb can be interpreted as a single, shared ['system image'](https://en.wikipedia.org/wiki/Single_system_image) that can be accessed and added to permissionlessly. Notably, the PAM itself intends to be a truly minimal computation model: It does not enforce any forms of consensus upon its execution directly, nor the use of any particular virtual machine. Even the requirements it imparts upon the host runtime environment are minimal. Instead, the PAM focuses on offering the simplest possible representation of _data_ and _computation_ upon that data, tailored for Arweave's distributed environment and [HTTP](https://datatracker.ietf.org/doc/html/rfc9114) access methods.
This document contains a rough specification, in `engineering note` form, for the Converge Protocol. Converge is a method through which the permaweb can be interpreted as not only a flat, permanent ledger of data, but also as a mutable and reactive computation environment. Through this frame, the permaweb can be interpreted as a single, shared ['system image'](https://en.wikipedia.org/wiki/Single_system_image) that can be accessed and added to permissionlessly. Notably, the Converge itself intends to be a truly minimal computation model: It does not enforce any forms of consensus upon its execution directly, nor the use of any particular virtual machine. Even the requirements it imparts upon the host runtime environment are minimal. Instead, the Converge focuses on offering the simplest possible representation of _data_ and _computation_ upon that data, tailored for Arweave's distributed environment and [HTTP](https://datatracker.ietf.org/doc/html/rfc9114) access methods.

[HyperBEAM](https://github.com/permaweb/HyperBEAM) is an implementation of the permaweb abstract machine, as well as [AO](https://ao.arweave.net), a framework ontop of the PAM that constructs an environment for _trustless_ -- not just _permissionless_ -- computation.
[HyperBEAM](https://github.com/permaweb/HyperBEAM) is an implementation of the converge protocol, as well as [AO](https://ao.arweave.net), a framework ontop of the Converge that constructs an environment for _trustless_ -- not just _permissionless_ -- computation.

## Context

Expand All @@ -16,13 +16,13 @@ In this specification we refer to a number of abstract properties of computation

`Trustlessness`: Users can participate in the network without needing to trust other parties are not acting maliciously.

Both of these properties are extremely powerful and can only be offered by decentralized computation machines. These properties can also, unfortunately, only presently be offered in degrees. For example, Bitcoin may offer a high level of `permissionlessness`, but it is not absolute: A user still requires that a majority of the mining power does not censor their transactions or blocks containing them. Similarly, while your recipient of a Bitcoin transfer has a high degree of `trustlessness`, at minimum the user must still trust the implementors of the cryptographic verification algorithms of their client in order to use the system. Rather than offering a single, standardized approach to the problem of offering permissionless and trustlessness computation, which would necessitate enforcing the same trade-offs upon all parties, PAM and AO instead focus on allowing users to make their own appropriate choices amongst a variety of options while still being able to interoperate together.
Both of these properties are extremely powerful and can only be offered by decentralized computation machines. These properties can also, unfortunately, only presently be offered in degrees. For example, Bitcoin may offer a high level of `permissionlessness`, but it is not absolute: A user still requires that a majority of the mining power does not censor their transactions or blocks containing them. Similarly, while your recipient of a Bitcoin transfer has a high degree of `trustlessness`, at minimum the user must still trust the implementors of the cryptographic verification algorithms of their client in order to use the system. Rather than offering a single, standardized approach to the problem of offering permissionless and trustlessness computation, which would necessitate enforcing the same trade-offs upon all parties, Converge and AO instead focus on allowing users to make their own appropriate choices amongst a variety of options while still being able to interoperate together.

## Machine Definition

Every item on the permaweb is described as a `Message`. Each `Message` is interpretable by PAM as a `map` of named functions, or as a concrete binary term. Each function in a message may be called through the creation of another message, providing a map of arguments to the execution.
Every item on the permaweb is described as a `Message`. Each `Message` is interpretable by Converge as a `map` of named functions, or as a concrete binary term. Each function in a message may be called through the creation of another message, providing a map of arguments to the execution.

Each `message` on the permaweb may optionally state a `Device` which should be used by PAM-compatible systems to interpret its contents. If no `Device` key is explicitly stated by the message, it must be infered as `Map`. The `Map` device should be implemented to simply return the binary or message at a given function name (a `key` in the map). Every `Device` must implement functions with the names `ID` and `Keys`. An `ID` is a function that can be used in order to refer to a message at a later time, while `Keys` should return a binary representation (with `Encoding` optionally specified as a parameter in the argument message) of each key in the message.
Each `message` on the permaweb may optionally state a `Device` which should be used by Converge-compatible systems to interpret its contents. If no `Device` key is explicitly stated by the message, it must be infered as `Map`. The `Map` device should be implemented to simply return the binary or message at a given function name (a `key` in the map). Every `Device` must implement functions with the names `ID` and `Keys`. An `ID` is a function that can be used in order to refer to a message at a later time, while `Keys` should return a binary representation (with `Encoding` optionally specified as a parameter in the argument message) of each key in the message.

Concretely, these relations can be expressed as follows:
```
Expand All @@ -31,21 +31,21 @@ Types:
Message :: Map< Name :: Binary, (Message?) => Message > | Binary
Functions:
Message(Key, ParameterMessage?) => Message :: PAM.apply(Message, Key, Parameters) => Message
PAM.apply(Message[Device], Message, RuntimeEnv? :: Message) => Message
Message(Key, ParameterMessage?) => Message :: Converge.apply(Message, Key, Parameters) => Message
Converge.apply(Message[Device], Message, RuntimeEnv? :: Message) => Message
Invariants:
∀ message ∈ Permaweb, ∃ <<"ID">> ∈ Message
∀ message ∈ Permaweb, ∃ <<"Keys">> ∈ Message
∀ message ∈ Permaweb, ∃ (<<"Device">> ∈ Message ∨ PAM.apply(message, <<"Device">>) => <<"Map">>)
∀ message ∈ Permaweb, ∃ (<<"Device">> ∈ Message ∨ Converge.apply(message, <<"Device">>) => <<"Map">>)
```

The PAM intends to be a computer native to the technologies of the internet. More specifically, we have focused its representation on compatibility with the HTTP family of protocols (`HTTP/{1.1,2,3}`). As such, every message in this system can be refered to via `Path`s. A path starts from a given message in the system (whether written to the permaweb yet or not), and applies a series of additional messages on top of it. Each resulting message itself must have an `ID` resolvable via its device -- subsequently enabling additional paths to be described atop the intermediate message.
The Converge intends to be a computer native to the technologies of the internet. More specifically, we have focused its representation on compatibility with the HTTP family of protocols (`HTTP/{1.1,2,3}`). As such, every message in this system can be refered to via `Path`s. A path starts from a given message in the system (whether written to the permaweb yet or not), and applies a series of additional messages on top of it. Each resulting message itself must have an `ID` resolvable via its device -- subsequently enabling additional paths to be described atop the intermediate message.

For example:
```
/StartingID/Input1ID/Input2ID/Input3ID =>
/{PAM.apply(StartingMsg, Input1)}/Input2ID/Input3ID =>
/{Converge.apply(StartingMsg, Input1)}/Input2ID/Input3ID =>
/OutputID1/Input2ID/Input3ID =>
...
/Output3ID
Expand All @@ -54,7 +54,7 @@ For example:

## Hyperbeam Devices

The hyperbeam environment implements PAM through the use of device modules. Each module in the base deployment is namespaced `dev_*`, for example `dev_scheduler`. Devices in hyperbeam can communicate the basic set of keys that they offer using the function exports. Each of these functions is interpreted as yielding the value for a key on a message containing that device, with the key's name being defined by its function name. Each of these functions may take one to three arguments of the following: `function(StateMessage, InputMessage, Environment)`. It must yield a result of the form `{Status, NewMessage}`, where `Status` is typically (but not necessarily) either `ok` or `error`. If a device does not offer one of the required functions (`ID` or `device`), the environment falls back to the underlying `message` device implementations. `tolowercase` is applied to all function names before execution, and `-` characters are replaced with `_` to allow for more readable device implementations.
The hyperbeam environment implements Converge through the use of device modules. Each module in the base deployment is namespaced `dev_*`, for example `dev_scheduler`. Devices in hyperbeam can communicate the basic set of keys that they offer using the function exports. Each of these functions is interpreted as yielding the value for a key on a message containing that device, with the key's name being defined by its function name. Each of these functions may take one to three arguments of the following: `function(StateMessage, InputMessage, Environment)`. It must yield a result of the form `{Status, NewMessage}`, where `Status` is typically (but not necessarily) either `ok` or `error`. If a device does not offer one of the required functions (`ID` or `device`), the environment falls back to the underlying `message` device implementations. `tolowercase` is applied to all function names before execution, and `-` characters are replaced with `_` to allow for more readable device implementations.

The special case `info/{0,1,2}` function may be implemented by the device, signalling environment requirements to hyperbeam. The `info` function should can optionally take the `message` in question and the environment variables as arguments. It should return a map of environmental information of the following form:

Expand All @@ -80,6 +80,6 @@ The `uses` info key may be optionally utilized to signal to the environment whic

## The Stack Device

In order to allow messages to have more flexibility in their execution, hyperbeam offers an implementation of a PAM `stack`-style device, which combines a series of devices on a message into a single 'stack' of executable transformations. This device allows many complex forms of processors to be built inside the PAM environment -- for example, AO processes -- whiile transferring the architecture's modularity and flexibility to them.
In order to allow messages to have more flexibility in their execution, hyperbeam offers an implementation of a Converge `stack`-style device, which combines a series of devices on a message into a single 'stack' of executable transformations. This device allows many complex forms of processors to be built inside the Converge environment -- for example, AO processes -- whiile transferring the architecture's modularity and flexibility to them.

When added as the highest `Device` tag on a message, the stack device scans the remainder of the message's tags looking for (and subsequently loading) any other messages it finds. When a user then calls an execution on top of a message containing a device, the device passes through each of the elements of the stack in turn, 'folding' over it.
2 changes: 1 addition & 1 deletion docs/hacking-on-hyperbeam.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ to analyze would HB's path management `dev_message` modules are doing while you
run your tests, just execute:

```
HB_PRINT=hb_path,hb_pam rebar3 eunit --module=your_mod
HB_PRINT=hb_path,hb_converge rebar3 eunit --module=your_mod
```

The HB printing system is reasonably intelligent. It has a custom
Expand Down
32 changes: 16 additions & 16 deletions src/dev_message.erl
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ set(Message1, NewValuesMsg, Opts) ->
fun(Key) ->
not lists:member(Key, ?DEVICE_KEYS)
end,
hb_pam:keys(NewValuesMsg, Opts)
hb_converge:keys(NewValuesMsg, Opts)
),
% Find keys in the message that are already set (case-insensitive), and
% note them for removal.
NormalizedKeysToSet = lists:map(fun hb_pam:to_key/1, KeysToSet),
NormalizedKeysToSet = lists:map(fun hb_converge:to_key/1, KeysToSet),
ConflictingKeys =
lists:filter(
fun(Key) ->
lists:member(hb_pam:to_key(Key), NormalizedKeysToSet)
lists:member(hb_converge:to_key(Key), NormalizedKeysToSet)
end,
maps:keys(Message1)
),
Expand Down Expand Up @@ -100,14 +100,14 @@ set(Message1, NewValuesMsg, Opts) ->

%% @doc Remove a key or keys from a message.
remove(Message1, #{ item := Key }) ->
remove(Message1, #{ items => [hb_pam:to_key(Key)] });
remove(Message1, #{ items => [hb_converge:to_key(Key)] });
remove(Message1, #{ items := Keys }) ->
NormalizedKeysToRemove = lists:map(fun hb_pam:to_key/1, Keys),
NormalizedKeysToRemove = lists:map(fun hb_converge:to_key/1, Keys),
{
ok,
maps:filtermap(
fun(KeyN, Val) ->
NormalizedKeyN = hb_pam:to_key(KeyN),
NormalizedKeyN = hb_converge:to_key(KeyN),
case lists:member(NormalizedKeyN, NormalizedKeysToRemove) of
true -> false;
false -> {true, Val}
Expand Down Expand Up @@ -149,10 +149,10 @@ case_insensitive_get(Key, Msg) ->
case_insensitive_get(Key, Msg, Keys) when byte_size(Key) > 43 ->
do_case_insensitive_get(Key, Msg, Keys);
case_insensitive_get(Key, Msg, Keys) ->
do_case_insensitive_get(hb_pam:to_key(Key), Msg, Keys).
do_case_insensitive_get(hb_converge:to_key(Key), Msg, Keys).
do_case_insensitive_get(_Key, _Msg, []) -> {error, not_found};
do_case_insensitive_get(Key, Msg, [CurrKey | Keys]) ->
case hb_pam:to_key(CurrKey) of
case hb_converge:to_key(CurrKey) of
Key -> {ok, maps:get(CurrKey, Msg)};
_ -> do_case_insensitive_get(Key, Msg, Keys)
end.
Expand All @@ -174,7 +174,7 @@ is_private_mod_test() ->
%%% Device functionality tests:

keys_from_device_test() ->
?assertEqual({ok, [a]}, hb_pam:resolve(#{a => 1}, keys)).
?assertEqual({ok, [a]}, hb_converge:resolve(#{a => 1}, keys)).

case_insensitive_get_test() ->
?assertEqual({ok, 1}, case_insensitive_get(a, #{a => 1})),
Expand All @@ -188,31 +188,31 @@ case_insensitive_get_test() ->
private_keys_are_filtered_test() ->
?assertEqual(
{ok, [a]},
hb_pam:resolve(#{a => 1, private => 2}, keys)
hb_converge:resolve(#{a => 1, private => 2}, keys)
),
?assertEqual(
{ok, [a]},
hb_pam:resolve(#{a => 1, "priv_foo" => 4}, keys)
hb_converge:resolve(#{a => 1, "priv_foo" => 4}, keys)
).

cannot_get_private_keys_test() ->
?assertEqual(
{error, not_found},
hb_pam:resolve(#{ a => 1, private_key => 2 }, private_key)
hb_converge:resolve(#{ a => 1, private_key => 2 }, private_key)
).

key_from_device_test() ->
?assertEqual({ok, 1}, hb_pam:resolve(#{a => 1}, a)).
?assertEqual({ok, 1}, hb_converge:resolve(#{a => 1}, a)).

remove_test() ->
Msg = #{ <<"Key1">> => <<"Value1">>, <<"Key2">> => <<"Value2">> },
?assertMatch({ok, #{ <<"Key2">> := <<"Value2">> }},
hb_pam:resolve(Msg, #{ path => remove, item => <<"Key1">> })),
hb_converge:resolve(Msg, #{ path => remove, item => <<"Key1">> })),
?assertMatch({ok, #{}},
hb_pam:resolve(Msg, #{ path => remove, items => [<<"Key1">>, <<"Key2">>] })).
hb_converge:resolve(Msg, #{ path => remove, items => [<<"Key1">>, <<"Key2">>] })).

set_conflicting_keys_test() ->
Msg1 = #{ <<"Dangerous">> => <<"Value1">> },
Msg2 = #{ path => set, dangerous => <<"Value2">> },
?assertMatch({ok, #{ dangerous := <<"Value2">> }},
hb_pam:resolve(Msg1, Msg2)).
hb_converge:resolve(Msg1, Msg2)).
2 changes: 1 addition & 1 deletion src/dev_meta.erl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ execute_path([], Msg, _) -> {ok, Msg};
execute_path([FuncName|Path], Msg, S) ->
?event({meta_executing_on_path, {function, FuncName}, {path, Path}}),
Func = parse_path_to_func(FuncName),
execute_path(Path, hb_pam:resolve(Msg, Func, [Msg], S), S).
execute_path(Path, hb_converge:resolve(Msg, Func, [Msg], S), S).

parse_path_to_func(BinName) when is_binary(BinName) ->
binary_to_existing_atom(string:lowercase(BinName), utf8);
Expand Down
2 changes: 1 addition & 1 deletion src/dev_mu.erl
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ push_messages(upload, Messages, Opts) ->
}
),
Stack = dev_stack:create(?PUSH_DEV_STACK),
{ok, Results} = hb_pam:resolve(
{ok, Results} = hb_converge:resolve(
{dev_stack, execute},
push,
[
Expand Down
14 changes: 7 additions & 7 deletions src/dev_process.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
-export([info/2, scheduler/3, compute/3]).

%%% @moduledoc This module contains notes for the implmenetation of AO processes
%%% in PAM.
%%% in Converge.
%%%
%%% The ideal API for an AO process would be:
%%%
Expand Down Expand Up @@ -42,7 +42,7 @@ info(_Msg1, _Opts) ->
%% the scheduler.
scheduler(Msg1, Msg2, Opts) ->
{ok, Scheduler} = dev_message:get(scheduler, Msg1, Opts),
hb_pam:resolve(
hb_converge:resolve(
#{
device => Scheduler,
process => Msg1
Expand All @@ -59,19 +59,19 @@ compute(Msg1, Msg2, Opts) ->
{ok, Result} -> {ok, Result};
not_found ->
Initialized =
case hb_pam:get(initialized, Msg1, Opts) of
case hb_converge:get(initialized, Msg1, Opts) of
{ok, _Initialized} ->
Msg1;
not_found ->
{ok, Init} = hb_pam:resolve(
hb_pam:set(Msg1, #{ device => stack }),
{ok, Init} = hb_converge:resolve(
hb_converge:set(Msg1, #{ device => stack }),
#{ path => <<"Init">> },
Opts
),
Init
end,
hb_pam:resolve(
hb_pam:set(Initialized, #{ device => <<"Stack/1.0">> }),
hb_converge:resolve(
hb_converge:set(Initialized, #{ device => <<"Stack/1.0">> }),
Msg2,
Opts
)
Expand Down
Loading

0 comments on commit 15e8fe9

Please sign in to comment.