Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions e2e/client/node/scripts/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ const config = Value.Parse(OrchestratorConfig, {
launchEvm("@e2e/evm-contracts"),
yaci_enabled ? launchCardano("@e2e/cardano-contracts") : {},
launchMidnight("@e2e/midnight-contracts"),
// Uncomment to enable Avail Process
// launchAvail("@e2e/avail-contracts"),
launchAvail("@e2e/avail-contracts"),
{
stopProcessAtPort: [10590],
processes: [
Expand Down
9 changes: 9 additions & 0 deletions e2e/client/node/src/state-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ stm.addStateTransition(
},
);

stm.addStateTransition(
"avail-app-state",
function* (data) {
const { payload } = data.parsedInput;
console.log("📦 Avail App state has message:", payload.message);
return;
},
);

stm.addStateTransition(
"throw_error",
function* (data) {
Expand Down
3 changes: 2 additions & 1 deletion e2e/shared/contracts/avail/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
avail_path
avail_path
avail_app.json
2 changes: 1 addition & 1 deletion e2e/shared/contracts/avail/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ http_server_port=7007
port=37000

# WebSocket endpoint of a full node.
full_node_ws = ["ws://127.0.0.1:9944"]
full_node_ws = ["ws://127.0.0.1:9955"]

# Application ID. If not set or set to 0, application client is not started.
app_id=0
Expand Down
17 changes: 12 additions & 5 deletions e2e/shared/contracts/avail/deno.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
{
"name": "@e2e/avail-contracts",
"version": "0.3.0",
"exports": {},
"exports": {
".": "./read-app.ts"
},
"tasks": {
"avail-node:start": "deno run -A --unstable-detect-cjs @paima/npm-avail-node --dev",
"avail-node:wait": "wait-on tcp:9944",
"avail-light-client:start": "deno run -A --unstable-detect-cjs @paima/npm-avail-light-client --network local --config ./config.yml",
"avail-node:start": "deno run -A --unstable-detect-cjs npm:@paimaexample/npm-avail-node --dev --rpc-port 9955 --no-telemetry",
"avail-node:wait": "wait-on tcp:9955",
"avail-light-client:deploy": "deno run -A --unstable-detect-cjs ./deploy.ts",
"avail-light-client:start": "deno run -A --unstable-detect-cjs npm:@paimaexample/npm-avail-light-client --config ./config.yml --app-id $AVAIL_APP_ID",
"avail-light-client:wait": "wait-on tcp:7007"
},
"imports": {}
"imports": {
"avail-js-sdk": "npm:avail-js-sdk@^0.4.2",
"@paimaexample/npm-avail-node": "npm:@paimaexample/npm-avail-node@0.3.30",
"@paimaexample/npm-avail-light-client": "npm:@paimaexample/npm-avail-light-client@0.3.30"
}
}
66 changes: 66 additions & 0 deletions e2e/shared/contracts/avail/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Account, Pallets, SDK } from "avail-js-sdk";

const sdk = await SDK.New("ws://localhost:9955/ws");
const seed: string = Deno.env.get("SEED") ?? "//Alice";
if (!seed) {
throw new Error("SEED environment variable is not set");
}
const account = Account.new(seed);
const genesisHash = await sdk.client.api.rpc.chain.getBlockHash(0);
console.log("Account Address: ", account.address);
// Use a fixed key string
const ApplicationKey = "app_key_" + Date.now();

export async function createApplicationKey() {
// Create application key transaction
const tx = sdk.tx.dataAvailability.createApplicationKey(ApplicationKey);
console.log("Submitting transaction to create application key...");

// Execute and wait for inclusion
const res = await tx.executeWaitForInclusion(account, {});

// Check if transaction was successful
const isOk = res.isSuccessful();
if (isOk === undefined) {
throw new Error("Cannot check if transaction was successful");
} else if (!isOk) {
console.log("Transaction failed", res);
throw new Error("Transaction failed");
}

// Extract event data
if (res.events === undefined) throw new Error("No events found");

const event = res.events.findFirst(
Pallets.DataAvailabilityEvents.ApplicationKeyCreated,
);
if (event === undefined) {
throw new Error("ApplicationKeyCreated event not found");
}

const appId = event.id;
console.log(`Application created successfully:`);
console.log(`Owner: ${event.owner}`);
console.log(`Key: ${event.keyToString()}`);
console.log(`App Id: ${appId}`);
console.log(`Transaction Hash: ${res.txHash}`);
return { appId, txHash: res.txHash };
}

const { appId, txHash } = await createApplicationKey();
console.log("Transaction Hash: ", txHash.toString());
const data = JSON.stringify({ appId, txHash, ApplicationKey, genesisHash });
const fileName = Deno.cwd() + "/avail_app.json";
console.log("Writing to file: ", fileName);
await Deno.writeTextFile(fileName, data);

const child = new Deno.Command("deno", {
args: ["task", "-f", "@e2e/avail-contracts", "avail-light-client:start"],
env: {
AVAIL_APP_ID: appId.toString(),
},
}).spawn();

console.log("Light Client Started");

await child.status;
28 changes: 28 additions & 0 deletions e2e/shared/contracts/avail/read-app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export type AvailApplicationInfo = {
appId: number;
txHash: { // The txHash of the apps creation transaction
value: string;
};
ApplicationKey: string;
genesisHash: string;
};

let cachedAppInfo: AvailApplicationInfo | undefined;
export function readAvailApplication(): AvailApplicationInfo {
if (cachedAppInfo) return cachedAppInfo;
try {
// Get the directory of the current module file using Deno's URL API
const dir = new URL(".", import.meta.url);
// Construct the full path to avail_app.json
const appInfoPath = new URL("avail_app.json", dir);
const appInfoJson = Deno.readTextFileSync(appInfoPath);
const appInfo = JSON.parse(appInfoJson) as AvailApplicationInfo;
cachedAppInfo = appInfo;
return appInfo;
} catch (err) {
if (err instanceof Deno.errors.NotFound) {
throw new Error("avail_app.json not found in the current directory");
}
throw new Error(`Failed to read avail_app.json: ${String(err)}`);
}
}
32 changes: 32 additions & 0 deletions e2e/shared/data-types/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { readMidnightContract } from "@e2e/midnight-contracts";
import { contractAddressesEvmMain } from "@e2e/evm-contracts";
import { readAvailApplication } from "@e2e/avail-contracts";
import { getConnection } from "@paima/db";
import {
ConfigBuilder,
Expand Down Expand Up @@ -89,6 +90,14 @@ export const localhostConfig = new ConfigBuilder()
"0x0000000000000000000000000000000000000000000000000000000000000001",
networkId: 0,
nodeUrl: "http://127.0.0.1:9944",
})
.addNetwork({
name: "avail",
type: ConfigNetworkType.AVAIL,
genesisSeed: "//Alice",
nodeUrl: "ws://127.0.0.1:9955/ws",
genesisHash: readAvailApplication().genesisHash,
caip2: `avail:local`,
});

if (yaci_enabled) {
Expand Down Expand Up @@ -175,6 +184,17 @@ export const localhostConfig = new ConfigBuilder()
indexer: "http://127.0.0.1:8088/api/v1/graphql",
indexerWs: "ws://127.0.0.1:8088/api/v1/graphql/ws",
}),
)
.addParallel(
(networks) => networks.avail,
(network, deployments) => ({
name: "parallelAvail",
type: ConfigSyncProtocolType.AVAIL_PARALLEL,
rpc: network.nodeUrl,
lightClient: 'http://127.0.0.1:7007',
startBlockHeight: 1,
pollingInterval: 20_000,
}),
);

if (yaci_enabled) {
Expand Down Expand Up @@ -279,5 +299,17 @@ export const localhostConfig = new ConfigBuilder()
scheduledPrefix: "transfer-erc20-2",
}),
)
.addPrimitive(
(syncProtocols) => syncProtocols.parallelAvail,
(network, deployments, syncProtocol) => ({
name: "AvailContractState",
type: ConfigPrimitiveType.AvailPaimaL2,
startBlockHeight: 1,
appId: readAvailApplication().appId,
contractAddress: readAvailApplication().ApplicationKey,
genesisHash: readAvailApplication().genesisHash,
scheduledPrefix: "avail-app-state",
}),
)
)
.build();
10 changes: 9 additions & 1 deletion e2e/shared/data-types/src/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,18 @@ export const grammar = {
),
],
],
"avail-app-state": [
[
"payload",
Type.Object({
message: Type.String(),
}),
],
],
// Auto-generate other primitives, but exclude midnight (we define it explicitly above)
...Object.fromEntries(
Object.entries(mapPrimitivesToGrammar(localhostConfig.primitives))
.filter(([key]) => key !== "midnightContractState"),
.filter(([key]) => key !== "midnightContractState" && key !== "avail-app-state"),
),
} as const satisfies GrammarDefinition;

