logo

Creation of a Side Chain

Side chains can be created in the aelf ecosystem to enable scalability. This section introduces the process in detail.

Side Chain Creation API#

Anyone can request the creation of a side chain in the aelf ecosystem. The proposer/creator of a new side chain needs to request the creation through the cross-chain contract on the main chain. The request contains fields that determine the type of side chain to be created.

API for Proposing Side Chain Creation#

The fields in the SideChainCreationRequest determine the type of side chain that is created. For more details, follow RequestSideChainCreation in the Crosschain contract documentation.

Upon creating a new proposal for the side chain, the ProposalCreated event containing the proposal ID will be fired. A parliament organization, specified since the chain's launch, will approve this proposal within 24 hours (refer to the Parliament contract documentation for details). The proposer can release the side chain creation request with the proposal ID once it can be released. Refer to ReleaseSideChainCreation in the Crosschain contract documentation.

Once the side chain is created, the SideChainCreatedEvent containing the chain ID will be fired.

The side chain node can be launched once it is created on the main chain. Ensure the side chain ID from the creation result is configured correctly before launching the side chain node. Make sure the cross-chain communication context is correctly set, as the side chain node will request main chain node for chain initialization data. For more details, check the side chain node running tutorial.

Side Chain Types#

Two types of side chains currently exist: exclusive and shared. An exclusive side chain allows developers to choose the transaction fee model and set the transaction fee price. Only the creator of an exclusive side chain can propose deploying a new contract.

Paying for Side Chain#

Indexing Fee#

The indexing fee is paid for side chain indexing. You can specify the indexing fee price and prepayments amount when requesting side chain creation. The cross-chain contract charges prepayments once the side chain is created and pays the miner who indexes the side chain block every time.

Resource Fee#

Developers of an exclusive side chain pay producers for running it by paying CPU, RAM, DISK, and NET resource tokens. This model is called charge-by-time. The amount the side chain creator must share with the producers is set after the chain's creation. The exclusive side chain is priced according to the time used. The unit price of the fee is determined through negotiation between the production node and the developer.

Simple Demo for Side Chain Creation Request#

When a user (usually a developer) feels the need to create a new side chain on aelf, they must call the cross-chain contract and request a side chain creation. After the request, parliament organization members will either approve or reject the creation. If the request is approved, the developer must then release the proposal.

Step-by-Step Code Snippets#

We'll use the aelf-js-sdk to create a new side chain. The full script will be provided at the end.

