Platform Integration Guide

This guide walks you through our platform’s technology stack. You’ll learn how to set up your environment, build composable processing units (CoCells) that integrate WASM modules, process their output through customizable consensus logic (including proof aggregation), and verify proofs using our abstracted verification module (powered by zkWasm). We also explain how to use our API endpoints for submitting execution requests.


Table of Contents

  1. Introduction
  2. Architecture Overview
  3. Getting Started
  4. Basic Integration
  5. Advanced Integration
  6. API Routes for WASM Execution
  7. Example: Rust WASM Addition Program
  8. Final Notes & Next Steps

1. Introduction

Our platform processes data via WebAssembly (WASM) and ensures verifiable computations through zero‑knowledge proofs (ZKPs) using zkWasm. By combining composable processing units (CoCells) with state channels and a customizable consensus layer, the system supports robust, scalable, and secure applications. Verification is abstracted through our verification module, so you can focus on your business logic without worrying about low‑level details.


2. Architecture Overview

The platform consists of several key components:

  • CoCells:
    Modular processing units that encapsulate business logic. They combine WASM execution, output post‑processing, and consensus operations.

  • WASM Integration:
    Converts input parameters into a WASM‑friendly format, executes the module, and converts the output back into native types.

  • State Channels:
    Provide isolated, high‑throughput execution environments to offload processing from the main chain.

  • Customizable Consensus:
    Once a WASM module produces output, a CoCell object processes it. This includes:

    • Parsing output and extracting events.
    • Triggering zero‑knowledge proof generation.
    • Aggregating proofs into a new block when a threshold is reached.
    • Optionally sending analysis data to an external validator.
  • Zero‑Knowledge Proofs (zk):
    Generated by capturing two execution traces (the WASM bytecode and host API calls) to ensure verifiability without revealing sensitive data. Our system uses zkWasm for this purpose. For more information on compiling a program that outputs zk proofs or on verification details, please refer to the Delphinus zkWasm Documentation.

  • API Routes:
    Secure endpoints allow external clients to submit WASM execution requests.


3. Getting Started

Prerequisites & Environment Setup

Ensure you have the following installed:

  • Java 11 (OpenJDK recommended)
  • SBT 1.x
  • Docker & Docker Compose
  • Git

Set the necessary environment variables (adjust as needed):

export APP_KEYSTORE=path/to/keystore.p12
export APP_KEYALIAS=your_alias
export APP_PASSWORD=your_password
export API_HOST=localhost
export API_PORT=8080

Project Structure

  • src/main/scala/org/reality/combined – Contains CoCells, state channels, consensus logic, and WASM executors.
  • src/main/scala/org/reality/dag/l1 – Contains API routes, transaction, and consensus-related code.
  • src/main/scala/org/reality/combined/examples – Contains example integrations (e.g., a simple addition module).
  • Makefile – Automates building and testing of WASM modules (see Rust example below).

4. Basic Integration

Creating Your First CoCell

CoCells are the building blocks of our processing pipelines. For example, here’s a CoCell wrapping a WASM module that adds two integers:

package org.reality.combined.examples

import cats.effect.IO
import cats.implicits.toTraverseOps
import org.reality.combined.{CoCell, Portal, WasmExecutorCoCell}
import org.reality.dag.l1.WasmExecutionParams
import io.circe.generic.auto._
import io.circe.{Decoder, Json}
import io.github.kawamuray.wasmtime.Val

class CombinedWasmExample extends Portal {
  import CoCell._

  // Input model for addition
  case class AddParams(a: Int, b: Int)

  // Define WASM execution parameters for the add_numbers function.
  val wasmProgram: WasmExecutionParams[AddParams, Int] = WasmExecutionParams(
    wasmPath = "/app/wasm/wasm_add.wasm",
    functionName = "add_numbers",
    paramsConverter = { params: AddParams =>
      Array(Val.fromI32(params.a), Val.fromI32(params.b))
    },
    resultConverter = { vals =>
      vals.headOption.map(_.i32()).getOrElse(0)
    },
    serializeToJson = { result =>
      Json.obj("result" -> Json.fromInt(result))
    },
    paramsDecoder = Decoder[AddParams]
  )

  // Combine with the state channel CoCell.
  private val l0CoCell: CombinedL0 = CombinedL0()
  private val stateChannelCoCell: WasmExecutorCoCell[AddParams, Int] = WasmExecutorCoCell(wasmProgram)

  val mergedCells: Seq[CoCell] = l0CoCell ++ stateChannelCoCell

  // Define the cell program setup.
  val cellProgram: List[String] => IO[Seq[Context[CoCell]]] = (args: List[String]) =>
    for {
      coCellsInContext <- mergedCells.map(_.setup(args)).sequence
    } yield coCellsInContext
}