Expand Down
48 changes: 22 additions & 26 deletions packages/binaries/avail-light-client/config.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
# Default configuration for avail-light-client
# For a full list of options, see the official Avail documentation.
# Avail Light Client config (no --network needed)

log_level="info"
http_server_host="127.0.0.1"
http_server_port=7007
# Logging
log_level = "info"

# Secret key for libp2p keypair. Can be either set to 'seed' or to 'key'.
# If set to seed, keypair will be generated from that seed.
# If 'secret_key' is not set, a random seed will be used.
# secret_key={ seed: "avail" }
# API server
http_server_host = "127.0.0.1"
http_server_port = 7007

# P2P TCP listener port (default: 37000).
port=37000

# WebSocket endpoint of a full node.
# RPC to full node (custom 9955)
full_node_ws = ["ws://127.0.0.1:9944"]

# Application ID. If not set or set to 0, application client is not started.
app_id=0

# Confidence threshold (default: 92.0).
confidence=92.0

# File system path where RocksDB used by the light client stores its data.
# This path is relative to the location of this config file.
avail_path="avail_path"

# Vector of Light Client bootstrap nodes.
# This is for a local setup. Replace with public bootstraps for testnet/mainnet.
bootstraps=["/ip4/127.0.0.1/tcp/39000/p2p/12D3KooWMm1c4pzeLPGkkCJMAgFbsfQ8xmVDusg272icWsaNHWzN"]
# App / LC behavior
app_id = 0
confidence = 92.0
avail_path = "avail_path"
network_mode = "both" # both | p2p-only | rpc-only