This creation of a side chain (logical, on-chain creation) is done in four steps:

  • The developer must allow/approve some tokens to the cross-chain contract of the main chain.
  • The developer calls the cross-chain contract of the main chain to request the creation.
  • The parliament organization members must approve this request.
  • Finally, the developer must release the request to finalize the creation.
  • Set-Up#

    To test the creation process, you will need a producer node running and the following:

  • A key-pair (account) created; this will be your Producer (also used to create the creation request in this tutorial).
  • The node needs to be configured with an API endpoint, account, and miner list that correspond to what is in the script.
  • Here is the initialization code:

    1
    const AElf = require('aelf-sdk');
    2
    const Wallet = AElf.wallet;
    3
    4
    const { sha256 } = AElf.utils;
    5
    6
    // set the private key of the block producer.
    7
    // REPLACE
    8
    const defaultPrivateKey = 'e119487fea0658badc42f089fbaa56de23d8c0e8d999c5f76ac12ad8ae897d76';
    9
    const defaultPrivateKeyAddress = 'HEtBQStfqu53cHVC3PxJU6iGP3RGxiNUfQGvAPTjfrF3ZWH3U';
    10
    11
    // load the wallet associated with your block producer's account.
    12
    const wallet = Wallet.getWalletByPrivateKey(defaultPrivateKey);
    13
    14
    // API link to the node
    15
    // REPLACE
    16
    const aelf = new AElf(new AElf.providers.HttpProvider('http://127.0.0.1:1234'));
    17
    18
    // names of the contracts that will be used.
    19
    const tokenContractName = 'AElf.ContractNames.Token';
    20
    const parliamentContractName = 'AElf.ContractNames.Parliament';
    21
    const crossChainContractName = 'AElf.ContractNames.CrossChain';
    22
    23
    ...
    24
    25
    const createSideChain = async () => {
    26
    // check the chain status to make sure the node is running
    27
    const chainStatus = await aelf.chain.getChainStatus({sync: true});
    28
    const genesisContract = await aelf.chain.contractAt(chainStatus.GenesisContractAddress, wallet)
    29
    .catch((err) => {
    30
    console.log(err);
    31
    });
    32
    33
    // get the addresses of the contracts that we'll need to call
    34
    const tokenContractAddress = await genesisContract.GetContractAddressByName.call(sha256(tokenContractName));
    35
    const parliamentContractAddress = await genesisContract.GetContractAddressByName.call(sha256(parliamentContractName));
    36
    const crossChainContractAddress = await genesisContract.GetContractAddressByName.call(sha256(crossChainContractName));
    37
    38
    // build the aelf-sdk contract instance objects
    39
    const parliamentContract = await aelf.chain.contractAt(parliamentContractAddress, wallet);
    40
    const tokenContract = await aelf.chain.contractAt(tokenContractAddress, wallet);
    41
    const crossChainContract = await aelf.chain.contractAt(crossChainContractAddress, wallet);
    42
    43
    ...
    44
    }

    When running the script, the createSideChain function will be executed and will run through the full process of creating the side chain.

    Creation of the Side Chain#

    Set the Allowance

    First, the developer must approve some ELF tokens for use by the cross-chain contract.

    1
    var setAllowance = async function (tokenContract, crossChainContractAddress) {
    2
    // set some allowance to the cross-chain contract
    3
    const approvalResult = await tokenContract.Approve({
    4
    symbol: "ELF",
    5
    spender: crossChainContractAddress,
    6
    amount: 20000,
    7
    });
    8
    9
    let approveTransactionResult = await pollMining(approvalResult.TransactionId);
    10
    };

    Creation Request

    To request a side chain creation, the developer must call RequestSideChainCreation on the cross-chain contract. This creates a proposal with the Parliament contract. After calling this method, a ProposalCreated log will be created containing the ProposalId.

    1
    rpc RequestSideChainCreation(SideChainCreationRequest) returns (google.protobuf.Empty){}
    2
    3
    message SideChainCreationRequest {
    4
    int64 indexing_price = 1; // The cross chain indexing price.
    5
    int64 locked_token_amount = 2; // Initial locked balance for a new side chain.
    6
    bool is_privilege_preserved = 3; // Creator privilege boolean flag.
    7
    SideChainTokenCreationRequest side_chain_token_creation_request = 4; // Side chain token information.
    8
    repeated SideChainTokenInitialIssue side_chain_token_initial_issue_list = 5; // A list of accounts and amounts that will be issued when the chain starts.
    9
    map<string, int32> initial_resource_amount = 6; // The initial rent resources.
    10
    }
    11
    12
    message SideChainTokenCreationRequest{
    13
    string side_chain_token_symbol = 1; // Token symbol of the side chain to be created.
    14
    string side_chain_token_name = 2; // Token name of the side chain to be created.
    15
    int64 side_chain_token_total_supply = 3; // Token total supply of the side chain to be created.
    16
    int32 side_chain_token_decimals = 4; // Token decimals of the side chain to be created.
    17
    }
    18
    19
    message SideChainTokenInitialIssue{
    20
    aelf.Address address = 1; // The account that will be issued.
    21
    int64 amount = 2; // The amount that will be issued.
    22
    }

    In order for the creation request to succeed, some assertions must pass:

  • The Sender can only have one pending request at any time.
  • The locked_token_amount cannot be lower than the indexing price.
  • If is_privilege_preserved is true (exclusive side chain), the token initial issue list cannot be empty and all with an amount greater than 0.
  • If is_privilege_preserved is true (exclusive side chain), the initial_resource_amount must contain all resource tokens of the chain, and the value must be greater than 0.
  • The allowance approved to cross-chain contract from the proposer (Sender of the transaction) cannot be lower than the locked_token_amount.
  • No need to provide data about side chain token if is_privilege_preserved is false.
  • 1
    var sideChainCreationRequest = async function (crossChainContract) {
    2
    // call the cross-chain contract to request the creation
    3
    const creationRequestResult =
    4
    await crossChainContract.RequestSideChainCreation({
    5
    indexing_price: 1,
    6
    locked_token_amount: 20000,
    7
    is_privilege_preserved: true,
    8
    side_chain_token_creation_request: {
    9
    side_chain_token_symbol: "MEGA",
    10
    side_chain_token_name: "MEGA",
    11
    side_chain_token_total_supply: 100000000,
    12
    side_chain_token_decimals: 8,
    13
    },
    14
    side_chain_token_initial_issue_list: [
    15
    {
    16
    address: defaultPrivateKeyAddress,
    17
    amount: 10000000,
    18
    },
    19
    ],
    20
    initial_resource_amount: {
    21
    CPU: 100,
    22
    RAM: 100,
    23
    DISK: 100,
    24
    NET: 100,
    25
    },
    26
    });
    27
    28
    let sideChainProposalResult = await pollMining(
    29
    creationRequestResult.TransactionId
    30
    );
    31
    let logs = parseLogs(sideChainProposalResult.Logs);
    32
    let proposalId = logs.ProposalId;
    33
    };

    The cross-chain contract emits an event containing the ProposalId. This is needed for the last step.

    Approve the Proposal

    This is where the parliament organization members approve the proposal:

    1
    var proposalApproveTx = await parliamentContract.Approve(deserializedLogs[0].proposalId);
    2
    3
    await pollMining(proposalApproveTx.TransactionId);

    Note: when calling Approve it will be the Sender of the transaction that approves. Here the script is set to use the key of one parliament organization member, see full script at the end.

    Release the Proposal

    This part of the script releases the proposal:

    1
    var releaseResult = await crossChainContract.ReleaseSideChainCreation({
    2
    proposalId: deserializedLogs[0].proposalId
    3
    });
    4
    5
    let releaseTxResult = await pollMining(releaseResult.TransactionId);
    6
    7
    // Parse the logs to get the chain id.
    8
    let sideChainCreationEvent = crossChainContract.deserializeLog(releaseTxResult.Logs, 'SideChainCreatedEvent');

    This is the last step involved in creating a side chain, after this the chain id of the new side chain is accessible in the SideChainCreatedEvent event log.

    Complete Script#

    This script demonstrates the essential steps to create a side chain in the aelf ecosystem. The developer must approve some ELF tokens, request the side chain creation, get approval from the parliament organization, and finally release the proposal to create the side chain. Ensure to set the proper configurations and values as per your blockchain environment.

    1
    const AElf = require("aelf-sdk");
    2
    const Wallet = AElf.wallet;
    3
    4
    const { sha256 } = AElf.utils;
    5
    6
    // set the private key of the block producer
    7
    const defaultPrivateKey =
    8
    "e119487fea0658badc42f089fbaa56de23d8c0e8d999c5f76ac12ad8ae897d76";
    9
    const defaultPrivateKeyAddress =
    10
    "HEtBQStfqu53cHVC3PxJU6iGP3RGxiNUfQGvAPTjfrF3ZWH3U";
    11
    12
    const wallet = Wallet.getWalletByPrivateKey(defaultPrivateKey);
    13
    14
    // link to the node
    15
    const aelf = new AElf(new AElf.providers.HttpProvider("http://127.0.0.1:8000"));
    16
    17
    if (!aelf.isConnected()) {
    18
    console.log("Could not connect to the node.");
    19
    }
    20
    21
    const tokenContractName = "AElf.ContractNames.Token";
    22
    const parliamentContractName = "AElf.ContractNames.Parliament";
    23
    const crossChainContractName = "AElf.ContractNames.CrossChain";
    24
    25
    var pollMining = async function (transactionId) {
    26
    console.log(`>> Waiting for ${transactionId} the transaction to be mined.`);
    27
    28
    for (i = 0; i < 10; i++) {
    29
    const currentResult = await aelf.chain.getTxResult(transactionId);
    30
    // console.log('transaction status: ' + currentResult.Status);
    31
    32
    if (currentResult.Status === "MINED") return currentResult;
    33
    34
    await new Promise((resolve) => setTimeout(resolve, 2000)).catch(
    35
    function () {
    36
    console.log("Promise Rejected");
    37
    }
    38
    );
    39
    }
    40
    };
    41
    42
    var setAllowance = async function (tokenContract, crossChainContractAddress) {
    43
    console.log("\n>>>> Setting allowance for the cross-chain contract.");
    44
    45
    // set some allowance to the cross-chain contract
    46
    const approvalResult = await tokenContract.Approve({
    47
    symbol: "ELF",
    48
    spender: crossChainContractAddress,
    49
    amount: 20000,
    50
    });
    51
    52
    await pollMining(approvalResult.TransactionId);
    53
    };
    54
    55
    var checkAllowance = async function (tokenContract, owner, spender) {
    56
    console.log("\n>>>> Checking the cross-chain contract's allowance");
    57
    58
    const checkAllowanceTx = await tokenContract.GetAllowance.call({
    59
    symbol: "ELF",
    60
    owner: owner,
    61
    spender: spender,
    62
    });
    63
    64
    console.log(
    65
    `>> allowance to the cross-chain contract: ${checkAllowanceTx.allowance} ${checkAllowanceTx.symbol}`
    66
    );
    67
    };
    68
    69
    const createSideChain = async () => {
    70
    // get the status of the chain in order to get the genesis contract address
    71
    console.log("Starting side chain creation script\n");
    72
    73
    const chainStatus = await aelf.chain.getChainStatus({ sync: true });
    74
    const genesisContract = await aelf.chain
    75
    .contractAt(chainStatus.GenesisContractAddress, wallet)
    76
    .catch((err) => {
    77
    console.log(err);
    78
    });
    79
    80
    // get the addresses of the contracts that we'll need to call
    81
    const tokenContractAddress =
    82
    await genesisContract.GetContractAddressByName.call(
    83
    sha256(tokenContractName)
    84
    );
    85
    const parliamentContractAddress =
    86
    await genesisContract.GetContractAddressByName.call(
    87
    sha256(parliamentContractName)
    88
    );
    89
    const crossChainContractAddress =
    90
    await genesisContract.GetContractAddressByName.call(
    91
    sha256(crossChainContractName)
    92
    );
    93
    94
    // build the aelf-sdk contract object
    95
    const parliamentContract = await aelf.chain.contractAt(
    96
    parliamentContractAddress,
    97
    wallet
    98
    );
    99
    const tokenContract = await aelf.chain.contractAt(
    100
    tokenContractAddress,
    101
    wallet
    102
    );
    103
    const crossChainContract = await aelf.chain.contractAt(
    104
    crossChainContractAddress,
    105
    wallet
    106
    );
    107
    108
    // 1. set and check the allowance, spender is the cross-chain contract
    109
    await setAllowance(tokenContract, crossChainContractAddress);
    110
    await checkAllowance(
    111
    tokenContract,
    112
    defaultPrivateKeyAddress,
    113
    crossChainContractAddress
    114
    );
    115
    116
    // 2. request the creation of the side chain with the cross=chain contract
    117
    console.log("\n>>>> Requesting the side chain creation.");
    118
    const sideChainCreationRequestTx =
    119
    await crossChainContract.RequestSideChainCreation({
    120
    indexingPrice: 1,
    121
    lockedTokenAmount: "20000",
    122
    isPrivilegePreserved: true,
    123
    sideChainTokenCreationRequest: {
    124
    sideChainTokenDecimals: 8,
    125
    sideChainTokenName: "SCATokenName",
    126
    sideChainTokenSymbol: "SCA",
    127
    sideChainTokenTotalSupply: "100000000000000000",
    128
    },
    129
    sideChainTokenInitialIssueList: [
    130
    {
    131
    address: "28Y8JA1i2cN6oHvdv7EraXJr9a1gY6D1PpJXw9QtRMRwKcBQMK",
    132
    amount: "1000000000000000",
    133
    },
    134
    ],
    135
    initialResourceAmount: { CPU: 2, RAM: 4, DISK: 512, NET: 1024 },
    136
    });
    137
    138
    let sideChainCreationRequestTxResult = await pollMining(
    139
    sideChainCreationRequestTx.TransactionId
    140
    );
    141
    142
    // deserialize the log to get the proposal's ID.
    143
    let deserializedLogs = parliamentContract.deserializeLog(
    144
    sideChainCreationRequestTxResult.Logs,
    145
    "ProposalCreated"
    146
    );
    147
    console.log(
    148
    `>> side chain creation request proposal id ${JSON.stringify(
    149
    deserializedLogs[0].proposalId
    150
    )}`
    151
    );
    152
    153
    // 3. Approve the proposal
    154
    console.log("\n>>>> Approving the proposal.");
    155
    156
    var proposalApproveTx = await parliamentContract.Approve(
    157
    deserializedLogs[0].proposalId
    158
    );
    159
    await pollMining(proposalApproveTx.TransactionId);
    160
    161
    // 3. Release the side chain
    162
    console.log("\n>>>> Release the side chain.");
    163
    164
    var releaseResult = await crossChainContract.ReleaseSideChainCreation({
    165
    proposalId: deserializedLogs[0].proposalId,
    166
    });
    167
    168
    let releaseTxResult = await pollMining(releaseResult.TransactionId);
    169
    170
    // Parse the logs to get the chain id.
    171
    let sideChainCreationEvent = crossChainContract.deserializeLog(
    172
    releaseTxResult.Logs,
    173
    "SideChainCreatedEvent"
    174
    );
    175
    console.log("Chain chain created : ");
    176
    console.log(sideChainCreationEvent);
    177
    };
    178
    179
    createSideChain().then(() => {
    180
    console.log("Done.");
    181
    });

    Note: Replace the placeholders in the script with actual values and logic for your use case.

    Next, we can move on to Running a Side Chain.

    Edited on: 17 July 2024 03:24:14 GMT+0