dev_poda.erl - Proof of Delegated Authority (PoDA) Consensus
Overview
Purpose: Decentralized proof of authority consensus for AO processes
Module: dev_poda
Pattern: Authority Quorum → Message Validation → Commitment Aggregation
Device Tag: PODA
This device implements a simple exemplar decentralized proof of authority consensus algorithm for AO processes. It validates incoming messages through a quorum of trusted authorities before execution and aggregates commitments from authorities when producing results.
Architecture
Two-Flow Design
Execution Flow:- Initialization - Extract authority list and quorum
- Pre-execution validation - Verify authority commitments
- Result commitment - Add authority signatures to outbound messages
Dependencies
- HyperBEAM:
hb_ao,hb_maps,hb_util,hb_cache,hb_router,hb_client - Arweave:
ar_bundles,ar_wallet - Includes:
include/hb.hrl
Configuration
Process Definition Tags
Process#tx.tags = [
{<<"device">>, <<"PODA">>},
{<<"authority">>, Address1},
{<<"authority">>, Address2},
{<<"authority">>, Address3},
{<<"quorum">>, <<"2">>} % Binary integer
]authority- Trusted validator addresses (multiple)quorum- Minimum number of authority signatures required
Public Functions Overview
%% Execution Flow
-spec init(State, Params) -> {ok, State, Opts}.
-spec execute(Message, State, Opts) -> {ok, NewState} | {skip, ErrorState}.
%% Commitment Flow
-spec push(Item, State, Opts) -> {ok, StateWithCommitments}.
%% Validation
-spec is_user_signed(Message) -> boolean().Public Functions
1. init/2
-spec init(State, Params) -> {ok, State, Opts}
when
State :: map(),
Params :: [{Key, Value}],
Opts :: #{
authorities => [Address],
quorum => integer()
}.Description: Initialize PoDA device by extracting authority addresses and quorum from process tags.
Parameters Extraction:- Filters
authoritytags into list - Extracts
quorumtag as binary integer - Returns configuration map
-module(dev_poda_init_test).
-include_lib("eunit/include/eunit.hrl").
init_basic_test() ->
Params = [
{<<"authority">>, <<"addr1">>},
{<<"authority">>, <<"addr2">>},
{<<"authority">>, <<"addr3">>},
{<<"quorum">>, <<"2">>}
],
{ok, State, Opts} = dev_poda:init(#{}, Params),
?assertEqual([<<"addr1">>, <<"addr2">>, <<"addr3">>], maps:get(authorities, Opts)),
?assertEqual(2, maps:get(quorum, Opts)).
init_minimum_quorum_test() ->
Params = [
{<<"authority">>, <<"addr1">>},
{<<"quorum">>, <<"1">>}
],
{ok, _, Opts} = dev_poda:init(#{}, Params),
?assertEqual(1, maps:get(quorum, Opts)).2. execute/3
-spec execute(Outer, State, Opts) -> {ok, NewState} | {skip, ErrorState}
when
Outer :: #tx{},
State :: map(),
NewState :: map(),
ErrorState :: map().Description: Pre-execution validation of incoming messages. Verifies that messages have sufficient authority commitments before allowing execution.
Execution Passes:- Pass 1: Validation and commitment extraction
- Pass 3: Post-execution (no-op)
- Other passes: Pass-through
- Check if message is user-signed (skip validation)
- Extract commitments from message
- Verify all commitment signatures
- Verify commitments from trusted authorities
- Check quorum threshold
- Add commitments to VFS
- Update arg_prefix with unwrapped message
-module(dev_poda_execute_test).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").
execute_valid_message_test() ->
% execute/3 requires complex commitment bundle setup
% Verify module exports execute/3
code:ensure_loaded(dev_poda),
?assert(erlang:function_exported(dev_poda, execute, 3)).
execute_insufficient_quorum_test() ->
% execute/3 with insufficient quorum - verify export
Exports = dev_poda:module_info(exports),
?assert(lists:member({execute, 3}, Exports)).
execute_user_signed_bypass_test() ->
% execute/3 with user-signed message - verify export
code:ensure_loaded(dev_poda),
?assert(erlang:function_exported(dev_poda, execute, 3)).3. push/3
-spec push(Item, State, Opts) -> {ok, StateWithCommitments}
when
Item :: term(),
State :: map(),
Opts :: map(),
StateWithCommitments :: map().Description: Add PoDA commitments to outbound messages. Aggregates signatures from multiple authorities and wraps results in commitment bundles.
Flow:- Check if target process uses PoDA
- Gather commitments from peer authorities
- Add local commitment
- Bundle all commitments with message
- Sign complete commitment bundle
Parallel Execution:
Uses pfiltermap/2 for concurrent commitment gathering from multiple authorities.
-module(dev_poda_push_test).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").
push_adds_commitments_test() ->
% push/3 requires complex #tx{} record setup
% Verify module exports push/3
code:ensure_loaded(dev_poda),
?assert(erlang:function_exported(dev_poda, push, 3)).4. is_user_signed/1
-spec is_user_signed(Message) -> boolean()
when
Message :: #tx{}.Description: Determine if a message is user-signed (not from a process). Currently uses presence of from-process tag as indicator.
Note: Marked with ?no_prod(use_real_commitment_detection) for future enhancement.
-module(dev_poda_is_user_signed_test).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").
is_user_signed_true_test() ->
% is_user_signed/1 requires #tx{} records
% Verify module exports is_user_signed/1
code:ensure_loaded(dev_poda),
?assert(erlang:function_exported(dev_poda, is_user_signed, 1)).
is_user_signed_false_test() ->
% Verify exports
Exports = dev_poda:module_info(exports),
?assert(lists:member({is_user_signed, 1}, Exports)).Message Structure
Incoming Message (Requires Validation)
#tx{
data => #{
<<"body">> => #tx{
data => #{
<<"commitments">> => #tx{
data => #{
<<"1">> => Commitment1, % Authority signature
<<"2">> => Commitment2, % Authority signature
...
}
},
<<"body">> => ActualMessage % The message to execute
}
}
}
}Commitment Structure
#tx{
tags => [{<<"commitment-for">>, MessageID}],
data => <<>>,
signature => AuthoritySignature,
owner => AuthorityPublicKey
}Outgoing Message (With Commitments)
#tx{
data => #{
<<"commitments">> => #tx{
data => #{
<<"1">> => LocalCommitment,
<<"2">> => PeerCommitment1,
<<"3">> => PeerCommitment2,
...
}
},
<<"body">> => ResultMessage
}
}Validation Stages
Stage 1: Structure Check
Verify message has required commitments and body fields.
Stage 2: Signature Verification
Verify all commitment signatures are valid.
Stage 3: Quorum Check
- Filter commitments from trusted authorities
- Verify commitment relevance to message
- Count valid commitments
- Check against quorum threshold
Commitment Validation
A commitment is valid if all of these are true:
- Valid Signer: Signer is in authority list
- Valid Signature: Cryptographic signature verifies
- Relevant Message: One of:
- Commitment's unsigned ID matches message unsigned ID
- Has
commitment-fortag matching message ID - Message ID found in commitment bundle
Authority Coordination
Finding Authorities
% Router lookup for authority compute nodes
{ok, ComputeNode} = hb_router:find(
compute,
ProcessID,
AuthorityAddress,
Opts
)Requesting Commitments
% Client request to authority
Res = hb_client:resolve(
ComputeNode,
ProcessID,
AssignmentID,
#{ <<"commit-to">> => MessageID }
)Parallel Gathering
% Concurrent commitment collection
Commitments = pfiltermap(
fun(AuthorityAddress) ->
% Request commitment from authority
% Return {true, Commitment} or false
end,
AuthorityList
)Common Patterns
%% Define PoDA process
Process = ar_bundles:sign_item(
#tx{
tags = [
{<<"device">>, <<"PODA">>},
{<<"authority">>, Authority1Address},
{<<"authority">>, Authority2Address},
{<<"authority">>, Authority3Address},
{<<"quorum">>, <<"2">>}
],
data = <<"Process state">>
},
Wallet
).
%% Create message with commitments
InnerMsg = ar_bundles:sign_item(
#tx{data = <<"Execute this">>},
UserWallet
),
% Get commitments from authorities
Commitment1 = get_commitment_from_authority(Authority1, InnerMsg),
Commitment2 = get_commitment_from_authority(Authority2, InnerMsg),
% Bundle commitments
CommitmentBundle = ar_bundles:sign_item(
ar_bundles:normalize(#tx{
data => #{
<<"commitments">> => ar_bundles:sign_item(
#tx{data = #{
<<"1">> => Commitment1,
<<"2">> => Commitment2
}},
Wallet
),
<<"body">> => InnerMsg
}
}),
Wallet
).
%% Send for execution
Result = execute_poda_message(Process, CommitmentBundle).
%% Check commitment in VFS after execution
CommitmentPath = <<"/commitments/", (hb_util:encode(AuthorityID))/binary>>,
Commitment = hb_ao:get(CommitmentPath, State, #{}).Integration with Process Device
%% Process with PoDA validation
Process = #{
<<"device">> => <<"process@1.0">>,
<<"execution-device">> => <<"stack@1.0">>,
<<"execution-stack">> => [
<<"poda@1.0">>, % Validate first
<<"lua@5.3a">>, % Then execute
<<"patch@1.0">>
],
% PoDA configuration in tags
tags => [
{<<"device">>, <<"PODA">>},
{<<"authority">>, Addr1},
{<<"authority">>, Addr2},
{<<"quorum">>, <<"2">>}
]
}.Error Handling
Validation Errors
% Insufficient commitments
{skip, #{
results => #{
<<"/outbox">> => #tx{
data => <<"Not enough validations">>,
tags => [{<<"error">>, <<"PoDA">>}]
}
}
}}
% Invalid commitments
{skip, #{
results => #{
<<"/outbox">> => #tx{
data => <<"Invalid commitments">>,
tags => [{<<"error">>, <<"PoDA">>}]
}
}
}}
% Missing required fields
{skip, #{
results => #{
<<"/outbox">> => #tx{
data => <<"Required PoDA messages missing">>,
tags => [{<<"error">>, <<"PoDA">>}]
}
}
}}VFS Integration
Validated commitments are stored in the Virtual File System:
% Path format
<<"/commitments/", (EncodedAuthorityID)/binary>>
% Example
<<"/commitments/base64url_encoded_authority_address">>
% Access
Commitment = hb_ao:get(
<<"/commitments/authority1">>,
State,
#{}
).Performance Considerations
Parallel Commitment Gathering
- Uses
pfiltermap/2for concurrent requests - Spawns separate process for each authority
- Handles crashes gracefully
- Waits for all responses before proceeding
Message Unwrapping
- Two-layer unwrapping:
/Message/Message - Updates
arg_prefixwith unwrapped message - Preserves original structure in VFS
Security Properties
- Quorum Requirement: Configurable M-of-N threshold
- Authority Verification: Only trusted signers accepted
- Signature Validation: Cryptographic verification required
- Message Relevance: Commitments must reference specific message
- User Bypass: User-signed messages skip validation
- Error Isolation: Failed validation returns error, doesn't crash
Use Cases
-
Multi-Authority Validation
- Require multiple nodes to agree on computation
- Prevent single-node manipulation
-
Decentralized Consensus
- Distributed trust among authorities
- No single point of control
-
Result Attestation
- Multiple parties sign computation results
- Verifiable execution guarantees
-
Federated Networks
- Cross-organization validation
- Shared authority governance
References
- Arweave Bundles -
ar_bundles.erl - Process Device -
dev_process.erl - Router -
hb_router.erl - Client -
hb_client.erl - Cache -
hb_cache.erl
Notes
- Exemplar Implementation: Designed as simple reference, not production-ready
- User Bypass: User-signed messages skip PoDA validation
- Process Messages: From-process messages require commitments
- Quorum Threshold: Must be met for execution to proceed
- Authority List: Extracted from process tags at init
- Parallel Gathering: Concurrent commitment requests for performance
- VFS Storage: Commitments stored for later verification
- Error Returns: Use
skipto return error without execution - Outbox/Spawn Only: Currently only commits to these result paths
- Commitment Format: Uses ANS-104 data items with special tags
- Future Enhancement: Real commitment detection mechanism needed
- Router Integration: Requires router to find authority nodes
- Message Wrapping: Double-wrapped structure for validation
- Signature Relevance: Multiple ways to verify commitment-message relationship
- Production Note: Marked
?no_prodfor certain simplifications