# LibP2P
port = 37000
webrtc_port = 37001
# secret_key = { seed = "avail" } # optional deterministic key
bootstraps = ["/ip4/127.0.0.1/tcp/39000/p2p/12D3KooWMm1c4pzeLPGkkCJMAgFbsfQ8xmVDusg272icWsaNHWzN"]

# Telemetry / network identity (equivalent to local --network defaults)
ot_collector_endpoint = "http://127.0.0.1:4317"
genesis_hash = "DEV"
12 changes: 6 additions & 6 deletions packages/build-tools/orchestrator/scripts/launch-avail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { ComponentNames } from "@paima/log";
// packageName: the name of the package that implements the tasks.
//
export const launchAvail = (packageName: string) => ({
stopProcessAtPort: [9944, 7007],
stopProcessAtPort: [9955, 7007],
processes: [
{
name: ComponentNames.AVAIL_NODE,
Expand All @@ -27,21 +27,21 @@ export const launchAvail = (packageName: string) => ({
logs: "none",
type: "system-dependency",
},
{
name: ComponentNames.AVAIL_NODE_WAIT,
args: ["task", "-f", packageName, "avail-node:wait"],
},
{
name: ComponentNames.AVAIL_CLIENT,
args: [
"task",
"-f",
packageName,
"avail-light-client:start",
"avail-light-client:deploy",
],
waitToExit: false,
type: "system-dependency",
},
{
name: ComponentNames.AVAIL_NODE_WAIT,
args: ["task", "-f", packageName, "avail-node:wait"],
},
{
name: ComponentNames.AVAIL_CLIENT_WAIT,
args: [
Expand Down
51 changes: 51 additions & 0 deletions packages/node-sdk/sm/primitives/avail/contract-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { createScheduledData, insertPrimitiveAccounting } from "@paima/db";
import { BuiltinTransitions, generateRawStmInput } from "@paima/concise";
import {
type ConfigPrimitivePayloadType,
ConfigPrimitiveAccountingPayloadType,
ConfigPrimitiveType,
type ConfigSyncProtocolType,
type FlattenSyncProtocolIOFor,
} from "@paima/config";
import { type StateUpdateStream, World } from "@paima/coroutine";

import type { PaimaBlockNumber } from "@paima/utils";

export default function* processAvailPaimaL2Datum(
paima_block_height: PaimaBlockNumber,
response: FlattenSyncProtocolIOFor<
ConfigSyncProtocolType.AVAIL_PARALLEL,
ConfigPrimitiveType.AvailPaimaL2,
ConfigPrimitivePayloadType.Event
>,
): StateUpdateStream<void> {
const { scheduledPrefix } = response.input;
const { payload, syncProtocol } = response.output;
const scheduledInputData = generateRawStmInput(
BuiltinTransitions[ConfigPrimitiveType.AvailPaimaL2]
.scheduledPrefix,
scheduledPrefix,
{ payload },
);
yield* World.resolve(insertPrimitiveAccounting, {
primitive_name: response.output.syncProtocol.payload.primitiveName,
paima_block_height: paima_block_height,
payload_type: ConfigPrimitiveAccountingPayloadType.Event,
payload: JSON.stringify(response.output.payload) as any,
});
if (scheduledPrefix) {
yield* createScheduledData(
JSON.stringify(scheduledInputData),
{
blockHeight: syncProtocol.payload.ownChain.blockNumber,
},
{
primitiveName: response.output.syncProtocol.payload.primitiveName,
txHash: response.output.syncProtocol.internal.transactionHash,
caip2: response.output.syncProtocol.payload.caip2,
fromAddress: response.input.contractAddress,
contractAddress: response.input.contractAddress,
}
);
}
}
Loading