WASM Integration Example

The WasmExecutorCoCell integrates your WASM module into the CoCell framework:

package org.reality.combined

import cats.effect.{Async, IO}
import org.reality.dag.l1.WasmExecutionParams
import org.reality.kernel.{Cell, CellError, StackF, Ω}
import org.reality.sdk.app.SDK
import org.reality.security.SecurityProvider

case class WasmExecutorCoCell[T, R](wasmProgram: WasmExecutionParams[T, R]) extends CoCell {
  val stateChannel: MkStateChannel = WasmExecutorStateChannel

  def mkCell[F[_]: Async: SecurityProvider: cats.effect.std.Random](
    ctx: BlockConsensusContext[F]
  ): Ω => Cell[F, StackF, Ω, Ω, Either[CellError, Ω]] = data => {
    val consensusCell: Ω => Cell[F, StackF, Ω, Ω, Either[CellError, Ω]] = BlockConsensusCell.mkCell(ctx)
    val wasmCell: Ω => Cell[F, StackF, Ω, Ω, Either[CellError, Ω]] = WasmExecutorStateChannel.mkCell[F](ctx)
    Cell.cellMonoid[F, StackF].combine(consensusCell(data), wasmCell(data))
  }

  override def setup(args: List[String]): IO[CoCell.Context[CoCell]] =
    setupCombined[org.reality.dag.l1.cli.Run](
      stateChannel :: Nil,
      this,
      argsToStartUp(args).l1method,
      org.reality.dag.l1.cli.method.opts,
      L1HttpApi.mkResources(_: org.reality.dag.l1.cli.Run, _: SDK[IO]),
      List(wasmProgram)
    )
}

5. Advanced Integration

State Channels & Consensus

After a WASM module processes input, its output is passed to a CoCell object that handles post‑processing. This includes:

  • Output Processing:
    The system examines the WASM output to extract events and identifiers (e.g., user or game IDs).

  • Proof Generation:
    The platform triggers zero‑knowledge proof generation (using zkWasm) through our abstracted verification module. This ensures that outputs are verifiable without exposing low‑level details.

  • Block Aggregation:
    When a sufficient number of proofs (e.g., 10 proofs) are accumulated, they are aggregated into a new block. The block hash is computed from the proof data, and the block is queued for consensus.

  • Optional Validator API Call:
    Analysis data—including events and metadata—may be sent to an external validator for further verification.

Below is an illustrative snippet showing how this customizable consensus logic works:

object WasmExecutorCellObj extends StateChannelCell {
  // (API configuration and helper methods omitted for brevity)

  def mkCell[F[_]](ctx: BlockConsensusContext[F])(
    implicit F: Async[F],
    S: SecurityProvider[F],
    R: cats.effect.std.Random[F]
  ): Ω => Cell[F, StackF, Ω, Ω, Either[CellError, Ω]] = {
    val zkExecutor = new ZKWasmExecutor[IO]
    var currentBlockHeight: Long = 0
    var currentProofs: List[ProofData] = List.empty

    def processProof(proofPath: String): F[Unit] = for {
      _ <- F.delay(println(s"[WASM Executor] Proof generated at: $proofPath"))
      proofInfo <- F.delay(extractProofInfo(proofPath, s"$proofPath.meta", System.currentTimeMillis()))
      _ <- F.delay { currentProofs = proofInfo :: currentProofs; println(s"[WASM Executor] Proof added: ${proofInfo.commitment}") }
      _ <- if (currentProofs.length >= 10) {
             for {
               timestamp <- F.delay(System.currentTimeMillis())
               orderedProofs = currentProofs.reverse
               blockHash = computeBlockHash(currentBlockHeight, orderedProofs, timestamp)
               block = ProofBlockWrapper(
                         height = currentBlockHeight,
                         hash = blockHash,
                         proofs = orderedProofs.map(_.toBlockConsensusProofData),
                         timestamp = timestamp,
                         proofsHash = ProofsHash(computeProofsHash(orderedProofs).value)
                       )
               queue <- cats.effect.std.Queue.unbounded[F, ProofBlockWrapper]
               _ <- queue.offer(block)
               _ <- F.delay { println(s"[WASM Executor] New block: height=$currentBlockHeight, proofs=${orderedProofs.length}"); currentBlockHeight += 1; currentProofs = List.empty }
             } yield ()
           } else F.unit
    } yield ()

    data => {
      new Cell[F, StackF, Ω, Ω, Either[CellError, Ω]](
        data,
        scheme.hyloM(
          AlgebraM[F, StackF, Either[CellError, Ω]] {
            case More(a) => F.pure(a)
            case Done(Right(cmd: AlgebraCommand)) =>
              cmd match {
                case AlgebraCommand.ProcessWasmOutput(wrapper) =>
                  for {
                    _ <- F.delay(println("[WASM Executor] Processing output"))
                    // Extract events, trigger proof generation, etc.
                    proofAttempt <- F.delay {
                      Try {
                        zkExecutor.generateProof("proof_name", List.empty, List.empty).unsafeRunSync()
                      }
                    }
                    _ <- proofAttempt match {
                           case Success(Right(path)) =>
                             F.delay(println("[WASM Executor] Proof succeeded")) *> processProof(path.toString)
                           case Success(Left(error)) =>
                             F.delay(println(s"[WASM Executor] Proof failed: ${error.message}"))
                           case Failure(exception) =>
                             F.delay(println(s"[WASM Executor] Unexpected error: ${exception.getMessage}"))
                         }
                    finalResult <- F.pure(Right(NullTerminal): Either[CellError, Ω])
                  } yield finalResult
                case _ => F.pure(Right(NullTerminal): Either[CellError, Ω])
              }
            case Done(other) => F.pure(Right(NullTerminal): Either[CellError, Ω])
          },
          CoalgebraM[F, StackF, Ω] {
            case CoalgebraCommand.EnqueueWasmOutput(wrapper) =>
              F.pure(Done(Right(AlgebraCommand.ProcessWasmOutput(wrapper))): Either[CellError, AlgebraCommand])
            case _ =>
              F.pure(Done(Right(AlgebraCommand.NoAction)): Either[CellError, AlgebraCommand])
          }
        ),
        {
          case d: WasmOutputWrapper => CoalgebraCommand.EnqueueWasmOutput(d)
          case _                    => CoalgebraCommand.Empty()
        }
      )
    }
  }
}

