diff --git a/docs-sphinx/index.rst b/docs-sphinx/index.rst index 19d912b311..892183435b 100644 --- a/docs-sphinx/index.rst +++ b/docs-sphinx/index.rst @@ -55,6 +55,7 @@ Welcome to AElf's official documentation! Smart Contract API Acs Introduction Command-line Interface + Smart Contract .. toctree:: diff --git a/docs-sphinx/reference/smart-contract/consensus-contract.md b/docs-sphinx/reference/smart-contract/consensus-contract.md new file mode 100644 index 0000000000..f666820d33 --- /dev/null +++ b/docs-sphinx/reference/smart-contract/consensus-contract.md @@ -0,0 +1,535 @@ +# Consensus Contract + +## Overview + +Among numerous consensus contracts, AElf has chosen DPoS consensus, +which means that aelf's blocks will be packaged and broadcasted by a specific subset of nodes, +which need to be determined through elections. + +The introduction to the election section will be presented in another article. + +In this article, we will discuss: + +- Problems solved by blockchain consensus mechanism +- How AEDPoS Contract implemented +- What is the Last Irreversible Block +- Consensus scheduling in aelf nodes + +## Problems solved by blockchain consensus mechanism + +Briefly, the blockchain consensus module answers five questions: +1. When will a blockchain node produce blocks? +2. What information should be added to the block header for nodes that can produce blocks? +3. What transactions should be added to the block by nodes that can produce blocks? +4. When a node receives a block, +what consensus information in the block header can be considered compliant? +5. When a node receives a block and the transactions in the block are executed, +what kind of compliance should the state of the consensus contract change to? + +## How AEDPoS Contract implemented + +The five questions raised in the previous section are all answered by AEDPoS Contract via implementing interfaces defined in [acs4](https://docs.aelf.io/en/latest/reference/acs/acs4.html). + +### GetConsensusCommand + +The GetConsensusCommand interface is used to obtain information such as the time of the next production block for a public key. + +In the implementation of AEDPoS, the input is only a public key, and the calling time of the interface implementation method is also used as a reference (which is actually an important input). +In the AElf blockchain, when the system calls a read-only transaction internally, the context of contract execution is self-constructed. The calling time is generated by `DateTime.UtcNow` in C #'s built-in function library, +which is then converted into the timestamp data type `Timestamp` provided by protobuf and passed into the context of contract execution. + +```protobuf +rpc GetConsensusCommand (google.protobuf.BytesValue) returns (ConsensusCommand) { + option (aelf.is_view) = true; +} + +// ... + +message ConsensusCommand { + // Time limit of mining next block. + int32 limit_milliseconds_of_mining_block = 1; + // Context of Hint is diverse according to the consensus protocol we choose, so we use bytes. + bytes hint = 2; + // The time of arrange mining. + google.protobuf.Timestamp arranged_mining_time = 3; + // The expiration time of mining. + google.protobuf.Timestamp mining_due_time = 4; +} +``` + +In fact, regardless of whether the transaction to be executed is a read-only transaction, the timestamp passed in by the current contract execution context can be obtained through Context. +CurrentBlockTime in the contract code. + +This section mainly explains how AEDPoS consensus implements GetConsensusCommand. +Before that, I will briefly introduce the process of AEDPoS to readers who do not understand AElf consensus. +Or you can refer to [consensus protocol](../../protocol/consensus.md) to get more information. + +#### AEDPoS Process + +Assuming that the aelf main chain now elects 17 nodes through voting, we will call them BPs (Block Producers). + +These BPs are obtained by directly selecting the top 17 candidates at a certain block height (or time point) through a national election. +Each time the top 17 candidates are recounted and the BPs are reappointed, it is called a **Term**. + +In each session, all BPs produce blocks in rounds. Each round has 17 + 1 time slots, and each BP randomly occupies one of the first 17 time slots. The last time slot is produced by the producer of the extra block in this round. The producer of the extra block will initialize the information of the next round based on the random number published by each BP in this round. After 18 time slots, the next round begins. This cycle continues. + +The data structure of Round is as follows: + +```protobuf +message Round { + // The round number. + int64 round_number = 1; + // Current miner information, miner public key -> miner information. + map real_time_miners_information = 2; + // The round number on the main chain + int64 main_chain_miners_round_number = 3; + // The time from chain start to current round (seconds). + int64 blockchain_age = 4; + // The miner public key that produced the extra block in the previous round. + string extra_block_producer_of_previous_round = 5; + // The current term number. + int64 term_number = 6; + // The height of the confirmed irreversible block. + int64 confirmed_irreversible_block_height = 7; + // The round number of the confirmed irreversible block. + int64 confirmed_irreversible_block_round_number = 8; + // Is miner list different from the the miner list in the previous round. + bool is_miner_list_just_changed = 9; + // The round id, calculated by summing block producers’ expecting time (second). + int64 round_id_for_validation = 10; +} + +message MinerInRound { + // The order of the miner producing block. + int32 order = 1; + // Is extra block producer in the current round. + bool is_extra_block_producer = 2; + // Generated by secret sharing and used for validation between miner. + aelf.Hash in_value = 3; + // Calculated from current in value. + aelf.Hash out_value = 4; + // Calculated from current in value and signatures of previous round. + aelf.Hash signature = 5; + // The expected mining time. + google.protobuf.Timestamp expected_mining_time = 6; + // The amount of produced blocks. + int64 produced_blocks = 7; + // The amount of missed time slots. + int64 missed_time_slots = 8; + // The public key of this miner. + string pubkey = 9; + // The InValue of the previous round. + aelf.Hash previous_in_value = 10; + // The supposed order of mining for the next round. + int32 supposed_order_of_next_round = 11; + // The final order of mining for the next round. + int32 final_order_of_next_round = 12; + // The actual mining time, miners must fill actual mining time when they do the mining. + repeated google.protobuf.Timestamp actual_mining_times = 13; + // The encrypted pieces of InValue. + map encrypted_pieces = 14; + // The decrypted pieces of InValue. + map decrypted_pieces = 15; + // The amount of produced tiny blocks. + int64 produced_tiny_blocks = 16; + // The irreversible block height that current miner recorded. + int64 implied_irreversible_block_height = 17; +} +``` + +In the AEDPoS contract, there is a map structure, the key is a long type of RoundNumber, which is incremented from 1, and the value is the above-mentioned Round structure. +Each block generated by BP will update the information of the current or next round to promote consensus and block production, +and provide a basic basis for consensus verification. + +#### Consensus Command +```protobuf +message ConsensusCommand { + // Time limit of mining next block. + int32 limit_milliseconds_of_mining_block = 1; + // Context of Hint is diverse according to the consensus protocol we choose, so we use bytes. + bytes hint = 2; + // The time of arrange mining. + google.protobuf.Timestamp arranged_mining_time = 3; + // The expiration time of mining. + google.protobuf.Timestamp mining_due_time = 4; +} +``` + +In the implementation of AEDPoS consensus, the Hint field provides a clear path for BP to produce the next type of block. +We provide a dedicated data structure for Hint, AElfConsensusHint. + +```protobuf +message AElfConsensusHint { + // The behaviour of consensus. + AElfConsensusBehaviour behaviour = 1; + // The round id. + int64 round_id = 2; + // The previous round id. + int64 previous_round_id = 3; +} +``` + +The block type is included in the following AElfConsensusBehaviour: + +```protobuf +enum AElfConsensusBehaviour { + UPDATE_VALUE = 0; + NEXT_ROUND = 1; + NEXT_TERM = 2; + NOTHING = 3; + TINY_BLOCK = 4; +} +``` + +**UpdateValue** and **UpdateValue(WithoutPreviousInValue)** represents that the BP wants to produce an ordinary block in a certain round (understood in conjunction with the consensus module in the aelf white paper or the aelf system introduction). + +In these two behaviors, the consensus information that the BP focuses on updating includes the `in_value` (`previous_in_value`) of its previous round, the out_value generated in this round, and the password fragments of the in_value used to generate out_value in this round. + +(The BP will encrypt 16 password fragments with the public keys of in_value and other BPs, and other BPs can only decrypt them with their own private keys. When the decrypted fragments reach a certain number, the original in_value will be revealed. This is an application of shamir's secret sharing.) + +In addition, a timestamp that actually triggers block production behavior is added to the `actual_mining_times`. UpdateValueWithoutPreviousInValue The only difference between UpdateValue and the BP is that there is no need to publish the in_value (previous_in_value) of the previous round, because the current round is the first round or has just changed (and the BP is a + +**NextRound** represents that the BP is an additional block producer for this round (or remedier - when the designated additional block producer is absent), and needs to initialize the next round of information. +The next round of information includes the time slot arrangement of each BP and the additional block producer specified according to the rules for the next round. + +**NextTerm** is similar to **NextRound**, except that it will recount the top 17 candidates in the election and initialize the next round of information based on the new BP. + +**Nothing** is when found that the input public key is not a BP. + +**TinyBlock** represents that the BP has just updated the consensus information, but its time slot has not yet passed, and it still has time to produce a few additional blocks. Currently, each time slot can produce up to 8 small blocks. The benefit of this is to improve the efficiency of block verification (eos does the same). + +There is a time slot issue that needs special attention. Since AEDPoS chooses to generate the first round of consensus information (i.e. the time slots of all initial BPs) in the genesis block, +and the genesis block should be completely consistent for each node, the consensus information of the first round has to be assigned to a unified time (otherwise the hash value of the genesis block will be inconsistent): +now this time is 0:00 in 1970. This will cause the time slot of the first round to be extremely inaccurate, so special treatment will be done when obtaining the `ConsensusCommand` of the first round. + +#### GetConsensusBehaviour + +In the AEDPoS Contract, in order for the `GetConsensusCommand` method to return ConsensusCommand, the AElfConsensusBehaviour will be obtained based on the input public key and call time. +Then, the AElfConsensusBehaviour will be used to determine the next block time and other information. + +The following figure briefly illustrates the implementation of this method, but lacks some details. +You can combine it with the code for more details if interested. + +![GetConsensusCommand](images/get-consensus-behaiviour.png) + +##### GetConsensusCommand - UpdateValue(WithoutPreviousInValue) + +`AElfConsensusBehaviour.UpdateValueWithoutPreviousInValue`'s main function is to implement the [Commitment Scheme](https://en.wikipedia.org/wiki/Commitment_scheme), +which only includes one _commit phase_ and does not include the _reveal phase_. +The corresponding stage of the consensus mining process is the first round of each session +(including the first session, which is when the chain just started), and BP tries to generate the first block of this round. + +If it is currently in the first **Round** of the first **Term**, it is necessary to obtain +the order of provided pubkey from `Round.real_time_miners_information`. +And the expected mining time will be `order * mining_interval` seconds later. +The `mining_interval` is 4000 milliseconds by default. + +Otherwise, the `expected_mining_time` is read directly from the Round information, and ConsensusCommand is returned based on this. + +##### GetConsensusCommand - UpdateValue + +`AElfConsensusBehaviour.UpdateValue` consists of a _reveal phase_ in the Commitment Scheme and a new _commit phase_. +The phase corresponding to the consensus mining process is the second round of each session and beyond, and BP attempts to generate the first block of this round. + +Directly read the current round information in the BP pubkey corresponding to the `expected_mining_time` field. + +##### GetConsensusCommand - NextRound + +`AElfConsensusBehaviour.NextRound` will generate the order and corresponding time slots of each BP in the next round according to the information published by each BP in this round, +and advance the RoundNumber by one number. + +For the BP designated as the extra block producer in this round, +the extra block generation time slot of this round can be directly read. + +Otherwise, in order to prevent the designated additional block producer from dropping or producing blocks on another fork (which may occur in the case of network instability), +all other BPs will also receive a different time slot for producing additional blocks. +These BPs will immediately reset their schedulers after synchronizing to any additional block produced by a BP, so there is no need to worry about conflicts. + +For the first round of special treatment with `AElfConsensusBehaviour.UpdateValue (WithoutPreviousInValue)`. + +##### GetConsensusCommand - NextTerm + +`AElfConsensusBehaviour.NextTerm` will re-select 17 BPs based on the current election results to generate information for the first round of the new term. + +The information is the quite similar to `AElfConsensusBehaviour.NextRound` in the first round of the first term. + +##### GetConsensusCommand - TinyBlock + +`AElfConsensusBehaviour.TinyBlock` occurs in two scenarios: +1. The current BP is the producer of additional blocks from the previous round. +After producing blocks containing one NextRound transaction, it needs to continue producing up to 7 blocks in the same time slot. +2. BP has just produced blocks containing UpdateValue transactions and needs to continue producing up to 7 blocks in the same time slot. + +The basic judgment logic is that if the current BP is a block containing UpdateValue transactions that have been produced in this round, +that is, case 2, combined with the current BP being the producer of additional blocks in the previous round, a time slot with a length of 4000ms is cut into 8 smaller time slots of 500ms for allocation; +otherwise, for the above case 1, a reasonable smaller time slot is directly allocated based on the number of small blocks that have been produced. + +#### GetConsensusExtraData + +Consensus Extra Data is stored in the ExtraData field in the Block Header. +This field is used to store information that various parts of the aelf system can use to assist in completing block verification. +Consensus Extra Data is the data used to assist in completing consensus information verification. +Because it is stored in the block header, it is used to help all nodes receiving the block **quickly verify** the consensus information before executing transactions in the block. +If the consensus information contained in the block header is incorrect, then the transaction in this block does not need to be executed and can be discarded directly. + +In the process of producing blocks, BP obtains the Consensus Extra Data to be set in the block header through the `GetConsensusExtraData` method of the AEDPoS contract. + +The parameter of this method is a binary array. +Different consensus mechanisms can customize their own parameter types and deserialize them in the method implementation. +As for the AEDPoS implementation, the parameter is a `AElfConsensusTriggerInformation` structure. + +```protobuf +message AElfConsensusTriggerInformation { + // The miner public key. + bytes pubkey = 1; + // The InValue for current round. + aelf.Hash in_value = 2; + // The InValue for previous round. + aelf.Hash previous_in_value = 3; + // The behaviour of consensus. + AElfConsensusBehaviour behaviour = 4; + // The encrypted pieces of InValue. + map encrypted_pieces = 5; + // The decrypted pieces of InValue. + map decrypted_pieces = 6; + // The revealed InValues. + map revealed_in_values = 7; +} +``` + +In the AEDPoS implementation, the return value is a `AElfConsensusHeaderInformation` structure: + +```protobuf +message AElfConsensusHeaderInformation { + // The sender public key. + bytes sender_pubkey = 1; + // The round information. + Round round = 2; + // The behaviour of consensus. + AElfConsensusBehaviour behaviour = 3; +} +``` + +The acquisition of Consensus Extra Data is first based on the `AElfConsensusTriggerInformation` behaviour field, and different consensus behaviors will generate different block header information. + +For example, in `UpdateValue`, the `InValue` of the previous round will be made public, and the `OutValue` of this round will be announced; +in `TinyBlock`, the number of small blocks corresponding to BP in the Round information of this round will only be increased by one; +in `NextRound` and `NextTerm`, new Round information will be generated, the difference is that the latter will retrieve the MinerList (Election Contract's `TryToGetVictories` method). + +Finally, in order to prevent the returned `AElfConsensusHeaderInformation` instance from being too large, the data related to `SecretSharing` will be deleted, and this part of the data does not need to be included in the block header for verification. + +#### GenerateConsensusTransactions + +This method is used to assist BP in generating Consensus System Transactions to be included in newly produced blocks. + +The imported parameter is `GetConsensusExtraData` and `AElfConsensusTriggerInformation`, which have already discussed before. + +The return value is `TransactionList`. + +```protobuf +message TransactionList { + // Consensus system transactions. + repeated aelf.Transaction transactions = 1; +} +``` + +`UpdateValue`, `TinyBlock`, `NextRound`, `NextTerm` transactions are generated depending on the behaviour. + +After these transactions are executed, the consensus data in StateDb, such as Round structure data, will be modified. + +At the same time, these transactions will be prioritized as system transactions and packaged into the upcoming blocks. + +### ValidateConsensusBeforeExecution + +This method is used to verify whether the Consensus Extra Data in BlockHeader is correct. +- The imported parameter is parsed into AElfConsensusHeaderInformation structure: extraData. +- Take the consensus information of the current round in StateDb: baseRound; +- Before verification, fill the baseRound with Round information from extraData. +- Construct validation context structure ConsensusValidationContext; +- Do three basic verifications: + - **MiningPermissionValidationProvider**: Is Sender in the Miner List? + - **TimeSlotValidationProvider**: The current time is not in the time slot of Sender (get the time slot according to Round information); + - **ContinuousBlocksValidationProvider**: To place a BP generate too many consecutive blocks at once (no more than 8). +- Add validation based on consensus behavior: + - UpdateValue: + - **UpdateValueValidationProvider**: Verify Out Value and Previous In Value information; + - **LibInformationValidationProvider**: Verify whether the lib information is correct. + - NextRound: + - **NextRoundMiningOrderValidationProvider**: Verify whether the BP block sequence in the newly generated round is correct; + - **RoundTerminateValidationProvider**: Whether the newly generated Round information is correct; + - NextTerm: + - **RoundTerminateValidationProvider**: whether the newly generated Round information is correct. + +#### ValidateConsensusAfterExecution + +This method is used to verify whether the execution result is consistent with Consensus Extra Data after the execution of the consensus system transaction (that is, the transaction generated by the `GenerateConsensusTransactions` method). + +We will also check if the information in this round has been modified. +If it has been modified, whether the modification is appropriate. +(For example, if there is a BP replacement in this round, there will be modifications. At this time, we will verify whether the replacement result is correct.) + + +## Last Irreversible Block + +### Definition of LIB + +Irreversible block: + +In any blockchain, the newly generated block has the possibility of being reversed. +A new block needs to undergo a certain amount of confirmation, that is, some subsequent blocks are generated on the blockchain to ensure that it is valid and that other network participants have agreed on the validity of the block. +Once a block has been confirmed enough, it is considered irreversible, that is, it is impossible to change or revoke the transaction content. + +The consensus mechanism of aelf will select a past block as the latest irreversible block (Last Irreversable Block) through double confirmation. +The information of this block will be recorded in the Chain structure in ChainDb. + +That is, two properties in the `Chain` data structure: `last_irreversible_block_hash` and `last_irreversible_block_height`. + +```protobuf +{ + "ChainId": "tDVV", + "Branches": { + "21a6058fe1419042c6a9f780734fab175694052f966667b992a151af5d65d751": 17122469 + }, + "NotLinkedBlocks": {}, + "LongestChainHeight": 17122469, + "LongestChainHash": "21a6058fe1419042c6a9f780734fab175694052f966667b992a151af5d65d751", + "GenesisBlockHash": "564892c8e1cddfb2ddd27e06992324f16fc833e5152bfd95e01b0f4971677131", + "GenesisContractAddress": "2dtnkWDyJJXeDRcREhKSZHrYdDGMbn3eus5KYpXonfoTygFHZm", + "LastIrreversibleBlockHash": "a5678d99ccda03b861d1c63fde7ce27ec484c74f1cc349119e08ac86b3ac5aea", + "LastIrreversibleBlockHeight": 17122160, + "BestChainHash": "21a6058fe1419042c6a9f780734fab175694052f966667b992a151af5d65d751", + "BestChainHeight": 17122469 +} +``` + +### Generation of LIB + +During the block production process, BP hints at the current height as LIB by observing the filling status of the time slots in the last two rounds. +If the filling status of the time slots is ideal, the height of the last block (`UpdateValue`) will be hinted at when the BP block is produced. + +- If in a time slot, the corresponding BP has produced at least one block, we say that the time slot has been filled. + +When the progress of this round exceeds 2/3, on the premise that the number of hints is sufficient, +take one-third of the position of the LIB implied by the previous round and this round have all produced blocks, +and set it to the latest LIB. + +BPs will attempt to set up LIB during the execution of `UpdateValue` transactions. + +AElf node will start the process of setting LIB after the AEDPoS Contract throws the event `IrreversibleBlockFound`. + +The confirmation of LIB means that the block of aelf blockchain has got the manuscript ready. +Blocks before the height of LIB (< LIB height) are irreversible blocks. + +### Calculation of LIB + +Assuming that current aelf network has 5 bps. + +In Round 9: +- BP1 produced block from 101 (UpdateValue) to 108 + - implies that the LIB height is 101 +- BP2 produced block from 109 (UpdateValue) to 116 + - implies that the LIB height is 109 +- BP3 produced block from 117 (UpdateValue) to 124 + - implies that the LIB height is 117 +- BP4 produced block from 125 (UpdateValue) to 132 + - implies that the LIB height is 125 - assuming the LIB height is set to 84 +- BP5 produced block from 133 (UpdateValue) to 140 + - implies that the LIB height is 133 +- BP1 as the EBP, produced block from 141(NextRound) to 148 + - not implies LIB height + +In Round 10: +- BP5 produced block from 149 (UpdateValue) to 156 +- implies that the LIB height is 149 +- BP4 produced block from 157 (UpdateValue) to 164 + - implies that the LIB height is 157 +- BP3 produced block from 165 (UpdateValue) to 172 + - implies that the LIB height is 165 +- BP2 produced block from 173 (UpdateValue) to 180 + - implies that the LIB height is 173 (the progress of this round is more than 2/3) + + - It will be carried out in the 197th block. + - The nodes that have produced blocks in this round are BP2, BP3, BP4, BP5. The height implied in the previous round was 109,117,125,133. + - The nodes that have produced blocks in this round + the previous round are (BP2, BP3, BP4, BP5) 4 > = BP quantity (5) * 2/3 + 1 == > returns LIBHeight = 117 + + - Node throws log: +> Finished calculation of lib height: 117 +New lib height: 117 + +Throw event: `IrreversibleBlockFound`, trigger IrreversibleBlockFoundLogEventProcessor +Modify `last_irreversible_block_hash` and `last_irreversible_block_height` data in Chain + + +- BP1 produced block from 181 (UpdateValue) to 188 + - implies that the LIB height is 181 +- BP5 as the EBP, produced block from 189(NextRound) to 196 + +![LIB Calculation](images/lib-calculation.png) + +Block confirmation time: + +- Height 85 After the block is produced, it is set to an irreversible block at height 173, 89 blocks, which takes about 44.5 seconds +- Height 117 is set as an irreversible block after block production to height 173, 67 blocks, which takes about 33.5 seconds + +## AElf Consensus Schedule + +### Trigger of consensus mechanism + +Trigger consensus mechanism means call `GetConsensusCommand` method to get next mining information and set a related event that will be executed in the future. +There are a total of **four** types of logic that trigger the consensus mechanism. + +Multiple effective triggering of consensus will not cause bugs: +for example, after completing one trigger, the consensus module informs the aelf BP node that blocks can be produced after 10s (via `GetConsensusCommand` method), +and triggers again after 2s. +The consensus module will inform the node that blocks can be produced after 8s and reset the time scheduler. +If triggered again after 4s, the consensus may inform the node that blocks can be produced after 20s, +which is possible in the current implementation (occurring in the complement logic of the extra time slot). + +This section will list all four types of logic of triggering consensus schedule. + +#### Trigger consensus mechanism when node starts + +Code: `BlockchainNodeContextService.StartAsync`. + +During the startup of the aelf node, the presence of the consensus module appears after confirming that there is a Chain-type data in the local ChainDb. + +Before this, the node probes whether the Chain structure can be obtained in ChainDb. +If it can, roll back to lib and start synchronizing blocks based on lib. If not, it means that ChainDb was empty before. At this point, the node's mission is to construct a Genesis block based on the AElf. Blockchains. * project they choose, and then initialize the Chain structure and put it into ChainDb. + +#### Trigger consensus mechanism after completing block synchronization + +Code: `FullBlockchainService.SetBestChain`. + +1. The node receives a block from the network, completes verification, execution, and re-verification, and the block is about to be confirmed and synchronized locally. +At this time, it attempts to set the BestChain information to the Chain structure. +Then trigger the consensus mechanism. + - For example: GrpcServerService.BlockBroadcastStream -> BlockReceivedEvent -> BlockReceivedEventHandler.HandleEventAsync -> BlockSyncService.SyncByBlockAsync -> EnqueueAttachBlockJob -> BlockSyncAttachService.AttachBlockWithTransactionsAsync, put the logic of validating block to the UpdateChainQueue -> BlockAttachService.AttachBlockAsync -> BlockExecutionResultProcessingService.ProcessBlockExecutionResultAsync -> FullBlockchainService.SetBestChain +2. The node has produced a block by itself, but still needs to complete verification, execution, and re-verification before it can be synchronized to the local. + - For example: ConsensusService.- > ConsensusRequestMiningEventData - > ConsensusRequestMiningEventHandler. HandleEventAsync - > After generating the block (whether successful or failed) + +#### Re-trigger the consensus mechanism after an exception occurs during the block generation process + +Code: `ConsensusRequestMiningEventHandler.HandleEventAsync`. + +If somehow failed to generate block, aelf node will try to trigger consensus mechanism again. + +#### Re-trigger consensus mechanism after consensus verification fails + +Code: +- `ConsensusService.ValidateConsensusBeforeExecutionAsync` +- `ConsensusService.ValidateConsensusAfterExecutionAsync` + +If the consensus information verification fails, aelf node will try to trigger consensus mechanism again. + +### Time scheduling for block production + +There are currently two implementations of time scheduling (implementing the `IConsensusScheduler` interface), **Rx.Net** and **FluentScheduler**. + +The aelf MainNet now is using the Rx.Net implementation. + +The `IConsensusScheduler` interface provides two methods: +- `NewEvent`, with countdown milliseconds and a structure containing the instructions needed for block generation: `ConsensusRequestMiningEventData` +- `CancelCurrentEvent`, used to cancel the block production task that is currently in countdown. + +Calling `NewEvent` will mount a timed task for producing blocks and enter the countdown. +When the countdown is complete, a `ConsensusRequestMiningEventData` event is published for `ConsensusRequestMiningEventHandler` to handle. \ No newline at end of file diff --git a/docs-sphinx/reference/smart-contract/cross-chain-contract.md b/docs-sphinx/reference/smart-contract/cross-chain-contract.md new file mode 100644 index 0000000000..129df94f9c --- /dev/null +++ b/docs-sphinx/reference/smart-contract/cross-chain-contract.md @@ -0,0 +1,113 @@ +# CrossChain Contract + +## Overview + +Unlike other blockchain ecosystem, aelf blockchain has a built-in cross chain mechanism. + +We have designed a structure of **MainChain**-**SideChains**, where a MainChain can have multiple SideChains. +The MainChain can act as a data center, regularly indexing the cross chain information generated by its SideChains, +and then submitting the cross chain information of any SideChain to all other SideChains. + +This way, not only can cross chain interaction occur between the MainChain and its SideChains, but also between its SideChains with the help of the MainChain. + +In addition, each SideChain can also serve as the MainChain, serving as the data center for the next level of SideChains. + +In this article, we will discuss: + +- Modules for implementing cross chain mechanism +- Cross chain mechanism technical foundation +- The process of initializing a SideChain +- The process of cross chain token transfer +- Merkle Path generation + +## Modules for implementing cross chain mechanism + +Cross chain mechanism on aelf is a very independent module, +and there was a requirement in the initial design: +assuming that aelf nodes are unaware of their ability of cross-chain indexing and verification. +The structuring of aelf code is better as a result, but it also increases the complexity of cross chain modules to some extent. + +![Cross Chain Class Diagram](images/cross-chain-class-diagram.png) + +`AElf.CrossChain.Core` is responsible for the core implementation logic of the cross chain mechanism, including: + +- New SideChain creation. +- Cross chain indexing. +- Cross chain indexing related transactions' generation. + +In order to ensure that communication between the MainChain and SideChains does not affect communication between aelf nodes, +we have specifically implemented a GRpc module for cross chain communication, that is `AElf.CrossChain.Grpc`. + +In addition, we all know that the modification of blockchain status (StateDb) requires the execution of transactions. +The interfaces for these transactions are defined in [acs7](https://docs.aelf.io/en/latest/reference/acs/acs7.html). + +**CrossChain Contract**, which is the focus of this article's upcoming discussion, +is responsible for implementing these interfaces. + +## Cross chain mechanism technical foundation + +As explained in [this](https://aelf-ean.readthedocs.io/en/latest/architecture/cross-chain/crosschain-verification.html) document, +we use **Merkle Tree** to achieve cross chain validation. + +Every piece of information that needs to be transmitted from the MainChain to SideChains, or from a SideChain to the MainChain and other SideChains, +will serve as a leaf node and participate in building the Merkle Tree. + +This transmission process is completed through the `AElf.CrossChain.Grpc` module mentioned above. +Which means that the MainChain node must be able to establish GRpc connections with all the SideChains nodes. +This is similar to the **Merged Mining**: +The nodes elected on the MainChain will also be responsible for block production on all SideChains. + +In the implementation of the aelf cross chain mechanism, the leaf nodes of Merkle Tree are composed of two entities: + +- TransactionId +- TransactionResultStatus + +`TransactionId` is computed from a `Transaction` entity. +And `TransactionResultStatus` is an enum value, here's the definition: + +```protobuf +enum TransactionResultStatus { + // The execution result of the transaction does not exist. + NOT_EXISTED = 0; + // The transaction is in the transaction pool waiting to be packaged. + PENDING = 1; + // Transaction execution failed. + FAILED = 2; + // The transaction was successfully executed and successfully packaged into a block. + MINED = 3; + // When executed in parallel, there are conflicts with other transactions. + CONFLICT = 4; + // The transaction is waiting for validation. + PENDING_VALIDATION = 5; + // Transaction validation failed. + NODE_VALIDATION_FAILED = 6; +} +``` + +Regardless of the execution result of a transaction, +its related information will be condensed into the hash value calculated by these two data, +which will be used as leaf nodes to construct the Merkle Tree. + +Subsequently, the **Merkle Tree Root** will be transmitted to the SideChain or MainChain through the GRpc module. + +![Verify Parent Chain Tx](images/verify-parent-chain-tx.png) + +Finally, on another chain, +**Merkle Path** can be used to verify that the information of a certain transaction exists and has been successfully executed by the user. + +Taking the above image as an example, transactions are executed in the MainChain, the verification process happens in the SideChain. +The Merkle Path to validate the existence and successful execution of the transaction represented using `Hash3` is: +`[Hash3, Hash4, Hash12, Hash5656, Root]`. +(Hash3 = `Hash(TransactionId, TransactionResultStatus.Mined)`) + +If the Merkle Path is successfully validated and the `Root` does exist within the SideChain CrossChain Contract, +then the code following the validation logic can be executed in the SideChain. + +## The process of initializing a SideChain + +![Initialize SideChain](images/initialize-side-chain.png) + +## The process of cross chain token transfer + +![Cross Chain Token Transfer](images/cross-chain-token-transfer.png) + diff --git a/docs-sphinx/reference/smart-contract/genesis-contract.md b/docs-sphinx/reference/smart-contract/genesis-contract.md new file mode 100644 index 0000000000..2333d7aef8 --- /dev/null +++ b/docs-sphinx/reference/smart-contract/genesis-contract.md @@ -0,0 +1,217 @@ +# Genesis Contract + +## Overview + +Genesis Contract, also known as the Zero Contract, is mainly used to deploy and maintain smart contracts running on the aelf blockchain. + +This contract will be deployed first when aelf blockchain launched so that it can be used to deploy other smart contracts. + +To achieve this purpose, the Genesis Contract implements the following methods defined in [acs0](https://docs.aelf.io/en/latest/reference/acs/acs0.html): + +```protobuf +service ACS0 { + // Deploy a system smart contract on chain and return the address of the system contract deployed. + rpc DeploySystemSmartContract (SystemContractDeploymentInput) returns (aelf.Address) { + } + + // Deploy a smart contract on chain and return the address of the contract deployed. + rpc DeploySmartContract (ContractDeploymentInput) returns (aelf.Address) { + } + + // Update a smart contract on chain. + rpc UpdateSmartContract (ContractUpdateInput) returns (aelf.Address) { + } + + // and others. + // ... +} +``` + +Therefore, developers can deploy (and update) their own smart contracts by interacting with the Genesis Contract. + +In this article, we will discuss: + +- Implementation of deploy and update contracts +- How the contract will be loaded to the smart contract execution environment +- The current process of deploying and updating aelf smart contracts +- Other details of the Genesis Contract + +## `SmartContractRegistration` and `ContractInfo` + +There is a critical data structure defined in `aelf/core.proto`, called SmartContractRegistration: + +```C# +message SmartContractRegistration { + // The category of contract code(0: C#). + sint32 category = 1; + // The byte array of the contract code. + bytes code = 2; + // The hash of the contract code. + Hash code_hash = 3; + // Whether it is a system contract. + bool is_system_contract = 4; + // The version of the current contract. + int32 version = 5; + // The version of the contract. + string contract_version = 6; + // The address of the current contract. + Address contract_address = 7; + // Indicates if the contract is the user contract. + bool is_user_contract = 8; +} +``` +Smart Contract code is stored in the `code` field. + +However, each `SmartContractRegistration` entity is not a one-to-one correspondence with the contract, and its storage structure is: + +```C# +public MappedState SmartContractRegistrations { get; set; } +``` + +The `SmartContractRegistration` entity can be fetched by the hash value of the contract code. +It is only written once when deploying the contract. + +The data structure that corresponds one-to-one with contracts is called `ContractInfo`. +Structure `ContractInfo` is defined in [acs0](https://docs.aelf.io/en/latest/reference/acs/acs0.html). + +```C# +message ContractInfo +{ + // The serial number of the contract. + int64 serial_number = 1; + // The author of the contract is the person who deployed the contract. + aelf.Address author = 2; + // The category of contract code(0: C#). + sint32 category = 3; + // The hash of the contract code. + aelf.Hash code_hash = 4; + // Whether it is a system contract. + bool is_system_contract = 5; + // The version of the current contract. + int32 version = 6; + string contract_version = 7; + // Indicates if the contract is the user contract. + bool is_user_contract = 8; +} +``` + +We use the MappedState to store related instances. + +```C# +public MappedState ContractInfos { get; set; } +``` + +From the `code_hash` field of `ContractInfo`, it is not difficult to guess: + +1. When trying to retrieve the contract code, the `code_hash` of ContractInfo is first read, and then the contract code itself is read from the `State.SmartContractRegistrations` mapped state. +2. Upgrading a contract on aelf is replacing the `code_hash` of `ContractInfo`. + +## Deploy and update contracts + +To deploy a smart contract to aelf, developers need to interact with the `DeploySmartContract` or `DeployUserSmartContract` defined by [acs0](https://docs.aelf.io/en/latest/reference/acs/acs0.html) and implemented by the Genesis Contract. +The differences between these two methods will be explained later. + +When executing the deployment method, the contract code will be stored in the StateDb through the structure we mentioned before: `SmartContractRegistration`. +More specifically, it is the `code` field. + +If developer's smart contract is written by C#, the `category` should be `0`. +The execution environment will select which runtime to load the contract code into based on the `category` field. + +And the `code_hash` is a unique identifier for the contract code. +For C# smart contract, the code hash is calculated by the Genesis Contract during deployment. + +After the contract code is saved in StateDb, another field is used to store the relevant information of the contract. +The structure is also mentioned before: `ContractInfo`. +There is a `code_hash` field defined in this structure, make it possible to use `GetContractInfo` method to get the contract information of provided contract address, +then use `GetSmartContractRegistrationByCodeHash` method to get contract code via contract code hash. +In addition, the contract code can also be obtained through method `GetSmartContractRegistrationByAddress`. + +As for updating the contract code, the contract information (`ContractInfo`) can be directly modified through method `UpdateSmartContract`, +then aelf smart contract execution environment can obtain the new contract code via new contract hash from the Genesis Contract in the future. + +## Execution of contract code + +Although the execution process of the contract is unrelated to the Genesis Contract. +Developers may be concerned about how their contract code will be consumed in the future after deployment. +Therefore, here are some brief explanations. + +![Contract Execution](images/contract-execution.png) + +As shown in the above figure, assuming that the contract code has been stored in the Genesis Contract. +When a caller tries to call a method of the contract, within the aelf node, the corresponding `SmartContractRegistration` will be obtained from the Genesis Contract, the contract code will be extracted, encapsulated as an Executive type, for the contract execution environment to call. +After completing the call, return the transaction result to the caller. + +Upgrading the contract will change the `SmartContractRegistration` obtained during the above process, so it is feasible to upgrade the deployed contract in aelf. + +## Calculation of contract address + +The contract address is calculated through a field that increases with the number of contract deployments. + +```C# +public Int64State ContractSerialNumber { get; set; } +``` + +Its calculation process is located in the `DeploySmartContract` method: + +```C# +var contractAddress = AddressHelper.BuildContractAddress(Context.ChainId, serialNumber); +``` + +- The contract address of each chain of aelf is different. +- The contract address is not related to the contract code, but only to the order in which it is deployed on this chain. + - Therefore, when testing newly written contracts in `aelf-boilerplate` or `aelf-developer-tools`, the new contract always has a fixed address. + +After the 1.6.0 version, Salt is added to the imported parameter of the deployment/upgrade contract. The contract address is calculated by using the Deployer address of the deployment account and the hash value Salt. + +```C# +var contractAddress = AddressHelper.ComputeContractAddress(deployer, salt); +``` + +- Deploying contracts with the same account and using the same Salt can make the contract address of each chain of aelf the same. + +## Contract deployment and update process + +### Deploy contract with audit + +![Deploy Contract With Audit](images/deploy-contract-with-audit.png) + +The current pipeline starts with Propose, which generates a parliamentary proposal. +When more than 2/3 of the BPs agree to deploy/update, a new proposal is released to request code inspection. +Finally, after the code audition is passed, the real contract deployment/upgrade will be achieved through the proposal of releasing the code inspection. + +### Deploy contract without audit + +![Deploy Contract Without Audit](images/deploy-contract-without-audit.png) + +Developers send deployment/update user contract transactions, generate a parliamentary CodeCheck proposal, and when more than 2/3 of the BPs conduct code checks and pass, achieve real contract deployment/upgrade through the proposal of automatically releasing code checks. + +### Contract deployment and upgrade new version number + +When upgrading a contract, check the contract version information +- If the contract version is less than or equal to the original contract version, the upgrade contract transaction fails + The old version of the contract only has a version number after being upgraded. +- If the version number is increasing, the upgrade contract transaction is successful. + +In the updateSmartContract method, increase the version number judgment: + +```C# +var contractInfo = Context.UpdateSmartContract(contractAddress, reg, null, info.ContractVersion); +Assert(contractInfo.IsSubsequentVersion, + $"The version to be deployed is lower than the effective version({info.ContractVersion}), please correct the version number."); +``` + +## Contract error message + +`DeployUserSmartContract` method: +- No permission. Trying to deploy a smart contract to an aelf private sidechain, and the transaction sender is not in the allowlist. +- contract code has already been deployed before. Contract code deployed. +- Already proposed. Duplicate deployment request. + +`UpdateUserSmartContract` method: +- No permission. The transaction sender is not the contract author. +- Code is not changed. The contract code has not changed since deployment or the previous update. +- The version to be deployed is lower than the effective version({currentVersion}), please correct the version number. The updated contract version number is too low. +- Already proposed. Duplicate deployment request. + +## Usage +Check the `deploy` command of [aelf-command](https://docs.aelf.io/en/latest/reference/cli/methods.html#deploy-deploy-a-smart-contract). \ No newline at end of file diff --git a/docs-sphinx/reference/smart-contract/images/contract-execution.png b/docs-sphinx/reference/smart-contract/images/contract-execution.png new file mode 100644 index 0000000000..5369c99fe7 Binary files /dev/null and b/docs-sphinx/reference/smart-contract/images/contract-execution.png differ diff --git a/docs-sphinx/reference/smart-contract/images/cross-chain-class-diagram.png b/docs-sphinx/reference/smart-contract/images/cross-chain-class-diagram.png new file mode 100644 index 0000000000..1f6fdd2847 Binary files /dev/null and b/docs-sphinx/reference/smart-contract/images/cross-chain-class-diagram.png differ diff --git a/docs-sphinx/reference/smart-contract/images/cross-chain-token-transfer.png b/docs-sphinx/reference/smart-contract/images/cross-chain-token-transfer.png new file mode 100644 index 0000000000..ee0e45f1b2 Binary files /dev/null and b/docs-sphinx/reference/smart-contract/images/cross-chain-token-transfer.png differ diff --git a/docs-sphinx/reference/smart-contract/images/deploy-contract-with-audit.png b/docs-sphinx/reference/smart-contract/images/deploy-contract-with-audit.png new file mode 100644 index 0000000000..1092aef66c Binary files /dev/null and b/docs-sphinx/reference/smart-contract/images/deploy-contract-with-audit.png differ diff --git a/docs-sphinx/reference/smart-contract/images/deploy-contract-without-audit.png b/docs-sphinx/reference/smart-contract/images/deploy-contract-without-audit.png new file mode 100644 index 0000000000..084762681f Binary files /dev/null and b/docs-sphinx/reference/smart-contract/images/deploy-contract-without-audit.png differ diff --git a/docs-sphinx/reference/smart-contract/images/get-consensus-behaiviour.png b/docs-sphinx/reference/smart-contract/images/get-consensus-behaiviour.png new file mode 100644 index 0000000000..88276020c1 Binary files /dev/null and b/docs-sphinx/reference/smart-contract/images/get-consensus-behaiviour.png differ diff --git a/docs-sphinx/reference/smart-contract/images/initialize-side-chain.png b/docs-sphinx/reference/smart-contract/images/initialize-side-chain.png new file mode 100644 index 0000000000..d1f7b5ed94 Binary files /dev/null and b/docs-sphinx/reference/smart-contract/images/initialize-side-chain.png differ diff --git a/docs-sphinx/reference/smart-contract/images/lib-calculation.png b/docs-sphinx/reference/smart-contract/images/lib-calculation.png new file mode 100644 index 0000000000..8e03c7c4bb Binary files /dev/null and b/docs-sphinx/reference/smart-contract/images/lib-calculation.png differ diff --git a/docs-sphinx/reference/smart-contract/images/verify-parent-chain-tx.png b/docs-sphinx/reference/smart-contract/images/verify-parent-chain-tx.png new file mode 100644 index 0000000000..274ebbcb05 Binary files /dev/null and b/docs-sphinx/reference/smart-contract/images/verify-parent-chain-tx.png differ diff --git a/docs-sphinx/reference/smart-contract/index.rst b/docs-sphinx/reference/smart-contract/index.rst new file mode 100644 index 0000000000..2a8e568fb5 --- /dev/null +++ b/docs-sphinx/reference/smart-contract/index.rst @@ -0,0 +1,11 @@ +Smart Contract +============== + +.. toctree:: + + Genesis Contract + Consensus Contract + MultiToken Contract + Cross Chain Contract + Vote Contract + Profit Contract \ No newline at end of file diff --git a/docs-sphinx/reference/smart-contract/multi-token-contract.md b/docs-sphinx/reference/smart-contract/multi-token-contract.md new file mode 100644 index 0000000000..436d81ffe1 --- /dev/null +++ b/docs-sphinx/reference/smart-contract/multi-token-contract.md @@ -0,0 +1,622 @@ +# MultiToken Contract + +## Overview + +In the aelf blockchain, native token **ELF** is issued, +managed, and circulated through the MultiToken Contract. + +We have implemented all the functions defined by ERC20 in the MultiToken contract: + +- transfer (Transfer) +- transferFrom (TransferFrom) +- approve (Approve / UnApprove) +- balanceOf (GetBalance) +- allowance (GetAllowance) +- name, symbol, decimals, totalSupply (GetTokenInfo) +- burn (Burn) + +Cross chain transfer between aelf MainChain and SideChains is also achieved through the MultiToken Contract, +refer to [CrossChain Contract](https://aelf-ean.readthedocs.io/en/latest/reference/smart-contract/cross-chain-contract.html) +for more details because we won't talk too much about this topic in this article: + +- CrossChainTransfer +- CrossChainReceiveToken + +On the basis of implementing the above methods, +all users can create their own tokens on the MultiToken contract, +without having to write their own code. + +- Create +- Issue + +Once a new token is created in the MultiToken Contract, it can automatically equip the functions defined in ERC20, just like **ELF**, and allow this type of token to be transferred across the MainChain and SideChains of aelf. +At the same time, we have reserved some customized solutions to provide the possibility of customizing token operations. + +In addition, we have also provided more operability for tokens hosted in the MultiToken Contract, such as Lock and UnLock. + +Due to the fact that aelf's smart contracts can be updated after deployment, in order to ensure users' assets, we recommend all developers to use MultiToken Contract to create and manage tokens. + +In this article, we will discuss: + +- How to create and issue a new type of token +- Basic operations on tokens +- How to customize token behaviour + +## Create and Issue new type of token + +### Create + +Any ELF token holder can create their own token in the MultiToken Contract through the `Create` method +on the aelf MainChain after paying an amount of transaction fees. + +Note: Token creation can **only** be happened in the aelf **MainChain**. + +The input type of `Create` method is: + +``` +message CreateInput { + // The symbol of the token. + string symbol = 1; + // The full name of the token. + string token_name = 2; + // The total supply of the token. + int64 total_supply = 3; + // The precision of the token + int32 decimals = 4; + // The address that has permission to issue the token. + aelf.Address issuer = 5; + // A flag indicating if this token is burnable. + bool is_burnable = 6; + // A whitelist address list used to lock tokens. + repeated aelf.Address lock_white_list = 7; + // The chain id of the token. + int32 issue_chain_id = 8; + // The external information of the token. + ExternalInfo external_info = 9; + // The address that owns the token. + aelf.Address owner = 10; +} +``` + +For example, if you use **aelf-command** tool described [here](https://aelf-ean.readthedocs.io/en/latest/reference/cli/index.html), +this command will help you create a new type of token: + +``` +aelf-command send AElf.ContractNames.Token Create '{"symbol": "NEW_TOKEN_SYMBOL", "tokenName": "New Token Name", "totalSupply": "1000000000", "decimals": "8"}' +``` + +After the new token is successfully created, users can obtain relevant information about the token they have created through the `GetTokenInfo` method. + +The input type of `GetTokenInfo` method is: + +``` +message GetTokenInfoInput { + // The symbol of token. + string symbol = 1; +} +``` + +You can use the `call` command of **aelf-command** tool to get the token information: + +``` +aelf-command call AElf.ContractNames.Token GetTokenInfo '{"symbol": "NEW_TOKEN_SYMBOL"}' +``` + +For instance, to get the token information of **ELF**, the interaction will be like: + +``` +aelf-command call AElf.ContractNames.Token GetTokenInfo '{"symbol": "ELF"}' + +? Enter the the URI of an AElf node: https://aelf-public-node.aelf.io +? Enter a valid wallet address, if you don't have, create one by aelf-command cr +eate: 2WNUjzNPyd7dBwo9a5KG56AXfPCdwmcydXv4kDyTyFZwTum5nd +? Enter the password you typed when creating a wallet: ******** +✔ Fetching contract successfully! +✔ Calling method successfully! +AElf [Info]: +Result: +{ + "symbol": "ELF", + "tokenName": "Native Token", + "supply": "99616840732317872", + "totalSupply": "100000000000000000", + "decimals": 8, + "issuer": "cxZuMcWFE7we6CNESgh9Y4M6n7eM5JgFAgVMPrTNEv9wFEvQp", + "isBurnable": true, + "issueChainId": 9992731, + "issued": "100000000000000000", + "externalInfo": null, + "owner": null +} +✔ Succeed! +``` + +### Issue + +When the user successfully creates a new token, +the token is still in a non circulating state. +Token circulation can be achieved by calling the `Issue` method. + +The input type of `Issue` method is: + +```protobuf +message IssueInput { + // The token symbol to issue. + string symbol = 1; + // The token amount to issue. + int64 amount = 2; + // The memo. + string memo = 3; + // The target address to issue. + aelf.Address to = 4; +} +``` + +You can use the `send` command of **aelf-command** tool complete the issue process: + +``` +aelf-command send AElf.ContractNames.Token Issue '{"symbol": "NEW_TOKEN_SYMBOL", "amount": "1000000000", "to": "2WNUjzNPyd7dBwo9a5KG56AXfPCdwmcydXv4kDyTyFZwTum5nd"}' +``` + +Then the address `2WNUjzNPyd7dBwo9a5KG56AXfPCdwmcydXv4kDyTyFZwTum5nd` will receive 1000000000 new tokens. + +Next, you can use the `call` command of aelf-command tool to check the balance via `GetBalance` method. + +The input type of `GetBalance` method is: +```protobuf +message GetBalanceInput { + // The symbol of token. + string symbol = 1; + // The target address of the query. + aelf.Address owner = 2; +} +``` + +``` +aelf-command call AElf.ContractNames.Token GetBalance '{"symbol": "NEW_TOKEN_SYMBOL", "owner": "2WNUjzNPyd7dBwo9a5KG56AXfPCdwmcydXv4kDyTyFZwTum5nd"}' +``` + +In addition, if developers wish to place the logic of the token issue in their own smart contract, +they can place the **Issue** operation in an appropriate position in the code through cross contract calls. + +``` +// If this code has been executed since the contract deployment, then it can be skipped. +State.TokenContract.Value = Context.GetContractAddressByName(SmartContractConstants.TokenContractSystemName); + +State.TokenContract.Issue.Send(new IssueInput +{ + Symbol = symbol, + To = receiverAddress, + Amount = amount +}); +``` + +or: + +``` +var tokenContractAddress = Context.GetContractAddressByName(SmartContractConstants.TokenContractSystemName); +Context.SendInline(tokenContractAddress, "Issue", new IssueInput +{ + Symbol = symbol, + To = receiverAddress, + Amount = amount +}); +``` + +### Create NFT + +Users can also create NFTs directly through the MultiToken Contract. + +The creation of NFTs can be divided into two categories: NFT Collection and a new type of NFT. +This difference is distinguished by the `symbol` filed when users send the `Create` transaction. + +If the `symbol` filed contains a **"-"** character, it will be considered as creating a NFT Collection or a new type of NFT. +And if there is a **"0"** after the **"-"** character, it will be considered as creating a NFT Collection. +Otherwise, it will be considered as creating a new type of NFT. + +Only after creating a NFT Collection can new NFTs be created in that Collection. + +When creating a NFT Collection, you can specify an image through the URL to facilitate displaying the NFT Collection in other tools on aelf. +The URL should be put the `external_info` filed via a specific key: **__nft_image_url**. + +For example, developers can use the following code to create a NFT Collection named **HELLO** in their contract code: + +``` +State.TokenContract.Create.Send(new CreateInput +{ + Symbol = "HELLO-0", + TokenName = "Hello", + TotalSupply = 1000, + Decimals = 0,//nft decimal=0 + Issuer = issuerAddress, + IssueChainId = chainId, + ExternalInfo = new ExternalInfo() + { + Value = + { + { + "__nft_image_url", + "https://example.com/head.jpg" + } + } + } +}); +``` + +Next, through the same `Create` method, users can create NFTs for the "Hello" NFT Collection. + +``` +State.TokenContract.Create.Send(new CreateInput +{ + Symbol = "HELLO-0001", + TokenName = "Hello", + TotalSupply = 10, + Decimals = 0, + Issuer = issuerAddress, + IssueChainId = chainId, + ExternalInfo = new ExternalInfo() + { + Value = + { + { + "__nft_image_url", + "https://example.com/1.jpg" + } + } + } +}); +``` + +The issuance, transfer, and cross-chain transfer of NFTs are consistent with FTs, +so there will be no further explanation here. + +## Basic operations on tokens + +### Transfer + +Users can transfer their own tokens by calling the Transfer method. + +The input type of `Transfer` method is: + +```protobuf +message TransferInput { + // The receiver of the token. + aelf.Address to = 1; + // The token symbol to transfer. + string symbol = 2; + // The amount to to transfer. + int64 amount = 3; + // The memo. + string memo = 4; +} +``` + +For example, if you use `aelf-command` tool described [here](https://aelf-ean.readthedocs.io/en/latest/reference/cli/index.html), this command will help you transfer your tokens: + +``` +aelf-command send AElf.ContractNames.Token Transfer '{"symbol": "ELF", "to": "C91b1SF5mMbenHZTfdfbJSkJcK7HMjeiuwfQu8qYjGsESanXR", "amount": "1000000"}' +``` + +If you're developing an aelf contract, after initializing the reference contract stub `State.TokenContract.Value` (by setting its address), you can do this: + +``` +// If this code has been executed since the contract deployment, then it can be skipped. +State.TokenContract.Value = Context.GetContractAddressByName(SmartContractConstants.TokenContractSystemName); + +State.TokenContract.Transfer.Send(new TransferInput +{ + Symbol = symbol, + To = receiverAddress, + Amount = amount +}); +``` + +This will transfer tokens **from your contract** to the account of `receiverAddress`. + +If your intention was not to transfer tokens from your contract, then you may consider to use `TransferFrom` method. + +### TransferFrom + +Users can transfer tokens from one account to another account by calling the `TransferFrom` method. + +Of course, the initiator of this transaction needs to obtain authorization to **from** account in advance via `Approve` method, +we will discuss this authorization method later. + +In the context of TransferFrom: +- **from** will be the transfer. +- **to** will be the token receiver. + +The input type of `TransferFrom` method is: + +``` +message TransferFromInput { + // The source address of the token. + aelf.Address from = 1; + // The destination address of the token. + aelf.Address to = 2; + // The symbol of the token to transfer. + string symbol = 3; + // The amount to transfer. + int64 amount = 4; + // The memo. + string memo = 5; +} +``` + +If you have obtained an amount of approved value from the **"from"** account, +you can send a `TransferFrom` transaction using the aelf-command tool. + +``` +aelf-command send AElf.ContractNames.Token TransferFrom '{"symbol": "ELF", "from": "C91b1SF5mMbenHZTfdfbJSkJcK7HMjeiuwfQu8qYjGsESanXR", "to": "2WNUjzNPyd7dBwo9a5KG56AXfPCdwmcydXv4kDyTyFZwTum5nd", "amount": "1000000"}' +``` + +Of course, it should be noted that the transfer amount cannot exceed the approved amount. + +When developing aelf smart contracts, it is often necessary to use the `TransferFrom` method to manipulate tokens: + +``` +// If this code has been executed since the contract deployment, then it can be skipped. +State.TokenContract.Value = Context.GetContractAddressByName(SmartContractConstants.TokenContractSystemName); + +State.TokenContract.TransferFrom.Send(new TransferFromInput +{ + Symbol = symbol, + From = fromAddress, + To = toAddress, + Amount = amount +}); +``` + +### Approve + +Users can use this method to approve whether an amount of token from an account can be spent by a third-party account. + +The `Approve` transaction's signer account will be the **"from"** account in the `TransferFrom` method. +And the "spender" account will have the right to transfer tokens from the **"from"** account. + +The input type of `Approve` method is: + +``` +message ApproveInput { + // The address that allowance will be increased. + aelf.Address spender = 1; + // The symbol of token to approve. + string symbol = 2; + // The amount of token to approve. + int64 amount = 3; +} +``` + +If you're using the aelf-command tool: + +``` +aelf-command send AElf.ContractNames.Token Approve '{"symbol": "ELF", "spender": "C91b1SF5mMbenHZTfdfbJSkJcK7HMjeiuwfQu8qYjGsESanXR", "amount": "1000000"}' +``` + +Next, you can use the `call` command of aelf-command tool to check the allowance via `GetAllowance` method. + +The input type of `GetAllowance` method is: +```protobuf +message GetAllowanceInput { + // The symbol of token. + string symbol = 1; + // The address of the token owner. + aelf.Address owner = 2; + // The address of the spender. + aelf.Address spender = 3; +} +``` + +``` +aelf-command call AElf.ContractNames.Token GetAllowance '{"symbol": "ELF", "owner": "2WNUjzNPyd7dBwo9a5KG56AXfPCdwmcydXv4kDyTyFZwTum5nd", "spender": "C91b1SF5mMbenHZTfdfbJSkJcK7HMjeiuwfQu8qYjGsESanXR"}' +``` + +### UnApprove + +The **sponsor** (The **"from"** account in `TransferFrom` method) can use the `UnApprove` method to decrease the allowance of a **spender** account. + +If the previous allowance was 100000, the sponsor can reduce the allowance by 50000 through the `UnApprove` method, then the allowance will be changed to 50000. + +The input type of `UnApprove` method is: + +``` +message UnApproveInput { + // The address that allowance will be decreased. + aelf.Address spender = 1; + // The symbol of token to un-approve. + string symbol = 2; + // The amount of token to un-approve. + int64 amount = 3; +} +``` + +If the `amount` filed of the input parameter exceeds or equals the allowance, then the allowance will become 0. + +If you're using the aelf-command tool: + +``` +aelf-command send AElf.ContractNames.Token UnApprove '{"symbol": "ELF", "spender": "C91b1SF5mMbenHZTfdfbJSkJcK7HMjeiuwfQu8qYjGsESanXR", "amount": "1000000"}' +``` + +Remember to call the `GetAllowance` method to check the new allowance, ensure that the allowance has changed. + +### Burn + +If a token is burnable, then any token holder can burn their own tokens through `Burn` method. + +Whether a token can be burned can be queried by calling `GetTokenInfo` method. + +Like if you‘re trying to get the **TokenInfo** of **ELF**: + +``` +aelf-command call AElf.ContractNames.Token GetTokenInfo '{"symbol": "ELF"}' +``` + +The response will be : + +``` +AElf [Info]: +Result: +{ + "symbol": "ELF", + "tokenName": "Native Token", + "supply": "99616865842269247", + "totalSupply": "100000000000000000", + "decimals": 8, + "issuer": "cxZuMcWFE7we6CNESgh9Y4M6n7eM5JgFAgVMPrTNEv9wFEvQp", + "isBurnable": true, + "issueChainId": 9992731, + "issued": "100000000000000000", + "externalInfo": null, + "owner": null +} +``` + +Note this line: +``` +"isBurnable": true, +``` +Which means the ELF token can be burned. + +The input type of `Burn` method is: + +``` +message BurnInput { + // The symbol of token to burn. + string symbol = 1; + // The amount of token to burn. + int64 amount = 2; +} +``` + +If you're using the aelf-command tool: + +``` +aelf-command send AElf.ContractNames.Token Burn '{"symbol": "ELF", "amount": "100"}' +``` + +### Lock & Unlock + +The `Lock` method is similar to `TransferFrom` in that +`TransferFrom` directly transfers the tokens to a specific address, +while `Lock` transfers the token to a virtual address generated by the MultiToken Contract. + +This means that every time the `Lock` method is executed, a new virtual address will be generated to store the locked token for this time. +Moreover, only the MultiToken Contract have the permission to transfer tokens from this virtual address. + +This to some extent ensures the security of user assets. +The user only locks the tokens in a virtual address for some purpose, rather than directly sending it to another address; +After meeting certain conditions, this tokens can be returned to the user. + +The input type of `Lock` method is: +``` +message LockInput { + // The one want to lock his token. + aelf.Address address = 1; + // Id of the lock. + aelf.Hash lock_id = 2; + // The symbol of the token to lock. + string symbol = 3; + // a memo. + string usage = 4; + // The amount of tokens to lock. + int64 amount = 5; +} +``` + +When you are an aelf smart contract developer and need users to lock tokens, this can be achieved through: + +``` +// If this code has been executed since the contract deployment, then it can be skipped. +State.TokenContract.Value = Context.GetContractAddressByName(SmartContractConstants.TokenContractSystemName); + +State.TokenContract.Lock.Send(new LockInput +{ + Symbol = YOUR_TOKEN_SYMBOL, + Address = LOCKER_ADDRESS, + Amount = AMOUNT, + LockId = A_HASH_VALUE, + Usage = MEMO +}); +``` + +However, there is one condition for this operation: +the address of the contract using this code, must be contained by the `lock_white_list` field of the locking token's `TokenInfo`. +Otherwise, this contract won't have the permission to lock user's certain type of tokens. + +And to unlock tokens, the code will be like: +``` +State.TokenContract.Unlock.Send(new UnlockInput +{ + Symbol = YOUR_TOKEN_SYMBOL, + Address = LOCKER_ADDRESS, + Amount = AMOUNT, + LockId = SAME_HASH_VALUE +}); +``` + +## How to customize token behaviour + +If as a developer, you have already created your own token through the MultiToken Contract, +then you can leverage `external_info` field of `TokenInfo` structure, to call a configured method (**Callback Method**) when the following actions occur on the token: + +- Transfer / TransferFrom +- Lock +- Unlock + +It should be noted that in the current version, +`TokenInfo` can only be set when creating a new token. + +The callback method looks like: + +``` +message CallbackInfo { + aelf.Address contract_address = 1; + string method_name = 2; +} +``` + +For example: +- You have already deployed a smart contract via Genesis Contract, +the contract address is `2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS`. +- And there's a `CheckTransfer` method in your contract. +- You want to create an **ABC** token via the MultiToken Contract. + +Let's say, if your intention is to automatically call the `CheckTransfer` method +whenever someone transfers **ABC** tokens. If the method fails, the transfer will fail. + +Then, when you create **ABC** token, +you need to fill the **external_info** field with the key **aelf_transfer_callback**. The C# code will be like: + +``` +var externalInfo = new ExternalInfo(); +externalInfo.Value.Add("aelf_transfer_callback", new CallbackInfo +{ + ContractAddress = Address.FromBase58("2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS"), + MethodName = "CheckTransfer" +}.ToString()); + +State.TokenContract.Create.Send(new CreateInput +{ + Symbol = "ABC", + TokenName = "ABC Token", + Decimals = 8, + Issuer = ISSUER_ADDRESS, + Owner = OWNER_ADDRESS, + IsBurnable = true, + TotalSupply = 1000000000, + ExternalInfo = externalInfo +}); +``` + +In this way, whenever a user transfers **ABC** tokens in the future, +the `CheckTransfer` method in the contract with address `2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS` +will be executed at the same time. +If the `CheckTransfer` method fails to execute, +the `Transfer` or `TransferFrom` transaction will fail. + +The author of aforementioned contract +can modify the implementation of the `CheckTransfer` method through contract update. + +Similarly, key **aelf_lock_callback** can be used to specify the callback method when a user's ABC token is locked by the `Lock` method, +key **aelf_unlock_callback** can be used to specify the callback method when a user's ABC token is unlocked by the `Unlock` method. \ No newline at end of file diff --git a/docs-sphinx/reference/smart-contract/plantuml/contract-execution.puml b/docs-sphinx/reference/smart-contract/plantuml/contract-execution.puml new file mode 100644 index 0000000000..905a37a5d7 --- /dev/null +++ b/docs-sphinx/reference/smart-contract/plantuml/contract-execution.puml @@ -0,0 +1,11 @@ +@startuml +autonumber + +Caller -> TransactionExecutionService: Transaction +TransactionExecutionService -> SmartContractExecutiveService: Call Execute method +SmartContractExecutiveService -> GenesisContract: Call GetSmartContractRegistrationByAddress method +GenesisContract --> SmartContractExecutiveService: SmartContractRegistration instance +SmartContractExecutiveService --> TransactionExecutionService: Executive instance +TransactionExecutionService --> Caller: Transaction Result + +@enduml \ No newline at end of file diff --git a/docs-sphinx/reference/smart-contract/plantuml/cross-chain-token-transfer.puml b/docs-sphinx/reference/smart-contract/plantuml/cross-chain-token-transfer.puml new file mode 100644 index 0000000000..4586ba42e3 --- /dev/null +++ b/docs-sphinx/reference/smart-contract/plantuml/cross-chain-token-transfer.puml @@ -0,0 +1,13 @@ +@startuml + +autonumber + +participant "User" as U +participant "Chain A MultiToken Contract" as A +participant "Chain B MultiToken Contract" as B + +U -> A: CrossChainTransfer +A --> U: +U -> B: CrossChainReceiveToken + +@enduml \ No newline at end of file diff --git a/docs-sphinx/reference/smart-contract/plantuml/deploy-contract-with-audit.puml b/docs-sphinx/reference/smart-contract/plantuml/deploy-contract-with-audit.puml new file mode 100644 index 0000000000..8ea27e8931 --- /dev/null +++ b/docs-sphinx/reference/smart-contract/plantuml/deploy-contract-with-audit.puml @@ -0,0 +1,23 @@ +@startuml + +autonumber + +Developer -> GenesisContract: ProposeNewContract / ProposeUpdateContract +GenesisContract -> ParliamentContract: CreateProposalBySystemContract +ParliamentContract -> ParliamentContract: CreateNewProposal \n(GenesisContract.ProposeContractCodeCheck) +ParliamentContract -> BlockProducer: ProposalCreated event +BlockProducer -> ParliamentContract: Approve >= 2/3 \n(ContractZero.ProposeContractCodeCheck) +ParliamentContract -> Developer: ReceiptCreated event +Developer -> GenesisContract: ReleaseApprovedContract +GenesisContract -> ParliamentContract: Release +ParliamentContract -> ParliamentContract: CreateProposalBySystemContract +ParliamentContract -> ParliamentContract: CreateNewProposal \n(ContractZero.DeploySmartContract \n/ContractZero.UpdateSmartContract) +ParliamentContract -> BlockProducer: ProposalCreated event +BlockProducer -> ParliamentContract: Approve \n(Automatically by aelf node) +ParliamentContract -> Developer: ReceiptCreated event +Developer -> GenesisContract: ReleaseCodeCheckedContract +GenesisContract -> ParliamentContract: Release +ParliamentContract -> GenesisContract: DeploySmartContract \n/UpdateSmartContract + + +@enduml \ No newline at end of file diff --git a/docs-sphinx/reference/smart-contract/plantuml/deploy-contract-without-audit.puml b/docs-sphinx/reference/smart-contract/plantuml/deploy-contract-without-audit.puml new file mode 100644 index 0000000000..edc7808497 --- /dev/null +++ b/docs-sphinx/reference/smart-contract/plantuml/deploy-contract-without-audit.puml @@ -0,0 +1,16 @@ +@startuml + +autonumber + +Developer -> GenesisContract: DeployUserSmartContract \n/UpdateUserSmartContract +GenesisContract -> ParliamentContract: CreateProposalBySystemContract +ParliamentContract -> ParliamentContract: CreateNewProposal \n(ContractZero.PerformDeployUserSmartContract \n/ContractZero.PerformUpdateUserSmartContract) +ParliamentContract -> BlockProducer: ProposalCreated event +BlockProducer -> ParliamentContract: Approve \n(Automatically by aelf node) + +BlockProducer -> GenesisContract: Approve>=2/3 \nReleaseApprovedUserSamrtContract \n(Automatically by aelf node) +GenesisContract -> ParliamentContract: Release +ParliamentContract -> GenesisContract: PerformDeployUserSmartContract \n/PerformUpdateUserSmartContract + + +@enduml \ No newline at end of file diff --git a/docs-sphinx/reference/smart-contract/plantuml/get-consensus-behaiviour.puml b/docs-sphinx/reference/smart-contract/plantuml/get-consensus-behaiviour.puml new file mode 100644 index 0000000000..3547443fc6 --- /dev/null +++ b/docs-sphinx/reference/smart-contract/plantuml/get-consensus-behaiviour.puml @@ -0,0 +1,21 @@ +@startuml +start +:GetConsensusCommand; +if (MinerList contains provided pubkey?) then (false) + :Nothing; + stop +else (true) + if (Timeslot for this BP already passed?) then (false) + if (Has this BP produced enough tiny blocks?) then (false) + :TinyBlock; + stop + else (true) + :NextRound; + stop + endif + else (true) + :NextRound; + stop + endif + +@enduml diff --git a/docs-sphinx/reference/smart-contract/plantuml/initialize-side-chain.puml b/docs-sphinx/reference/smart-contract/plantuml/initialize-side-chain.puml new file mode 100644 index 0000000000..b7919daffa --- /dev/null +++ b/docs-sphinx/reference/smart-contract/plantuml/initialize-side-chain.puml @@ -0,0 +1,16 @@ +@startuml + +autonumber + +participant "SideChain CrossChain Contract" as SC +participant "SideChain" as S +participant "MainChain" as M +participant "MainChain CrossChain Contract" as MC + +S -> S: RequestChainInitializationDataAsync +S -> M: RequestChainInitializationDataFromParentChain +M -> MC: GetChainInitializationData +MC -> S: GetInitializeMethodList +S -> SC: Initialize + +@enduml \ No newline at end of file diff --git a/docs-sphinx/reference/smart-contract/profit-contract.md b/docs-sphinx/reference/smart-contract/profit-contract.md new file mode 100644 index 0000000000..15608f81a1 --- /dev/null +++ b/docs-sphinx/reference/smart-contract/profit-contract.md @@ -0,0 +1,276 @@ +# Profit Contract + +## Overview + +Profit Contract can be used to manage the lifecycle of a profit scheme (dividend plan), +such as creating a profit scheme, maintaining the beneficiaries of profits, and distributing profits. + +The manager of the profit scheme can adjust +the amount of profits that the beneficiary can receive when releasing profit in the future, +by maintaining the shares of each profit beneficiary. + +At the same time, a profit scheme can also have sub profit schemes, +which will exist as special profit beneficiaries. +The sub profit scheme independently maintains its beneficiaries and profit distribution time. + +In the aelf MainChain, +we use the Profit Contract to manage the benefits of Block Producers and +the revenue of voters participating in the node elections. + +In this article, we will discuss: + +- How to create a profit scheme +- How to manage shares of beneficiaries +- How to contribute and distribute profits +- Application of Profit Contract in AElf Economic System +- Profit Contract method explanation + +## How to create a profit scheme + +In the Profit Contract, the `Scheme` structure is used to store basic information of a profit scheme: + +``` +message Scheme { + // The virtual address of the scheme. + aelf.Address virtual_address = 1; + // The total weight of the scheme. + int64 total_shares = 2; + // The manager of the scheme. + aelf.Address manager = 3; + // The current period. + int64 current_period = 4; + // Sub schemes information. + repeated SchemeBeneficiaryShare sub_schemes = 5; + // Whether you can directly remove the beneficiary. + bool can_remove_beneficiary_directly = 6; + // Period of profit distribution. + int64 profit_receiving_due_period_count = 7; + // Whether all the schemes balance will be distributed during distribution each period. + bool is_release_all_balance_every_time_by_default = 8; + // The is of the scheme. + aelf.Hash scheme_id = 9; + // Delay distribute period. + int32 delay_distribute_period_count = 10; + // Record the scheme's current total share for deferred distribution of benefits, period -> total shares. + map cached_delay_total_shares = 11; + // The received token symbols. + repeated string received_token_symbols = 12; +} +``` + +The field `scheme_id` is the unique identifier of a profit scheme. + +And more importantly, `virtual_address` can be considered as the overall ledger address for a profit scheme. +If you want to know how much money is in the dividend pool of a profit scheme, +you can obtain the token balance of the `virtual_address` through the `GetBalance` method of the MultiToken Contract. +The symbol of the token in the dividend pool is recorded in the `received_token_symbols` field. +Then it is obvious that a profit scheme's dividend pool can allow for the existence of multiple types of tokens. + +Each beneficiary and sub profit scheme has an amount of **shares**, and if these shares are added, it is `totol_shares` field. +And when tokens in the dividend pool are released, +beneficiaries and sub profit schemes receive a certain number of tokens based on their respective shares. + +The input type of `CreateScheme` method is: + +``` +message CreateSchemeInput { + // Period of profit distribution. + int64 profit_receiving_due_period_count = 1; + // Whether all the schemes balance will be distributed during distribution each period. + bool is_release_all_balance_every_time_by_default = 2; + // Delay distribute period. + int32 delay_distribute_period_count = 3; + // The manager of this scheme, the default is the creator. + aelf.Address manager = 4; + // Whether you can directly remove the beneficiary. + bool can_remove_beneficiary_directly = 5; + // Use to generate scheme id. + aelf.Hash token = 6; +} +``` + +The following is the process of creating a profit scheme on the aelf TestNet using the **aelf-command** tool: + +``` +aelf-command send AElf.ContractNames.Profit CreateScheme +? Enter the the URI of an AElf node: https://aelf-test-node.aelf.io +? Enter a valid wallet address, if you don't have, create one by aelf-command create: 2WNUjzNPyd7dBwo9a +5KG56AXfPCdwmcydXv4kDyTyFZwTum5nd +? Enter the password you typed when creating a wallet: ******** +✔ Fetching contract successfully! + +If you need to pass file contents as a parameter, you can enter the relative or absolute path of the file + +Enter the params one by one, type `Enter` to skip optional param: +? Enter the required param : 0 +? Enter the required param : true +? Enter the required param : 0 +? Enter the required param : +? Enter the required param : true +? Enter the required param : ELF +The params you entered is: +{ + "profitReceivingDuePeriodCount": 0, + "isReleaseAllBalanceEveryTimeByDefault": true, + "delayDistributePeriodCount": 0, + "canRemoveBeneficiaryDirectly": true, + "token": "ELF" +} +✔ Succeed! +AElf [Info]: +Result: +{ + "TransactionId": "ab97bd5a3d42e2f8ab3d2f3e02019fe232369380791e4c90180c54b318630f9d" +} +✔ Succeed! +``` +- Each time the profit scheme distributes tokens from the dividend pool we call it a **period**. `profitReceivingDuePeriodCount` is used to limit how many periods of dividends a user can receive each time. If not set, it defaults to 10. +- If `isReleaseAllBalanceEveryTimeByDefault` is true, then every time the profit scheme manager distribute tokens, if the number of tokens distributed is not specified, all balance in `virtual_address` will be distributed by default. +- The dividends generated in the current period can be distributed in the future, use the `delayDistributePeriodCount` to set how many period will be delay distributed. +- If `canRemoveBeneficiaryDirectly` is true, the profit scheme manager can remove a beneficiary without considering he's shares are not expired. +- It is necessary to specify a default `token` for the profit scheme when creating it. However, it should be noted that this does not limit the distribution of this profit scheme to only one token. + +After the creation, you can use the following command to get your `SchemeId` and `VirtualAddress`: + +``` +aelf-command event ab97bd5a3d42e2f8ab3d2f3e02019fe232369380791e4c90180c54b318630f9d +? Enter the the URI of an AElf node: https://aelf-test-node.aelf.io + +[Info]: +The results returned by +Transaction: ab97bd5a3d42e2f8ab3d2f3e02019fe232369380791e4c90180c54b318630f9d is: +[ + { + "Address": "JRmBduh4nXWi1aXgdUsj5gJrzeZb2LxmrAbf7W99faZSvoAaE", + "Name": "TransactionFeeCharged", + "Indexed": [ + "GiIKIMZijqxLIVIayGidcVc0r+o9XtJ5T1N80m4rQ/qfnd38" + ], + "NonIndexed": "CgNFTEYQtOqhrQM=", + "Result": { + "chargingAddress": "2WNUjzNPyd7dBwo9a5KG56AXfPCdwmcydXv4kDyTyFZwTum5nd", + "symbol": "ELF", + "amount": "900232500" + } + }, + { + "Address": "2ZUgaDqWSh4aJ5s5Ker2tRczhJSNep4bVVfrRBRJTRQdMTbA5W", + "Name": "SchemeCreated", + "Indexed": [], + "NonIndexed": "CiIKIEFwvVDodszlfIh14Vzp6K07Nduws7ZZY43hC3gl+ZtrEiIKIMZijqxLIVIayGidcVc0r+o9XtJ5T1N80m4rQ/qfnd38GAogASoiCiBhamBcQiHIK9XIinJ5+8/00jtJUQEKy7vuJKavwrY1mQ==", + "Result": { + "virtualAddress": "Vpb3LDUnkgrVetF1RADywdLbXQ5fY95VeLymgVsM7ti1XUyh4", + "manager": "2WNUjzNPyd7dBwo9a5KG56AXfPCdwmcydXv4kDyTyFZwTum5nd", + "profitReceivingDuePeriodCount": "10", + "isReleaseAllBalanceEveryTimeByDefault": true, + "schemeId": "616a605c4221c82bd5c88a7279fbcff4d23b4951010acbbbee24a6afc2b63599" + } + } +] +✔ Succeed! +``` + +As you can see from the event message: +- The SchemeId is `616a605c4221c82bd5c88a7279fbcff4d23b4951010acbbbee24a6afc2b63599`. +- The VirtualAddress is `Vpb3LDUnkgrVetF1RADywdLbXQ5fY95VeLymgVsM7ti1XUyh4`. + +## How to manage shares of beneficiaries + +There are two types of profits beneficiaries: + +- Normal beneficiary, which means that someone can operate the account through a private key. +- Sub profit scheme, just like the profit scheme created above. +And the sub profit scheme can be allocated a certain share of profits like the normal beneficiary. + +If you're going to add a new normal beneficiary, you can send an `AddBeneficiary` transaction: + +``` +aelf-command send AElf.ContractNames.Profit AddBeneficiary +? Enter the the URI of an AElf node: https://aelf-test-node.aelf.io +? Enter a valid wallet address, if you don't have, create one by aelf-command create: 2WNUjzNPyd7dBwo9a5KG +56AXfPCdwmcydXv4kDyTyFZwTum5nd +? Enter the password you typed when creating a wallet: ******** +✔ Fetching contract successfully! + +If you need to pass file contents as a parameter, you can enter the relative or absolute path of the file + +Enter the params one by one, type `Enter` to skip optional param: +? Enter the required param : 616a605c4221c82bd5c88a7279fbcff4d23b4951010acbbbee24a6afc2b63599 +? Enter the required param : 2HeW7S9HZrbRJZeivMppUuUY3djhWdfVnP5zrDsz8wqq6hK +MfT +? Enter the required param : 1 +? Enter the required param : 10 +? Enter the required param : +The params you entered is: +{ + "schemeId": "616a605c4221c82bd5c88a7279fbcff4d23b4951010acbbbee24a6afc2b63599", + "beneficiaryShare": { + "beneficiary": "2HeW7S9HZrbRJZeivMppUuUY3djhWdfVnP5zrDsz8wqq6hKMfT", + "shares": 1 + }, + "endPeriod": 10 +} +✔ Succeed! +AElf [Info]: +Result: +{ + "TransactionId": "3afa8abf9560815ce43a8d585aa3e1a432711171a0ec2a002b7ba621dfe92dc0" +} +✔ Succeed! +``` + +Then the profit scheme will have the first beneficiary `2HeW7S9HZrbRJZeivMppUuUY3djhWdfVnP5zrDsz8wqq6hKMfT`, +and the shares of this beneficiary is 1. + +If you no longer add other beneficiaries, +the next time the profit scheme is released, +this account will receive all profits; +If another beneficiary with a share of 1 is added, +the two beneficiaries will share the profits equally. + +Now if you use the `GetScheme` method to check your profit scheme: + +``` +aelf-command call AElf.ContractNames.Profit GetScheme +? Enter the the URI of an AElf node: https://aelf-test-node.aelf.io +? Enter a valid wallet address, if you don't have, create one by aelf-command create: 2WNUjzNPyd7dBwo9a5KG +56AXfPCdwmcydXv4kDyTyFZwTum5nd +? Enter the password you typed when creating a wallet: ******** +✔ Fetching contract successfully! + +If you need to pass file contents as a parameter, you can enter the relative or absolute path of the file + +Enter the params one by one, type `Enter` to skip optional param: +? Enter the required param : 616a605c4221c82bd5c88a7279fbcff4d23b4951010acbbbee24a6afc2b63599 +The params you entered is: +"616a605c4221c82bd5c88a7279fbcff4d23b4951010acbbbee24a6afc2b63599" +✔ Calling method successfully! +AElf [Info]: +Result: +{ + "subSchemes": [], + "cachedDelayTotalShares": {}, + "receivedTokenSymbols": [], + "virtualAddress": "Vpb3LDUnkgrVetF1RADywdLbXQ5fY95VeLymgVsM7ti1XUyh4", + "totalShares": "1", + "manager": "2WNUjzNPyd7dBwo9a5KG56AXfPCdwmcydXv4kDyTyFZwTum5nd", + "currentPeriod": "1", + "canRemoveBeneficiaryDirectly": true, + "profitReceivingDuePeriodCount": "10", + "isReleaseAllBalanceEveryTimeByDefault": true, + "schemeId": "616a605c4221c82bd5c88a7279fbcff4d23b4951010acbbbee24a6afc2b63599", + "delayDistributePeriodCount": 0 +} +✔ Succeed! +``` + +You can see the `totalShares` now is 1. + +If you want to add multiple beneficiaries at once, you can use the `AddBeneficiaries` method. +Then these beneficiaries will share the same `endPeriod`. + +And if you want to add sub profit schemes as a beneficiary, instead of providing the beneficiary address, +you need to provide the `subSchemeId` and `subSchemeShares`. +No need to provide `endPeriod` in this case because the lasting period of sub profit schemes will be forever by default +until you remove it via a `RemoveSubScheme` transaction. + diff --git a/docs-sphinx/reference/smart-contract/vote-contract.md b/docs-sphinx/reference/smart-contract/vote-contract.md new file mode 100644 index 0000000000..e78b535512 --- /dev/null +++ b/docs-sphinx/reference/smart-contract/vote-contract.md @@ -0,0 +1,376 @@ +# Vote Contract + +## Overview + +Vote Contract can be used to manage the lifecycle of a voting event. + +When the aelf blockchain equipped with DPoS Consensus is launched, the Vote Contract is used as one of the underlying contracts for node election. +In this scenario, the candidate's public key will be registered as an option for a voting event, +and users can increase the number of votes for their supported nodes by locking ELF tokens. + +Users of aelf blockchain can quickly create their own voting events through the Vote Contract without implementing and deploying a voting contract themselves: +they only need to pay a tiny amount of transaction fees. + +In this article, we will discuss: + +- How to create a voting event +- How to vote for a voting event +- Application of Vote Contract in AElf Node Election +- Vote Contract method explanation + +## How to create a voting event + +In the Vote Contract, the `VotingItem` structure is used to store basic information about a voting event: + +``` +message VotingItem { + // The voting activity id. + aelf.Hash voting_item_id = 1; + // The token symbol which will be accepted. + string accepted_currency = 2; + // Whether the vote will lock token. + bool is_lock_token = 3; + // The current snapshot number. + int64 current_snapshot_number = 4; + // The total snapshot number. + int64 total_snapshot_number = 5; + // The list of options. + repeated string options = 6; + // The register time of the voting activity. + google.protobuf.Timestamp register_timestamp = 7; + // The start time of the voting. + google.protobuf.Timestamp start_timestamp = 8; + // The end time of the voting. + google.protobuf.Timestamp end_timestamp = 9; + // The start time of current round of the voting. + google.protobuf.Timestamp current_snapshot_start_timestamp = 10; + // The sponsor address of the voting activity. + aelf.Address sponsor = 11; +} +``` + +The field `voting_item_id` is the unique identifier of a voting event. +And this voting event can only accept tokens of symbol is `accepted_currency` as valid votes. +Of course, this token need to be created in the MultiToken Contract. + +The `options` filed records all the valid options of current voting event for voters to vote. + +To create a new voting event, users can send a `Register` transaction to Vote Contract. + +The input type of `Register` method is: + +``` +message VotingRegisterInput { + // The start time of the voting. + google.protobuf.Timestamp start_timestamp = 1; + // The end time of the voting. + google.protobuf.Timestamp end_timestamp = 2; + // The token symbol which will be accepted. + string accepted_currency = 3; + // Whether the vote will lock token. + bool is_lock_token = 4; + // The total number of snapshots of the vote. + int64 total_snapshot_number = 5; + // The list of options. + repeated string options = 6; +} +``` + +If you're using the **aelf-command** tool: + +``` +aelf-command send AElf.ContractNames.Vote Register +? Enter the required param : 2023/10/10 13:30 +? Enter the required param : 2023/10/10 14:00 +? Enter the required param : ELF +? Enter the required param : true +? Enter the required param : 5 +? Enter the required param : ["A","B"] +``` + +In this way, you have successfully created a voting event with options "A" and "B" +that accepting users locking their ELF tokens for voting. + +Run this command to get your `VotingItemId` for the voting event you just created +from a `VotingItemRegistered` event: + +``` +aelf-command event -e ${endpoint} ${transactionId} +``` + +From now on, as the sponsor of this voting event, +you can use the `TakeSnapshot` method at any time to save a snapshot of the current vote count for each option. + +The input type of `TaskSnapshot` method is: + +``` +message TakeSnapshotInput { + // The voting activity id. + aelf.Hash voting_item_id = 1; + // The snapshot number to take. + int64 snapshot_number = 2; +} +``` + +If you're using the **aelf-command** tool: + +``` +aelf-command send AElf.ContractNames.Vote TakeSnapshot '{"voteItemId": "${vote_item_id}", "snapshot_number": 1}' +``` + +## How to vote for a voting event + +### Vote + +Users can use `Vote` method to vote to an option of a certain voting event. + +The input type of `Vote` method is: + +``` +message VoteInput { + // The voting activity id. + aelf.Hash voting_item_id = 1; + // The address of voter. + aelf.Address voter = 2; + // The amount of vote. + int64 amount = 3; + // The option to vote. + string option = 4; + // The vote id. + aelf.Hash vote_id = 5; + // Whether vote others. + bool is_change_target = 6; +} +``` + +Among them, the fields `voter` and `vote_id` can be empty if you want to send the `Vote` transaction directly to the Vote Contract. +The `voter` will be the sender by default, and the `vote_id` can only be provided if you want to change your voting target(option). + +If you're using the **aelf-command** tool, +and it's the first time you're voting for this voting event: + +``` +aelf-command send AElf.ContractNames.Vote Vote +? Enter the required param : ${voting_item_id} +? Enter the required param : +? Enter the required param : 5 +? Enter the required param