How to write a transaction protocol
To write a new transaction protocol you basically need to prepare the following files:
- config.yml or config.json: configuration for this transaction protocol. Used by
forge-compiler
to know which files to compile and what metadata to include in the protocol. - a proto file (optional): the protobuf definition for your transaction protocol. Must be referenced in config.yml.
- a pipeline file (optional): the transaction pipeline for your transaction protocol. Must be referenced in config.yml.
- a set of source code: the logic of the transaction protocol.
Once you write a new tx protocol, you can either compile it directly with
forge-compiler
:
$ forge-compiler config.yml
or add it into Makefile
so that make build-protocols
will take care it for you.
The compiled result is a url based64 encoded (padding: false) deploy protocol itx. For your local node, you can use a moderator's wallet to send it to the chain.
config.yml
An example of config.yml looks like this:
name: consume_asset
version: 0
namespace: CoreTx
description: Consume an asset that is owned by self
type_urls:
fg:t:consume_asset: ForgeAbi.ConsumeAssetTx
proto: protocol.proto
pipeline: protocol.yml
sources:
- protocol.ex
typeurls are a map of type urls (key is the typeurl and value is the module name) that you'd register to ForgeAbi. The typeurls mentioned here will be used by `ForgeAbi.encodeany/
ForgeAbi.decode_any`.
version must be monotonically increasing. Forge will refuse to install a tx protocol with an old version.
protocol source code
Normally you just need a single file if your protocol is not too complex. The file structure looks like this:
defmodule CoreTx.ProtocolName do
# RPC helper function for sdk to use
defmodule Rpc do
import ForgeSdk.Tx.Builder, only: [tx: 1]
tx :protocol_name
end
# customized pipe for Check pipeline
defmodule CheckTx do
use ForgePipe.Builder
def init(opts), do: opts
def call(info, _opts) do
info
end
end
# customized pipe for Verify pipeline
defmodule CheckTx do
use ForgePipe.Builder
def init(opts), do: opts
def call(info, _opts) do
info
end
end
# customized pipe for Update pipeline
defmodule UpdateTx do
use ForgePipe.Builder
def init(opts), do: opts
def call(info, _opts) do
info
end
end
end
If your code logic is too complex to put into one file, you can use multiple source files, just remember to reference them into the config.yml.
Guide on protocol upgrade
Protobuf message model ensures that if we're following certain rules, we can upgrade the message with backward-compatible changes. These rules are:
- Existing fields should not be renumbered. If you have already used
string name = 1
you can not dostring name = 2
later on. -
Existing fields should not have their types changed. You can't change
BigSint value = 2
toBigUint value = 2
. Exceptions:- regular field can be upgraded to an oneof
- a regular field can be upgraded to a repeated field.
- New fields should not reuse any previously assigned field number.
- Enum defaults should be picked such that they make sense looking forward, or be set to UNSPECIFIED.