Sabre Transaction Family

Overview

Sawtooth Sabre is a transaction family which implements on-chain smart contracts executed in a WebAssembly virtual machine.

WebAssembly (Wasm) is a stack-based virtual machine newly implemented in major browsers. It is well-suited for the purposes of smart contract execution due to its sandboxed design, growing popularity, and tool support.

State

All Sabre objects are serialized using Protocol Buffers before being stored in state. Theses objects include namespace registries, contract registries, and contracts. All objects are stored in a list to handle hash collisions.

NamespaceRegistry

A namespace is a state address prefix used to identify a portion of state. The NamespaceRegistry stores the namespace, the owners of the namespace, and the permissions given to that namespace. NamespaceRegistry is uniquely identified by its namespace.

Permissions are used to control read and/or write access to the namespace. It includes a contract name that correlates to the name of a Sabre contract and whether that contract is allowed to read and/or write to that namespace. The permission is uniquely identified by the contract_name. If the contract is executed but does not have the needed permission to read or write to state, the transaction is considered invalid.

message NamespaceRegistry {
  message Permission {
    string contract_name = 1;
    bool read = 2;
    bool write = 3;
  }

  string namespace = 1;
  repeated string owners = 2;

  repeated Permission permissions = 3;
}

When the same address is computed for different namespace registries, a collision occurs; all colliding namespace registries are stored in at the address in a NamespaceRegistryList

message NamespaceRegistryList {
  repeated NamespaceRegistry registries = 1;
}

ContractRegistry

ContractRegistry keeps track of versions of the Sabre contract and the list of owners. A ContractRegistry is uniquely identified by the name of its contract.

Versions represent the contract version and include the sha512 hash of the contract and the public key of the creator. The hash can be used by a client to verify this is the correct version of the contract that should be executed.

message ContractRegistry {
  message Version {
    string version = 1;

    // used to verify if a contract is the same as the one the client intended
    // to invoke
    string contract_sha512 = 2;

    // for client information purposes only - the key that created this
    // contract on the chain
    string creator = 3;
  }

  string name = 1;
  repeated Version versions = 2;
  repeated string owners = 3;
}

ContractRegistry entries whose addresses collide are stored in a ContractRegsitryList.

message ContractRegistryList {
  repeated ContractRegistry registries = 1;
}

Contract

A Contract represents the Sabre smart contract. It is uniquely identified by its name and version number. The contract also contains the expected inputs and outputs (namespaces) used when executing the contract, the public key of the creator, and the compiled wasm code of the contract.

message Contract {
  string name = 1;
  string version = 2;
  repeated string inputs = 3;
  repeated string outputs = 4;
  string creator = 5;
  bytes contract = 6;
}

Contracts whose addresses collide are stored in a ContractList.

message ContractList {
  repeated Contract contracts = 1;
}

Smart Permission

A smart permission is an executable piece of WebAssembly code. A smart permission is named, and associated with an organization created via the Pike transaction processor.

A smart permission is defined with three fields:

  • name: the name of the smart permission function as defined by a CreateContractAction

  • function: a byte array that stores the executable code of the smart permission

  • org_id: The identifier of the organization to which the smart permission function belongs. This organization is created via the Pike transaction processor

message SmartPermission {
  string name = 1;
  string org_id = 2;
  bytes function = 3;
}

Smart Permission List

Smart Permissions whose addresses collide are stored in a smart permission list. A smart permission list contains one field:

  • smart_permissions: a list of smart permissions

message SmartPermissionList {
    repeated SmartPermission smart_permissions = 1;
}

Addressing

Sabre objects are stored under 4 namespaces:

  • 00ec00: Namespace for NamespaceRegistry

  • 00ec01: Namespace for ContractRegistry

  • 00ec02: Namespace for Contracts

  • 00ec03: Namespace for Smart Permissions

The remaining 64 characters of the object’s address is the following:
  • NamespaceRegistry: the first 64 characters of the hash of the first 6 characters of the namespaces.

  • ContractRegistry: the first 64 characters of the hash of the name.

  • Contract: the first 64 characters of the hash of “name,version”

  • Smart Permission: first 6 characters of the hash of the organization ID and the first 58 characters of the hash of the smart permission name.

