Forge: framework for decentralized apps
By Tyr Chen on 01/08/2019

Forge: framework for decentralized apps

Brought to you by Tyr Chen

Forge: framework for decentralized apps

The Status-Quo of dApps development

dApp: Using public chain vs Bring your own chain

  • Public chain

    • handy and always available
    • lots of existing use cases and projects
    • not under my control
    • smart contract is not straight forward
    • smart contract is only 20% of a whole app
  • Bring your own chain (BYOC)

    • lots of flexibility
    • not mixing my data with other apps
    • hard to implement from scratch

The solutions provided by ArcBlock

ArcBlock Platform: A Platform as a Service for building and deploying blockchain apps

  • OCAP Service: bridge user to existing public chain technology

    • OCAP SDK: client sdk for OCAP
    • ArcConsole / Tao / TokenFlow / etc.
  • ArcBlock Chain Node: ArcBlock's BYOC blockchain execution environment

    • Forge Framework: ArcBlock's full-stack framework for building dapps.

Comparison between two solutions

byoc

Forge and ABT chain node

ABT chain node

Existing solutions for BYOC

  • Hyperledger: complex to setup, learn and code
  • Tendermint: very good model but lacks of lots of work
  • Substrate: very interesting (and promising) but in early stage
  • Customized/Forked Ethereum: lots of people go this way but the glass ceiling is there

Chain on Forge (CoF): the Ruby on Rails (RoR) dream

  • Full-stack development framework
  • Build a dApp on its own chain should be easy enough
  • developers can use many languages to build dApps based on Forge
  • A wholistic framework that all the batteries are included
  • A powerful CLI that alleviate most day to day work
  • A ready-to-use UI to explore the chain and manage node / accounts / states
  • It could be used for cryptocurrency world, but we'd love to see a much broader usage on applications that distributed worldwide

The GOALs of forge

  • Vision: "Ruby on Rails" alike powerful framework for building dApps
  • Everything is pluggable

    • consensus engine: Tendermint or anything else
    • storage engine: IPFS or anything else
    • state db: MPT or anything else
  • Decisions are made dynamically

    • engines: load time (after loading configuration)
    • wallet: create time (app or even user can choose what kind of algo to use)
  • User experience at its core

    • forge-cli can alleviate most of the normal operation for building the dApp
    • forge-web can allow dev/user easily explore and administrate the chain
    • wallet migration: user can choose to migrate to a new set of keys in case of existing keys are insecure (due to algo or due to loss)
    • system migration: stakeholders can vote on a planned migration and if that vote gets consensus every participating nodes will migrate automatically

The use cases for forge

  • build a blockchain that with cryptocurrency (like an advanced ERC20/ERC721/... token)
  • build a dApp that solve a certain problem

    • decentralized usage tracking and audit trail
    • decentralized identity
  • move existing internet application forward to decentralized world (why?)

    • decentralized twitter
    • decentralized wordpress
    • decentralized slack
  • build things that cannot be built on centralized solution

The baseline: Forge could be a big part on an app that partially on-chain and partially off-chain.

Forge framework: the entire flow

forge

How transaction handled in Forge

Forge tx

Things included in Forge

  • Core:

    • forge server: handles ABCI protocol, delivers tx and updates the states
    • forge pubsub: broadcast the interesting data to subscribers for given filters
    • forge RPC: GRPC server to expose the capabilities of forge
    • consensus engine: manages the consensus engine and provide consensus RPC
    • storage engine: manages the storage engine and provide storage RPC
  • SDK:

    • proto definition: core data structure and Protocols
    • RPC client: easy to use interface for developers
    • ABI server: server to interface with Forge so that forge app can just integrate it in
    • wallet tools: handles and manages wallet

Things included in Forge

  • Tools:

    • forge CLI: easy to use CLI for forge
    • forge web: web interfaces to explain the chain data and manage the chain
    • forge simulator: simulate things happened in the wild. For testing purpose
    • forge indexer: index the data for various purpose
    • forge benchmark: performance benchmark
    • etc.

Transactions supported by forge

  • declare
  • transfer
  • exchange
  • createasset / updateasset
  • stake (for node, user, asset, app)
  • vote
  • account_migrate
  • system_upgrade

Building a Forge app (elixir)
nodejs/python sdk is on its way

Define the tx and state structure

syntax = "proto3";
package kvstore_abi;

import "vendor.proto";

// add a new inner tx
message KvTx {
  bytes key = 2;
  bytes value = 3;
}

// extend account state with more data
message AccountKvState {
  repeated forge_vendor.KVPair store = 1;
}

verify callback

  @type t :: KvstoreAbi.KvTx.t()

  @doc """
  Verify an inner transaction
  """
  @spec verify(t(), Transaction.t(), AccountState.t(), [AccountState.t()]) :: non_neg_integer()
  def verify(%{key: ""}, _tx, _sender_state, _states),
    do: StatusCode.value(:insufficient_data)

  def verify(%{value: ""}, _tx, _sender_state, _states),
    do: StatusCode.value(:insufficient_data)

  def verify(%{key: key}, _tx, sender_state, _states) do
    data = decode_data(sender_state.data)

    case Enum.any?(data.store, fn %{key: k} -> key === k end) do
      true -> StatusCode.value(:invalid_sender_state)
      _ -> StatusCode.value(:ok)
    end
  end

update state

  @doc """
  Update state after applying the transaction
  """
  @spec update_state(t(), Transaction.t(), AccountState.t(), [AccountState.t()]) ::
          ResponseUpdateState.t()
  def update_state(%{key: key, value: value}, _tx, sender_state, _states) do
    data = decode_data(sender_state.data)
    data = %{data | store: [KVPair.new(key: key, value: value) | data.store]}

    state = %{sender_state | data: encode_data(data)}
    ResponseUpdateState.new(states: [state])
  end

  # private function
  def decode_data(nil), do: AccountKvState.new()

  def decode_data(data) do
    {_type, value} = ForgeSdk.decode_any(data)
    value
  end

  def encode_data(data) do
    ForgeSdk.encode_any(:kv_state, data)
  end

