Forge

Forge is a simple elixir blockchain framework to build new chains easily. Built on top of tendermint and ex_abci, Forge implemented the core part of the state db and exposes interfaces to maintain the states so that developers could build their own blockchain app easily.

In a very high level, a typical blockchain app consists of:

Forge provides these functionalities:

Installation

The package can be installed by adding :forge to your list of dependencies in mix.exs:

def deps do
  [
    {:forge, "~> 0.4.0"}
  ]
end

the docs can be found at https://hexdocs.pm/forge.

Usage

Config

After adding Forge into your dependency, you can put these config to define the db and app info:

config :forge, db: "./states.db"

config :forge, :app_info, {"Final Chapter", "0.1.0"}

Supervision Tree

You shall add ExAbci.Listener and Forge.Server into your application supervision tree. Forge.Server needs a module which implements Forge.Handler callback:

def start(_type, _args) do
  children = [
    ExAbci.Listener.child_spec(Forge.Server),
    {Forge.Server, YourAwesomeChain.Handler}
  ]

  opts = [strategy: :one_for_one, name: Forge.Supervisor]
  Supervisor.start_link(children, opts)
end

Forge.Handler callback

You need to implement two callback functions provided by Forge.Handler:

@callback verify(TransactionMessage.t(), Account.t(), AccountState.t()) :: true | false
@callback update_state(String.t(), Any.t(), Account.t(), map()) :: Account.t()

Forge will call your verify callback upon check_tx / deliver_tx with the transaction, the MPT, and the sender’s account state. Once the transaction satisfied the state, you shall return true to Forge.

When a tx is being calculated on deliver_tx, Forge will call your update_state callback with the transaction, the sender address, custom data inside the transaction, MPT and the current context (e.g. block height, tx hash). Once the transaction is processed, the update MPT reference shall be returned.

Extending the transaction

The transaction is defined in Forge, like this:

message TransactionMessage {
  bytes from = 1;
  uint64 nonce = 2;
  bytes public_key = 3;
  bytes signature = 4;
  oneof value {
    TransferMessage transfer = 10;
    google.protobuf.Any any = 50;
  }
}

message TransferMessage {
  bytes to = 1;
  uint64 total = 2;
}

Forge can process the most basic transfer transactions without application handle anything. But if application want a much wider behavior in the chain, e.g. account holder can publish a post into the chain, then you should define your own transaction message inside the Forge transaction. For example, you can define a CreatePostMessage like this:

syntax = "proto3";
package Example;
message CreatePostMessage {
  string title = 1;
  string content = 2;
}

after building this with grpc-elixir, you can encode and sign your transaction with Forge.CustomTransaction.create:

type_url = "example.com/CreatePostMessage";
type_mod = Example.CreatePostMessage; # this module is generated by elixir grpc tool.
attrs = %{title: "hello", content: "world"}
CustomTransaction.create(sender_address, nonce, type_url, type_mod, attrs, public_key, private_key)

Extending the account state

The basic account state is defined in Forge, like this:

message AccountState {
  uint64 balance = 1;
  uint64 nonce = 2;
  uint64 num_txs = 3;
  bytes address = 4;
  bytes owner = 5;

  // 5-9 reserve for future

  bytes genesis_tx = 10;
  bytes renaissance_tx = 11;
  google.protobuf.Timestamp genesis_time = 12;
  google.protobuf.Timestamp renaissance_time = 13;

  // 14-49 reserve for future

  google.protobuf.Any data = 50;
}

nonce, num_txs, address, genesis_tx, renaissance_tx, tenesis_time and renaissance_time will be updated by Forge, you don’t need to worried about them. balance will be maintained by the handling of TransferMessage, but your custom transaction message can manipulate that as well. You could extend data in your way, for example, you want to keep a list of posts for the user, and each post has its own account:

message UserAccountState {
  string nickname = 1;
  repeated string posts = 2;
}

message PostAccountState {
  bytes title = 1;
  bytes content = 2;
}

After compiling the proto definition, you can encode them and use Account.renaissance to update it.