For example, the address for a contract with name “example” and version “1.0” address would be:

>>> '00ec02' + get_hash("example,1.0")
'00ec0248a8e00e3fbca83815668ec5eee730023e6eb61b03b54e8cae1729bf5a0bec64'

Transaction Payload and Execution

Below, the different payload actions are defined along with the inputs and outputs that are required in the transaction header.

SabrePayload

A SabrePayload contains an action enum and the associated action payload. This allows for the action payload to be dispatched to the appropriate logic.

Only the defined actions are available and only one action payload should be defined in the SabrePayload.

message SabrePayload {
  enum Action {
    ACTION_UNSET = 0;
    CREATE_CONTRACT = 1;
    DELETE_CONTRACT = 2;
    EXECUTE_CONTRACT = 3;
    CREATE_CONTRACT_REGISTRY = 4;
    DELETE_CONTRACT_REGISTRY = 5;
    UPDATE_CONTRACT_REGISTRY_OWNERS = 6;
    CREATE_NAMESPACE_REGISTRY = 7;
    DELETE_NAMESPACE_REGISTRY = 8;
    UPDATE_NAMESPACE_REGISTRY_OWNERS = 9;
    CREATE_NAMESPACE_REGISTRY_PERMISSION = 10;
    DELETE_NAMESPACE_REGISTRY_PERMISSION = 11;
    CREATE_SMART_PERMISSION = 12;
    UPDATE_SMART_PERMISSION= 13;
    DELETE_SMART_PERMISSION = 14;
  }

  Action action = 1;

  CreateContractAction create_contract = 2;
  DeleteContractAction delete_contract = 3;
  ExecuteContractAction execute_contract = 4;

  CreateContractRegistryAction create_contract_registry = 5;
  DeleteContractRegistryAction delete_contract_registry = 6;
  UpdateContractRegistryOwnersAction update_contract_registry_owners = 7;

  CreateNamespaceRegistryAction create_namespace_registry = 8;
  DeleteNamespaceRegistryAction delete_namespace_registry = 9;
  UpdateNamespaceRegistryOwnersAction update_namespace_registry_owners = 10;
  CreateNamespaceRegistryPermissionAction create_namespace_registry_permission = 11;
  DeleteNamespaceRegistryPermissionAction delete_namespace_registry_permission = 12;

  CreateSmartPermissionAction create_smart_permission = 13;
  UpdateSmartPermissionAction update_smart_permission = 14;
  DeleteSmartPermissionAction delete_smart_permission = 15;
}

CreateContractAction

Creates a contract and updates the associated contract registry.

message CreateContractAction {
  string name = 1;
  string version = 2;
  repeated string inputs = 3;
  repeated string outputs = 4;
  bytes contract = 5;
}

If a contract with the name and version already exists the transaction is considered invalid.

The contract registry is fetched from state and the transaction signer is checked against the owners. If the signer is not an owner, the transaction is considered invalid.

If the contract registry for the contract name does not exist, the transaction is invalid.

Both the new contract and the updated contract registry are set in state.

The inputs for CreateContractAction must include:

  • the address for the new contract

  • the address for the contract registry

The outputs for CreateContractAction must include:

  • the address for the new contract

  • the address for the contract registry

Note

These inputs/outputs are for the general Sawtooth transaction. They are required for any transaction, whether it is a Sabre transaction or a transaction of any other transaction family. However, the inputs/outputs fields in the CreateContractAction, above, are not related to the ones listed in the Sawtooth transaction. The CreateContractAction inputs/outputs are used to enforce namespace permissions for the contract.

DeleteContractAction

Delete a contract and remove its entry from the associated contract registry.

message DeleteContractAction {
  string name = 1;
  string version = 2;
}

If the contract does not already exist or does not have an entry in the contract registry, the transactions is invalid.

If the transaction signer is not an owner, they cannot delete the contract and the transaction is invalid.

The contract is deleted and the version entry is removed from the contract entry.

The inputs for DeleteContractAction must include:

  • the address for the contract

  • the address for the contract registry

The outputs for DeleteContractAction must include:

  • the address for the contract

  • the address for the contract registry