On application start

  def start(_type, _args) do
    filename = :kvstore |> Application.app_dir() |> Path.join("priv/forge_kv.toml")
    servers = ForgeSdk.init(:kvstore, filename)
    update_type_url()

    opts = [strategy: :one_for_one, name: Kvstore.Supervisor]
    # use forge provided ABI servers
    Supervisor.start_link(servers, opts)
  end

  # private function
  defp update_type_url do
    ForgeSdk.TypeUrl.add([
      {:kv, "KV/kv", KvstoreAbi.KvTx},
      # state
      {:kv_state, "KV/kv_state", KvstoreAbi.AccountKvState}
    ])
  end

Kvstore: DEMO

iex(kvstore@127.0.0.1)> {w, t} = ForgeSdk.create_wallet(ForgeAbi.RequestCreateWallet.new(moniker: "tyrchen", passphrase: "abcd1234"))
{%ForgeAbi.WalletInfo{
   address: "f45711c95de3b2daca564a56fbe7d1a5797993c93",
   pk: <<145, 241, 247, 51, 50, 112, 252, 170, 112, 98, 45, 104, 113, 191, 7,
     213, 191, 239, 216, 73, 120, 116, 96, 177, 95, 162, 56, 164, 138, 147, 175,
     203>>,
   sk: "",
   type: %ForgeAbi.WalletType{...}
 }, <<56, 132, 180, 83, 155, 55, 4, 17, 202, 166, 222, 99, 41, 170, 35, 192>>}

iex(kvstore@127.0.0.1)> Kvstore.kv(KvstoreAbi.KvTx.new(key: "hello", value: "world"), wallet: w, token: t)
"630AAC4F256CB19B057BDDC303D8B19CF75FAAEAE1EA9FE875D3B1301EA2E554"

Kvstore: result

iex(kvstore@127.0.0.1)> Kvstore.get_account(w.address) |> ForgeSdk.display
%{
  address: "f45711c95de3b2daca564a56fbe7d1a5797993c93",
  balance: 1000000000000000000,
  data: %{store: [%ForgeVendor.KVPair{key: "hello", value: "world"}]},
  genesis_time: "21 hours 27 minutes ago",
  genesis_tx: "31EBFE497AF0BCA1E758DCADA227F84C4458A4E47EE224D3C404149CCEC43760",
  migrated_from: [],
  migrated_to: "",
  moniker: "tyrchen",
  nonce: 3,
  num_txs: 2,
  pk: "kfH3MzJw/KpwYi1ocb8H1b/v2El4dGCxX6I4pIqTr8s",
  power: 128,
  renaissance_time: "1 minutes 20 seconds ago",
  renaissance_tx: "630AAC4F256CB19B057BDDC303D8B19CF75FAAEAE1EA9FE875D3B1301EA2E554",
  role: 0,
  type: %ForgeAbi.WalletType{...}
}

Forge internals

Major protcols

protocols

TX processing in detail

protocols

Structure of forge application

pubsub

Structure of pubsub application

pubsub

Simulator demo

CPU utilization of Forge

perf

Pubsub demo

iex> req = ForgeAbi.RequestSubscribe.new(type: ForgeAbi.TopicType.value(:transfer), filter: "value.itx.value > 100")
%ForgeAbi.RequestSubscribe{filter: "value.itx.value > 100", type: 0}
iex> stream = ForgeSdk.subscribe(req)
#Stream<[
  enum: #Function<64.51129937/2 in Stream.unfold/2>,
  funs: [#Function<48.51129937/1 in Stream.map/2>]
]>
iex> Enum.take(stream, 1)
[topic: "198d69ca3fb2538fd3669c469554c726"]

Pubsub result

iex> [{_, tx}] = Enum.take(stream, 1)
[
  transfer: %ForgeAbi.Transaction{
    chain_id: 1,
    from: "z3Xj6828hC2SrUkgQ9KXVjt3DPfxL",
    itx: %Google.Protobuf.Any{
      type_url: "ft/Transfer",
      value: <<10, 28, 122, 76, 66, 52, 121, 99, 101, 90, 69, 82, 52, 67, 116,
        105, 122, 105, 116, 105, 74, 116, 121, 86, 110, 66, 76, 70, 72, 107, 18,
        3, 10, 1, 101>>
    },
    nonce: 65,
    signature: <<181, 235, 208, 10, 200, 18, 10, 76, 214, 242, 216, 134, 32, 37,
      159, 177, 62, 29, 50, 83, 84, 137, 99, 172, 143, 127, 1, 128, 23, 198, 62,
      231, 194, 21, 41, 52, 146, 157, 119, 67, 64, 70, 7, 144, ...>>,
    signatures: []
  }
]

Pubsub result decode

iex> ForgeSdk.display(tx)
%{
  chain_id: 1,
  from: "z3Xj6828hC2SrUkgQ9KXVjt3DPfxL",
  itx: %{
    assets: [],
    confirmation: false,
    to: "zLB4yceZER4CtizitiJtyVnBLFHk",
    value: 101
  },
  nonce: 65,
  signature: "tevQCsgSCkzW8tiGICWfsT4dMlNUiWOsj38BgBfGPufCFSk0kp13Q0BGB5D8XGauBfQ4LSGPy1gHsEGkjm4mAQ",
  signatures: []
}

Forge web: simple explorer so far

web
Arcblock