Privacy
Introduction
STRATO utilizes Private Chains to implement data privacy. Private Chains combine the audibility and verifiability of data stored on the blockchain with the ability to permission that data to different nodes in the network. Private chains operate similarly to the Main Chain - they have their own accounts and transactions which operate exclusively within their own address space. All private chain data is only visible to the permissioned members of the chain. User accounts become members of a private chain by being added by existing members.
Data Visibility
Private chains connect their own private transactions to the Main Chain by putting a secure hash of a transaction into a block's transaction data. This allows the transactions and data on a private chain to be recorded on the main chain, but not identifiable by non-members. A user that does not have access to a Private Chain will only see the hashed data of the private chain, but the data will have no other indication of what transactions occurred on the private chain, or any other information about the chain such as its members or chain ID. This hash acts as an encrypted "stub" of the transaction on the main chain, indicating a transaction exists, but no more details are visible.
Private Chain Use Cases
Private chains enable privacy by allowing data to be privileged amongst users. As an example, an application might have three types of user roles - Administrators, Managers, and User (A standard app user). There is global app data that needs to be visible to all users, however there also needs to be data that is only visible to Admins or only Managers. This would be accomplished by organizing the chains as follows:
- Main Chain
- Contains all public application contracts
- Administrator Private Chain
- Contains all contracts containing data for Administrators.
- Child of the Main Chain.
- Manager Private Chain
- Contains all contracts containing data for Managers.
- Child of the Main Chain.
Since private chains enable data to be permissioned by a user's identity, private chains are also especially useful when dealing with data visibility between internal and external parties'. This is useful when an application must be capable of handling data visibility from multiple parties, who might not have the same levels of permissions on a given asset or agreement. An internal party might be a member of your own organization, or group, and an external party might be a member of the network able to see or interact with data of your own organization or group. Private chains could be used for external and internal data respectively:
- External Chain
- Contains data and contracts visible to external parties.
- Internal Chain
- Contains data and contracts visible to internal parties.
Private Chains in Smart Contracts
Since private chains are not a feature of standard Ethereum, they cannot be accessed or be interacted with from standard Solidity contracts compiled and executed with the EVM. You must use BlockApps SolidVM to use private chain functionality within smart contracts. SolidVM uses the account
type to combine an account address and chain ID into a single type that allows an account on any chain to be referenced. The account
type behaves similarly to the Solidity address
type. Read more about the account type.
Node Synchronization
The STRATO P2P Protocol enables nodes to synchronize private chain data between its members. A node will only send and receive a private chain's data if a user account on it is currently a member of the chain. When a node sends a private transaction to STRATO, the full transaction data is only sent to the nodes of members of that chain, and not the entire network. The encrypted transaction stub is sent to the entire network to be inserted into a block. Non-member nodes do not validate transactions.
When a user is added as a member of a private chain, the STRATO P2P protocol will immediately initialize a synchronization between the node of the new user and the existing member nodes. This provides the new node with the data for the private chain. This includes all past transactions and state data for accounts on the chain. The node also receives a list of the current members of the chain in order to facilitate further transactions on this chain.
When a node is completely removed as a member of a private chain, the remaining member nodes will cease sending private transactions from this chain to the removed node. The node will still retain all past transactions from the chain that it already had. Since membership is associated with a member on a node, private chain information will continue to sync and be accessible to any account on a node as long as there is 1 or more member of a chain on that node. For this reason we recommend designing your network with this in mind. For the most secure usage of private chains, allocate one node to each user in your consortium.
Info
If a user is added as a member of a chain but they are not a member of its parent chain, the node will not sync chain data until it has been added as a member of the parent chain and a new transaction on the child chain has been sent through the network. Users must be added as members of a chain in ascending order of their depth.
If a user is removed as a member of a parent chain, but they are still a member of a child chain, they will cease receiving transactions for both parent and child chains. This follows the rule of a user needing to be a member of a parent chain to be a member of any of its children chains. The removal of membership from a chain "cuts ties" between the node and all of the chain's descendants.
Chain Relationships
Private Chains have directed acyclic relationships between other chains, creating an interconnected topography of chains on a STRATO network. Private chains can either be connected to the main chain itself - creating the first edges between chains on the network - or other private chains. STRATO supports arbitrary numbers of chains and chain relationships, however chain relationships can only be created during a chain creation, and cannot be modified after the fact.
Members of chains are restricted by the chain relationships that the chain has. A node can only be a member of a chain if it is also a member of each connected chain. By the transitive property, then for a chain relationship A -> B -> C and if there is a member X in chain A, then member X must also be a member of chains B and C. If a node is a member of an origin chain but not a connected chain, then they will not have access to the origin chain. However if they are added to the connected chain, they will automatically be given membership to the origin chain. Conversely, if a node is a member of both chains and they are removed from the connected chain, they will also lose access to the origin chain.
A connection between one chain to another enables the "from" chain to read data from the contracts on the "to" chain. Chains can never modify data on another private chain, since it would break the data privacy between chain members*. Chains can also only read data in the direction of a connection, meaning chains can only read data from their "to" chain, but the "to" chain cannot read data from that same chain.
A private chain can be connected to other chains by declaring them as "Parent Chains" (parentChains
property of request body) in the STRATO POST /chain
endpoint. Users must provide a name and chainId
pair for each connected chain. The name is used to reference this chain in smart contracts on the "from" chain. Users can also declare a singular connected chain by using the singular "parentChain
" property and providing it the chainId
of the desired parent chain. This chain can referenced from smart contracts using the "parent" keyword and is therefore reserved from user-provided names for other connected chains.
If a chain is created without any connections, it by default only connected to the main chain.
Chain Access Rules
The following is a list of data access rules that apply to contracts on private chains:
- Contracts can only read data on chain it is connected to. A chain is always connected to itself.
- Contract Accounts cannot modify the state of contracts on another chain.
Why Chains can't modify data on other chains?
Let's say chain A
has members X
, Y
, and Z
. And chain B
is connected to chain A
. So there is a directed arrow connection from chain B
to chain A
. Now since B
is connected to A
, its members can be a subset of the members of chain A
. So let chain B
have members X
and Y
. If a transaction on chain B
were allowed to change the state of chain A
, then member Z
would not know that the transaction exists, so from their point of view, the state of chain A
would change for no reason. When comparing states of chain A
between members X
and Z
, X
would have the changed state, and the Z
would have the original state, resulting in a state mismatch. Therefore to avoid this, STRATO does not allow cross-chain state changes.
Chain Membership Rules
- For a chain
A
with its set of all members beingM
andA
is connected to a set of other chainsC
, For all membersm
inM
, and chainsc
inC
,m
must be a member of a chainc
in order to be a member ofA
. All nodes are members of the Main Chain.
Chain Components
A chain has the following properties:
- Chain ID
- Label
- Members
- Parent Chain(s)
- Creator
Chain ID
The chain ID is a unique 64-character long hexadecimal number generated when the chain is created. It is the hash of the metadata used to create the chain. For example a chain ID might look like: 0x4574c8c75d6e88acd28f7e467dac97b5C60c3838d9dad993900bdf402152228e
. The chain ID is used within STRATO to reference contract accounts and other transactions that are a part of that private chain.
Label
The chain label is a user provided string used to identify it. Labels are useful when viewing different chains through a graphical user interface. Multiple chains may have the same label.
Members
Chain members are the STRATO nodes that are a part of this chain. A chain member can be either an (orgName
), or (orgName
,orgUnit
) or (orgName
, orgUnit
,commonName
). This makes adding a group of nodes from an organization extremely efficient and easy. A node's X.509 Certificate must have a matching identity (be in the defined group) with a chain's defined members. I.e. it must be part of the group that the chain member is representing to have access. A member of a chain is a STRATO node in the network that belongs to the defined group of identities listed in a chain's member list. If a user accesses STRATO from a node outside of a node from the members list, they will not have access.
As of STRATO 7.9, user's membership of a chain will not be recorded with their address and enode address.
More on X.509 Certificates can be found here.
Chain Member:
The combination of orgName
, orgUnit
and commonName
comes from a user's X509 certificate and acts as a unique identifier for a node.
Example Chain Members:
(orgName: Skynet
,orgUnit: Robotics
,commonName: The Terminator)
(orgName: Delos Inc.)
(orgName: Pied Piper
,orgUnit: Engineering)
User accounts have separate token balances on every chain. If gas in on in your network, make sure to grant each member enough tokens to make transactions. Members added at chain creation time can have a initial balance, however new members must be given a balance once they have been added to the chain.
Parent Chain(s)
The chains that this chain is connected to (see #chain-relationships). This allows smart contracts on this chain to read from the declared chains by their provided name.
Creator
The information about the user account that created this chain. This includes the account address, as well as the user's public key information.
Create a Private Chain
Note
User's must have a Verified Identity in order to create a private chain.
Create a new private chain with a provided governance contract.
A chain creation request body contains the following properties:
args
: object- Any constructor args to pass to the governance contract.
- An object containing key-value pairs for each argument.
members
: array- The initial groups of members of the chain.
- An array of objects, each object containing the (
orgName
,orgUnit
,commonName
,access
) fields of a chain member.
balances
: array- The token balances for each initial member of the chain.
- Only relevant when gas is enabled in your network, however the
balances
property must still be present in any request, regardless of gas being enabled or disabled.
contract
: string- The name of governance contract of this chain.
- Omitted when using a Code Pointer as the contract.
src
: string- Governance contract source code.
- Omitted when using a Code Pointer as the contract.
metadata
: object- Optional
- Metadata such as enabling contract history, or disabling contract indexing.
- Specify VM used for the Governance Contract.
- See API reference for more information about using metadata.
label
: string- The label for this chain.
parentChain
: string- Optional
- The chain ID of this chain's "parent" chain.
- Omitted when the parent chain is the Main Chain.
parentChains
: object- Optional
- An object with key/value pairs of connected chain names and their chain IDs. Chain names do not have to match the corresponding chain's
label
. The name is only used for referencing connected chains in smart contracts. - Omitted if this chain is only connected to the Main Chain.
codePtr
: object- Optional
- Contains the keys
account
andname
of a contract that will be used as the chain's governance contract. - Used in place of the
contract
andsrc
arguments. - See Code Pointers.
Endpoint
POST https://<strato_address>/bloc/v2.2/chain
Example
This example uses the Auto-Approve Governance Contract. Since the AutoApprove contract does not take any constructor args, the request args
are empty.
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"args": {},
"members":
[
{
"access":true,
"orgUnit":<orgUnit_1>,
"commonName":<commonName_1>,
"orgName": <orgName_1>
},
{
"access":true,
"orgUnit":<orgUnit_2>,
"commonName":<commonName_2>,
"orgName": <orgName_2>
}
],
"balances":
[
{
"address": <member_1_user_address>,
"balance": <member_1_balance>
},
{
"address": <member_2_user_address>,
"balance": <member_2_balance>
}
],
"contract": "AutoApprove",
"src": <governance-src>,
"metadata": {
"history": "AutoApprove",
"VM": "SolidVM"
},
"label": "MyPrivateChain",
"parentChains": {
"MyDapp": <DappChainID>,
"WeatherOracle": <WeatherOracleChainID>
}
}' \
"https://<strato_address>/bloc/v2.2/chain"
const contractSrc = await importer.combine("AutoApprove.sol") // import Governance Contract from file system
const chainInfo = {
label: "MyPrivateChain",
src: contractSrc,
args : {},
members : [
{
access: true,
orgUnit: orgUnit_1,
commonName: commonName_1,
orgName: orgName_1
},
{
access: true,
orgUnit: orgUnit_2,
commonName: commonName_2,
orgName: orgName_2
}
],
balances: [
{
address: member_1_user_address,
balance: member_1_balance
},
{
address: member_2_user_address,
balance: member_2_balance
},
],
parentChains: {
MyDapp: dappChainID,
WeatherOracle: weatherOracleChainID,
}
} // declare chain information
const contractInfo = {
name: "AutoApprove"
} // governance contract information
const chainId = await rest.createChain(stratoUser, chainInfo, contractInfo, options); // create the chain
Response
The chain ID of the newly created chain:
"4574c8c75d6e88acd28f7e467dac97b5C60c3838d9dad993900bdf402152228e"
Note
Hexadecimal values such as Chain ID and Address are returned from the STRATO API without the 0x
prefix. Similarly these values are stored in the STRATO databases without the prefix, so there is no need to append this prefix to API queries.
Using Existing Contract Code
STRATO allows governance contracts to use Code Pointers to reference existing governance contracts that have already been used to create other private chains. A code pointer acts as a pointer to the code that exists at the referenced address, not as a copy of code. Since addresses may contain more than one contract (the account holds a code collection), the name of the contract at the address must also be provided. This allows for faster, more consistent, and more reliable private chain creation. Since the governance contract code is already accessible to STRATO, it does not need to reparse or recompile any new contract code. It also eliminates the need for users to individually manage governance contracts, instead new chains can be created with a proven reliable code that has already been written and used. Governance contracts that use code pointers allow for more consistent namespacing in Cirrus when using Contract Versioning and X.509 Identity. This is due to the fact that the code used is an actual reference to an address, not a copy of the code with this user's details. The Cirrus table for a code pointer contract will maintain the same name (prefixing the creator's organization/group) it had as the original creator, ensuring that the organization and group of the original creator is kept intact. This would be beneficial in applications where a user must create private chains that are permissioned to different organization than their own. By knowing the pointer to an existing governance contract for that organization, it allows external party to create a private chain that belongs to that organization.
As a security measure, code pointers can only be used by user accounts that have access to the chain that the original governance contract is on. Furthermore, code pointers cannot be used to create any other type of contract except the governance contract.
To use a pointer to existing code, provide the Contract Account address and name in a codePtr
field of the chain creation payload. This is in place of the source
and contract
fields in the chain payload. Since code at an account may contain multiple contract definitions, the name must still be specified in the chain payload, as well as the codePtr
object. The code referenced may be any contract already uploaded as long as it is accessible from the newly created chain.
Example
In this example, let there be an existing governance contract at some address that is on the Main Chain.
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"args": {},
"members":
[
{
"access":true,
"orgUnit":<orgUnit_1>,
"commonName":<commonName_1>,
"orgName": <orgName_1>
},
{
"access":true,
"orgUnit":<orgUnit_2>,
"commonName":<commonName_2>,
"orgName": <orgName_2>
}
],
"balances":
[
{
"address": <member_1_user_address>,
"balance": <member_1_balance>
},
{
"address": <member_2_user_address>,
"balance": <member_2_balance>
}
],
"codePtr": {
"account": "<codePtr-address>",
"name": "<codePtr-contract-name>"
},
"parentChain": "<parentChainID>",
"metadata": {
"VM": "SolidVM"
},
"label": "MyPrivateChain"
}' \
"https://<strato_address>/bloc/v2.2/chain"
const chainInfo = {
label: "MyPrivateChain",
codePtr: {
account: `${codePtrAddress}`,
name: codePtrContractName
},
args : {},
members : [
{
access: true,
orgUnit: orgUnit_1,
commonName: commonName_1,
orgName: orgName_1
},
{
access: true,
orgUnit: orgUnit_2,
commonName: commonName_2,
orgName: orgName_2
}
],
balances: [
{
address: member_1_user_address,
balance: member_1_balance
},
{
address: member_2_user_address,
balance: member_2_balance
},
],
parentChain: parentChainId,
} // declare chain information
const contractInfo = {
name: codePtrContractName
} // governance contract information
const chainId = await rest.createChain(stratoUser, chainInfo, contractInfo, options); // create the chain
Access Chain Information
Get information about a specific chain or a number of chains.
Query parameters:
chainid
(optional)- The chain ID of the chain to get information about.
- To query for more than chain, use a new chainid parameter for each chain ID (demonstrated in example below.)
- If not specified, all chains this user has access to are returned.
Endpoint
GET https://<strato_address>/strato-api/eth/v1.2/chain"
Example
curl -X GET \
-H "Accept: application/json" \
-H "Authorization: Bearer <token>" \
"https://<strato_address>/strato-api/eth/v1.2/chain?chainid=4574c8c75d6e88acd28f7e467dac97b5C60c3838d9dad993900bdf402152228e&chainid=3c18ee0fbe01bdb9056cfabcebee1a413e7c33e0a0a1af41210f430a97028d2f"
const chainIds = ["4574c8c75d6e88acd28f7e467dac97b5C60c3838d9dad993900bdf402152228e", "3c18ee0fbe01bdb9056cfabcebee1a413e7c33e0a0a1af41210f430a97028d2f"]
const multipleChains = await rest.getChains(stratoUser, chainIds, options) // retrieve chain info
const chainId = "37dfbbbc20a3d0816a345cf1ac3aacf0b04ba66ed9ffa00c2bb68e4172cc950c"
const singleChain = await rest.getChain(stratoUser, chainId, options) // retrieve chain info
Response
[
{
"id": "4574c8c75d6e88acd28f7e467dac97b5C60c3838d9dad993900bdf402152228e",
"info": {
"signature": null,
"accountInfo": [
{
"balance": 123,
"address": "abcdef1234567",
}
],
"members": [
{
"access": true,
"orgUnit": "Artificial Intelligence",
"commonName": "The Terminator",
"orgName": "Skynet"
},
],
"metadata": {},
"creationBlock": "e8dd451531e2689273dafd7b1fb732fb7c509fbd0f570a2d3aece68820840853",
"codeInfo": [
{
"name": "AutoApprove",
"src": "<governance-src>",
"code": "<code-hash>"
}
],
"label": "MyPrivateChain",
"nonce": "0a2d3aece8dd451e68820840531ef92738853dafd7b19fbd0f57b732fb7c5026",
"parentChain": null,
"parentChains": {
"MyDapp": "3c18ee0fbe01bdb9056cfabcebee1a413e7c33e0a0a1af41210f430a97028d2f",
"WeatherOracle": "9056cfabcebee1a41c33e0a0a1af41210f433e73c18ee0fbe01bdb0a97028d2f",
}
}
},
{
"id": "3c18ee0fbe01bdb9056cfabcebee1a413e7c33e0a0a1af41210f430a97028d2f",
"info": {
...,
"label": "MySecondPrivateChain",
"members": [...],
"accountInfo": [...],
"nonce": "456...",
"codeInfo": [...],
"parentChain": null,
"parentChains": {...}
}
}
]
Private Transactions
To execute a transaction on a private chain, use the normal transaction payload syntax and add the chain ID in a chainid
query parameter or as the chainid
property in a transaction payload.
We use the following SimpleStorage
contract as the contract we are calling functions on:
contract SimpleStorage {
uint x;
constructor(uint _x) {
x = _x;
}
function set(uint _x) returns (uint) {
x = _x;
return x;
}
}
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"txs": [
{
"payload": {
"contractName": "SimpleStorage",
"contractAddress": "90f0a711cd0ab921bbc9b54c4a6bdb3219e590f9",
"method": "set",
"args": {
"_x": 5
},
"chainid": <chain-id>
},
"type": "FUNCTION"
}
],
"txParams": {
"gasLimit": 32100000000,
"gasPrice": 1
}
}' \
"https://<strato_address>/strato/v2.3/transaction?resolve=true"
Alternatively using the query parameter:
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"txs": [
{
"payload": {
"contractName": "SimpleStorage",
"contractAddress": "90f0a711cd0ab921bbc9b54c4a6bdb3219e590f9",
"method": "set",
"args": {
"_x": 5
}
},
"type": "FUNCTION"
}
],
"txParams": {
"gasLimit": 32100000000,
"gasPrice": 1
}
}' \
"https://<strato_address>/strato/v2.3/transaction?resolve=true?chainid=<chain-id>"
Returns
[
{
"status": "Success",
"hash": "936f9af8004c11edea1f29edd4f71c4b99c3e653c6a06f5c90687b9d7341e6c1",
"txResult": {
"deletedStorage": "",
"status": "success",
"contractsDeleted": "",
"gasUsed": "00000000000000000000000000000000000000000000000000000007794f2100",
"stateDiff": "",
"time": 0.0005209,
"kind": "SolidVM",
"chainId": "a6bff1b5361ebd13d7dd90b29f03554971128ab16d51b7fce60144601419f80b",
"response": "",
"blockHash": "c8a61f221886ac2f721a2467257ef11666812de5a3ef6037a9fe4465f97c585e",
"transactionHash": "6c55e2b27ba0d88cda3c4236deb616f8f8a8bbb77932f45e248200997f346a01",
"etherUsed": "00000000000000000000000000000000000000000000000000000007794f2100",
"newStorage": "",
"message": "Success!",
"trace": "",
"contractsCreated": ""
},
"data": {
"tag": "Call",
"contents": [
"7"
]
}
}
]
const transaction = {
contract: {
name: "SimpleStorage",
address: contractAddress,
},
method: "set",
args: {
_x: 5
},
chainid: chainId,
};
const result = await rest.callList(stratoUser, [transaction], options)
Returns
A list of each transactions' return values:
[
[
"7"
]
]
Reading Data from Connected Chains
In this example, we will demonstrate how smart contracts on private chains can read data from any of their named connected chains.
The following "ChainData
" contract would be posted on some private chain "A":
contract ChainData {
uint x;
constructor(uint _x) {
x = _x;
}
function getDoubleX() returns (uint) {
return x * 2;
}
function setX(uint _x) returns (uint) {
x = _x;
return x;
}
}
Then, on another chain connected to chain A, we could write the following contract to read the data from the "ChainData" contract on Chain "A":
contract ReadChainDataExample {
address chainDataContract;
uint myState;
constructor(address _chainDataContract) {
chainDataContract = _chainDataContract;
myState = 0;
}
//This function is allowed because it only reads data from chain A
function allowedReadData() {
ChainData cd = ChainData(account("A", chainDataContract));
myState = cd.getDoubleX();
}
//This function is NOT allowed because it sets the state on chain A
function disallowedWriteData() {
ChainData cd = ChainData(account("A", chainDataContract));
cd.setX(myState);
}
}
Governance
When a Private Chain is created, it is necessary to provide method through which to add or remove members. This is referred to as the Governance of a private chain.
Note
Network Consensus/Governance, and Private Chain Governance are completely separate topics and should not be confused. Network Governance is the method through which new nodes join or leave a network, and the management of Validator Nodes. These operations are done outside of smart contracts. Private Chain governance is the management of the members of a private chain. All operations occur within a smart contract.
Governance Contract
The smart contract that provides the rules of governance for a private chain is known as the Governance Contract. This is the first or "base" contract of the Private Chain. The Governance contract must be sent with the payload of a chain creation. The Governance Contract always resides at the Governance Address, which is constant across all chains.
The Governance Address is 0000000000000000000000000000000000000100
or simply 0x100
.
Governance Methods
The method of governance is defined in the logic of the Governance Contract.
Members can be added and removed using their X.509 Verified Identities which contain fields for an organization's name, unit, and common name.
To add or remove a member, use the following Solidity Events from within the Governance Contract:
event OrgAdded(string orgName);
event OrgRemoved(string orgName);
event OrgUnitAdded(string orgName, string orgUnit);
event OrgUnitRemoved(string orgName, string orgUnit);
event CommonNameAdded(string orgName, string orgUnit, string commonName);
event CommonNameRemoved(string orgName, string orgUnit, string commonName);
Each event adds/removes member nodes with increasing levels of specificity. The Org
events will manage all members that have identities within that Organization, regardless of other identity credentials like Organizational Unit or Common Name. This event type is typically used the most often. OrgUnit
events will manage all members that are in the specified organization and organizational unit, regardless of their Common Name. Lastly, the CommonName
events will only manage specific members that have exact matching credentials according to the provided arguments. This event type is rarely used since applications rarely need to know about the identity of a specific node.
These event names are reserved within STRATO to add/remove the listed member when the event is emitted from the Governance Contract. STRATO will not add/remove members when an event is emitted from a non-Governance Contract.
The governance algorithm used is heavily dependent on the required use case, however we provide some common ways in which governance may be administered. The Auto-Approve, Majority Rules, Two-In, and Admin Approval governance methods are shipped with STRATO and can be easily accessed from the STRATO Management Dashboard when creating a new chain.
Note
STRATO 7.9 fixed an issue where members could not be added or removed from private chains in their constructors.
Governance Contract Constructors
The constructor method behaves differently for EVM and SolidVM.
Within EVM, any arguments passed in the args
object will set the respective state variables of the same name to those values. If a variable has not been declared as a state variable, its value will be ignored. Any logic within the constructor is ignored. For this reason constructors in governance contracts are not needed when using the EVM.
For SolidVM, the contract constructor behaves normally with all internal logic being executed. Arguments passed in via the args
object will be given to the constructor as parameters.
Auto-Approve
The Auto-Approve method of governance will unconditionally add/remove a member when the respective functions are called.
contract AutoApprove {
event OrgAdded(string orgName);
event OrgRemoved(string orgName);
function voteToAdd(string o) {
emit OrgAdded(o);
}
function voteToRemove(string o) {
emit OrgRemoved(o);
}
}
Majority Rules
The Majority Rules method of governance requires that a majority of members must issue a vote for a member to be successfully added/removed. This is very similar to how STRATO's PBFT consensus algorithm and Validator Node voting already operates. Rather than relying on a ⅔ majority, this method only requires a ½ majority. This contract (and other contracts that rely on the existing members of the chain) manually tracks its own members so that it can be referenced within the proper functions.
contract MajorityRules {
event OrgAdded(string orgName);
event OrgRemoved(string orgName);
mapping(chainMember => uint) addVotes;
mapping(chainMember => uint) removeVotes;
struct chainMember {
string o
}
chainMember[] __members__;
function voteToAdd(string o) {
m = chainMember(o);
uint votes = addVotes[m] + 1;
uint mlen = __members__.length;
if (votes > mlen / 2) {
addVotes[m] = 0;
bool found = false;
for (uint i = 0; i < mlen; i++) {
if (__members__[i] == m) {
found = true;
i = mlen;
}
}
if (!found) {
__members__.push(m);
emit OrgAdded(o);
}
}
else {
addVotes[m] = votes;
}
}
function voteToRemove(string o) {
m = chainMember(o);
uint votes = removeVotes[m] + 1;
uint mlen = __members__.length;
if (votes > mlen / 2) {
removeVotes[m] = 0;
for (uint i = 0; i < mlen; i++) {
if (__members__[i] == m) {
__members__[i] = __members__[mlen - 1];
delete __members__[mlen - 1];
__members__.length--;
emit OrganizationRemoved(o);
i = mlen;
}
}
}
else {
removeVotes[m] = votes;
}
}
}
Two-In
The Two-In method of governance only requires that two members issue a vote to sucessfully add/remove a member.
contract TwoIn {
event OrgAdded(string orgName);
event OrgRemoved(string orgName);
mapping(chainMember => uint) addVotes;
mapping(chainMember => uint) removeVotes;
struct chainMember {
string o
}
function voteToAdd(string o) {
m = chainMember(o);
uint votes = addVotes[m] + 1;
if (votes >= 2) {
emit OrgAdded(o);
addVotes[m] = 0;
}
else {
addVotes[m] = votes;
}
}
function voteToRemove(string o) {
uint votes = removeVotes[m] + 1;
if (votes >= 2) {
emit OrgRemoved(o);
removeVotes[m] = 0;
}
else {
removeVotes[m] = votes;
}
}
}
Admin Approval
The Admin Approval method of governance only allows an admin to add or remove members. The admin's address is provided at the creation of the private chain.
contract AdminOnly {
event OrgAdded(string orgName);
event OrgRemoved(string orgName);
struct chainMember {
string o
}
chainMember admin;
constructor(chainMember _admin) {
admin = _admin;
}
function voteToAdd(string o) {
require(msg.sender == admin, "You do not have permission to vote");
emit OrAdded(o);
}
function voteToRemove(string o) {
require(msg.sender == admin, "You do not have permission to vote");
emit OrRemoved(o);
}
}
These are just a few examples of possible governance algorithms. The power of Solidity smart contracts allow for robust and complex voting algorithms.
Here are some more ideas for Governance Methods to inspire developers to create their own:
- Users may create a request to join a private chain via a smart contract deployed to the main chain. Private Chain members check for existing requests and approve or deny each one on a per-request basis.