ExecuteContractAction

Execute the contract.

message ExecuteContractAction {
  string name = 1;
  string version = 2;
  repeated string inputs = 3;
  repeated string outputs = 4;
  bytes payload = 5;
}

The contract is fetched from state. If the contract does not exist, the transaction is invalid.

The inputs and outputs are then checked against the namespace registry associated with the first 6 characters of each input or output. If the input or output is less than 6 characters the transaction is invalid. For every input, the namespace registry must have a read permission for the contract and for every output the namespace registry must have a write permission for the contract. If either are missing or the namespace registry does not exist, the transaction is invalid. The inputs and outputs in the ExecuteContractAction payload shall not be mistaken with the inputs and outputs of the Sawtooth transaction carrying the payload.

The contract is then loaded into the wasm interpreter and run against the provided payload. A result is returned. If the result is 1 the transaction is okay and the contract data is stored in state. If the result is -3 the transaction is invalid. If any other number result is returned there was an internal error.

The inputs for ExecuteContractAction must include:

  • the address for the contract

  • the address for the contract registry

  • any inputs that are required for executing the contract

  • the addresses for every namespace registry required to check the provided contract inputs

The outputs for ExecuteContractAction must include:

  • the address for the contract

  • the address for the contract registry

  • any outputs that are required for executing the contract

  • the addresses for every namespace registry required to check the provided contract outputs

CreateContractRegistryAction

Create a contract registry with no version.

message CreateContractRegistryAction {
  string name = 1;
  repeated string owners = 2;
}

If the contract registry for the provided contract name already exists, then the transaction is invalid.

Only those whose public keys are stored in sawtooth.swa.administrators are allowed to create new contract registries. If the transaction signer is an administrator, the new contract registry is set in state. Otherwise, the transaction is invalid.

The new contract registry is created for the name and provided owners. The owners should be a list of public keys of users that are allowed to add new contract versions, delete old versions, and delete the registry.

The new contract registry is set in state.

The inputs for CreateContractRegistryAction must include:

  • the address for the contract registry

  • the settings address for sawtooth.swa.administrators

The outputs for CreateContractRegistryAction must include:

  • the address for the contract registry

DeleteContractRegistryAction

Deletes a contract registry if there are no versions.

message DeleteContractRegistryAction {
  string name = 1;
}

If the contract registry does not exist, the transaction is invalid. If the transaction signer is not an owner or does not have their public key in sawtooth.swa.administrators or the contract registry has any number of versions, the transaction is invalid.

The contract registry is deleted.

The inputs for DeleteContractRegistryAction must include:

  • the address for the contract registry

  • the settings address for sawtooth.swa.administrators

The outputs for DeleteContractRegistryAction must include:

  • the address for the contract registry

UpdateContractRegistryOwnersAction

Update the contract registry’s owners list.

message UpdateContractRegistryOwnersAction {
  string name = 1;
  repeated string owners = 2;
}

If the contract registry does not exist or the transaction signer is not an owner or does not have their public key in sawtooth.swa.administrators, the transaction is invalid.

The new owner list will replace the current owner list and the updated contract registry is set in state.

The inputs for UpdateContractRegistryOwnersAction must include:

  • the address for the contract registry

  • the settings address for sawtooth.swa.administrators

The outputs for UpdateContractRegistryOwnersAction must include:

  • the address for the contract registry

CreateNamespaceRegistryAction

Creates a namespace registry with no permissions.

message CreateNamespaceRegistryAction {
  string namespace = 1;
  repeated string owners = 2;
}

The namespace must be at least 6 characters long. If the namespace registry already exists, the transaction is invalid.

Only those whose public keys are stored in sawtooth.swa.administrators are allowed to create new namespace registries. If the transaction signer is an administrator, the new namespace registry is set in state. Otherwise, the transaction is invalid.

The inputs for CreateNamespaceRegistryAction must include:

  • the address for the namespace registry

  • the settings address for sawtooth.swa.administrators

The outputs for CreateNamespaceRegistryAction must include:

  • the address for the namespace registry

DeleteNamespaceRegistryAction

Deletes a namespace registry if it does not contains any permissions.