Zero‑Knowledge Proof Generation & Verification

Our platform uses zkWasm for zero‑knowledge proofs. Two traces are captured during execution—one for the WASM bytecode and one for host API calls—to ensure that all operations are verifiable without exposing sensitive data. Verification is handled by our abstracted verification module, which encapsulates the underlying toolchain. For additional information on compiling programs that output zk proofs or for verification details, please refer to the Delphinus zkWasm Documentation.


6. API Routes for WASM Execution

Our platform provides a secure HTTP endpoint for submitting WASM execution requests.

API Overview

  • Route: POST /execute-wasm
  • Expected Payload:
{
  "transaction": { /* Transaction details with signatures */ },
  "data": {
    "functionName": "add_numbers",
    "params": { "a": 3, "b": 4 }
  }
}

The payload’s data field is defined as follows:

package org.reality.dag.l1

import io.circe.generic.semiauto._
import io.circe.{Decoder, Encoder, Json}

case class WasmExecutionRequestData(functionName: String, params: Json)

object WasmExecutionRequestData {
  implicit val encoder: Encoder[WasmExecutionRequestData] = deriveEncoder
  implicit val decoder: Decoder[WasmExecutionRequestData] = deriveDecoder
}

How the Route Works

  1. Logging & Parsing:
    Logs the function name and parses the JSON payload.
  2. Signature Validation:
    Validates signatures on both the transaction and request data.
  3. Data Hash Verification:
    Computes the hash of the parameters and verifies it against the transaction.
  4. Transaction Validation:
    Uses a TransactionValidator to validate the transaction.
  5. Enqueueing:
    On successful validation, the request is added to the WASM execution queue.
  6. Response:
    Returns a JSON response containing dataHash and txHash, or an error message.

Example implementation:

package org.reality.combined

import cats.data.Validated
import cats.effect.Async
import cats.syntax.all._
import org.reality.dag.l1.WasmExecutionRequest
import org.reality.dag.l1.modules.L1Queues
import org.reality.dag.transaction.TransactionValidator
import org.reality.modules.{AdditionalRoutes, HttpApi}
import org.reality.security.SecurityProvider
import org.reality.security.signature.SignedValidator
import org.reality.tools.HashUtils.hashJson
import io.circe.Json
import io.circe.generic.auto._
import org.http4s._
import org.http4s.circe.CirceEntityCodec.circeEntityDecoder
import org.http4s.dsl.Http4sDsl
import org.typelevel.log4cats.slf4j.Slf4jLogger