message DeleteNamespaceRegistryAction {
  string namespace = 1;
}

If the namespace registry does not exist or contain permissions, the transaction is invalid.

If the transaction signer is either an owner in the namespace registry or has their public key in sawtooth.swa.administrators, the namespace registry is deleted. Otherwise, the transaction is invalid.

The inputs for DeleteNamespaceRegistryAction must include:

  • the address for the namespace registry

  • the settings address for sawtooth.swa.administrators

The outputs for DeleteNamespaceRegistryAction must include:

  • the address for the namespace registry

UpdateNamespaceRegistryOwnersAction

Update the namespace registry’s owners list.

message UpdateNamespaceRegistryOwnersAction {
  string namespace = 1;
  repeated string owners = 2;
}

If the namespace registry does not exist, the transaction is invalid.

If the transaction signer is either an owner in the namespace registry or has their public key in sawtooth.swa.administrators, the namespace registry’s owners are updated. Otherwise, the transaction is invalid.

The updated namespace registry is set in state.

The inputs for UpdateNamespaceRegistryOwnersAction must include:

  • the address for the namespace registry

  • the settings address for sawtooth.swa.administrators

The outputs for UpdateNamespaceRegistryOwnersAction must include:

  • the address for the namespace registry

CreateNamespaceRegistryPermissionAction

Adds a permission entry into a namespace registry for the associated namespace.

message CreateNamespaceRegistryPermissionAction {
  string namespace = 1;
  string contract_name = 2;
  bool read = 3;
  bool write = 4;
}

If the namespace registry does not exist, the transaction is invalid.

If the transaction signer is either an owner in the namespace registry or has their public key in sawtooth.swa.administrators, a new permission is added for the provided contract_name. Otherwise, the transaction is invalid.

If there is already a permission for the contract_name in the namespace registry, the old permission is removed and replaced with the new permission.

The updated namespace registry is set in state.

The inputs for CreateNamespaceRegistryPermissionAction must include:

  • the address for the namespace registry

  • the settings address for sawtooth.swa.administrators

The outputs for CreateNamespaceRegistryPermissionAction must include:

  • the address for the namespace registry

DeleteNamespaceRegistryPermissionAction

Delete a permission entry in a namespace registry for the associated namespace.

message DeleteNamespaceRegistryPermissionAction {
  string namespace = 1;
  string contract_name = 2;
}

If the namespace registry does not exist, the transaction is invalid. If the transaction signer is either an owner in the namespace registry or has their public key in sawtooth.swa.administrators, the permission for the provided contract name is removed. Otherwise, the transaction is invalid.

The inputs for DeleteNamespaceRegistryPermissionAction must include:

  • the address for the namespace registry

  • the settings address for sawtooth.swa.administrators

The outputs for DeleteNamespaceRegistryPermissionAction must include:

  • the address for the namespace registry

CreateSmartPermissionAction

This operation loads a smart permission into Global State. The bytes provided are compiled smart permission code. org_id is the Organization identifier (smart permissions are organization-specific). name is the name of the function known to application transaction processors using this smart permission during that transaction processor’s permission function evaluation. Only an agent that holds an admin role for the included organization can create smart permissions for the organization. Agents and Organization are created and registered by the Pike transaction processor.

message CreateSmartPermissionAction {
  string name = 1;
  string org_id = 2;
  bytes function = 3;
}

UpdateSmartPermissionAction

This operation updates the bytes of smart permission function stored in Global State. Only an agent that holds an admin role for the included organization can update smart permissions for the organization. Agents and Organization are created and registered by the Pike transaction processor.

message UpdateSmartPermissionAction {
  string name = 1;
  string org_id = 2;
  bytes function = 3;
}

DeleteSmartPermissionAction

This operation deletes an existing smart permission function stored in Global State. Only an agent that holds an admin role for the included organization can delete smart permissions for the organization. Agents and Organization are created and registered by the Pike transaction processor.

  message DeleteSmartPermissionAction {
    string name = 1;
    string org_id = 2;
}

Transaction Header

Inputs and Outputs

The required inputs and outputs are defined for each action payload above.

Dependencies

No dependencies.

Family

  • family_name: “sabre”

  • family_version: “0.3”