class WasmExecutorRoutes[F[_]: Async: SecurityProvider](nodeApi: HttpApi[F], transactionValidator: TransactionValidator[F])
    extends AdditionalRoutes[F]
    with Http4sDsl[F] {
  private val queues = nodeApi.queues.asInstanceOf[L1Queues[F]]
  private val wasmExecutionQueue = queues.wasmExecutionQueue
  implicit val logger = Slf4jLogger.getLogger[F]

  val publicRoutes: HttpRoutes[F] = HttpRoutes.of[F] {
    case req @ POST -> Root / "execute-wasm" =>
      for {
        wasmRequest <- req.as[WasmExecutionRequest]
        _ <- logger.info(s"Received WASM execution request for function: ${wasmRequest.data.value.functionName}")
        signedValidator = SignedValidator.make[F]
        validatedTransaction <- signedValidator.validateSignatures(wasmRequest.transaction)
        validatedRequestData <- signedValidator.validateSignatures(wasmRequest.data)
        validationResult = (validatedTransaction, validatedRequestData).tupled
        response <- validationResult match {
          case Validated.Valid((validTransaction, validRequestData)) =>
            signedValidator.validateAllSignersMatch(List(validTransaction, validRequestData)) match {
              case Validated.Valid(_) =>
                for {
                  dataHash <- Async[F].delay(hashJson(validRequestData.value.params))
                  isValidHash = dataHash == validTransaction.value.dataHash
                  _ <- if (isValidHash) Async[F].unit
                       else {
                         val errorMsg = "Data hash does not match the hash in the transaction"
                         logger.error(errorMsg) *> BadRequest(Json.obj("error" -> Json.fromString(errorMsg)))
                       }
                  validatedTransactionResult <- transactionValidator.validate(validTransaction)
                  response <- validatedTransactionResult match {
                    case Validated.Valid(_) =>
                      for {
                        _ <- wasmExecutionQueue.offer(wasmRequest)
                        hashedTx <- validTransaction.toHashed[F]
                        response <- Ok(Json.obj("dataHash" -> Json.fromString(s"$dataHash"), "txHash" -> Json.fromString(s"${hashedTx.hash.value}")))
                      } yield response
                    case Validated.Invalid(errors) =>
                      val errorMsg = errors.toList.map(_.toString).mkString(", ")
                      logger.error(s"Transaction validation failed: $errorMsg") *> BadRequest(Json.obj("error" -> Json.fromString(s"Transaction validation failed: $errorMsg")))
                  }
                } yield response
              case Validated.Invalid(errors) =>
                val errorMsg = errors.toList.map(_.toString).mkString(", ")
                logger.error(s"Signers do not match: $errorMsg") *> BadRequest(Json.obj("error" -> Json.fromString(s"Signers do not match: $errorMsg")))
            }
          case Validated.Invalid(errors) =>
            val errorMsg = errors.toList.map(_.toString).mkString(", ")
            logger.error(s"Signature validation failed: $errorMsg") *> BadRequest(Json.obj("error" -> Json.fromString(s"Invalid signatures: $errorMsg")))
        }
      } yield response
  }

  val p2pRoutes: HttpRoutes[F] = HttpRoutes.empty[F]
}

Clients must ensure that the payload is correctly formatted and signed before sending a POST to /execute-wasm.


7. Example: Rust WASM Addition Program

Overview

A simple Rust project that compiles to WASM and exposes an add_numbers function.

Prerequisites

Project Structure

  • src/lib.rs: Contains Rust code.
  • Makefile: Automates building and testing.

Rust Code

// src/lib.rs
#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
    a + b
}

Build Instructions

Run:

make build

This produces the WASM module at:

target/wasm32-unknown-unknown/release/wasm_add.wasm

Test Instructions

Run:

make test

This should call add_numbers(3, 4) and return 7.

Sample Makefile

all: build test

build:
	cargo build --release --target wasm32-unknown-unknown

test:
	wasmer run target/wasm32-unknown-unknown/release/wasm_add.wasm --invoke add_numbers 3 4

clean:
	cargo clean

8. Final Notes & Next Steps

This guide has walked you through:

  • The overall platform architecture.
  • Setting up your development environment.
  • Building CoCells that integrate WASM modules.
  • Advanced post‑processing steps: once a WASM module produces output, a CoCell object processes it using customizable consensus logic. This involves:
    • Parsing output and extracting events.
    • Triggering zero‑knowledge proof generation via our abstracted verification module (powered by zkWasm).
    • Aggregating proofs into a new block once a threshold is reached.
    • Optionally sending analysis data to a validator API.
  • Using API endpoints for submitting WASM execution requests.
  • A complete example using a Rust WASM addition program.

For further details on how to compile programs that output zk proofs or for more information on verification, please refer to the Delphinus zkWasm Documentation.

You now have a complete, streamlined, and robust reference to get started with our platform. Feel free to extend or customize any section to meet your specific needs.

Happy coding!

Was this page helpful?