Skip to main content

Energi Core Technical Documentation

Intention and audience

The documentation aims to demystify the technical architecture of Energi blockchain functionality and further explore the main parts of the ecosystem. The following sections will be dedicated to explicitly define and analyze smart contracts that represent and utilize the main philosophy of Energi blockchain followed by discussion of PoS algorithm implementation for the blockchain consensus.

The target audience of the documentation is people interested in developing Energi Core Node or simply interested in its internal specifications. Most importantly information below represents low level descriptions of the public or internal apis that conduct main processes that sustain main functional systems like reward policies, checkpoint registry or masternode registry operations. Note, understanding Golang and Solidity languages is necessary for understanding Energi smart contracts or project codebase.

We will start by analyzing smart contract functionality by diving into implementations. The following contracts dictate energi ecosystem related processes:

  • Backbone as a reward for the creators.
  • StakerReward as a reward for staking energi coins
  • Treasury. (based on periodic superblocks - every 2 weeks = 20,160 blocks).
  • Masternodes.
  • Governance system for Treasury distribution based on Masternode voting on Proposal.
  • Dynamic blockchain checkpoints to mitigate network split situations.
  • Dynamic blacklists to mitigate fraud incidents.
  • SporkRegistry

As the starting point we need to mention IGovernedContract interface that is the base interface for Energi contracts. The general structure is defined in the following diagram:

alt_text

As seen in the diagram Energi consensus related contracts are GovernedProxy type contracts which means that they have specific property to be governed by predefined proxy address(es). Also contracts dictate block reward distribution; they are necessary to exist upon deploying genesis block.

Energi has defined specific addresses (“0x300”,”0x301”...) for the contracts to reside in. As we only have control only of the genesis block that’s the place the contracts are deployed and ready for blockchain functioning (for instance to be called upon first block generation). As the contracts inherit from IGovernedContract interface they become upgradeable by associated GovernedProxy contract when necessary.

The schematic look is that each GovernedContract has its own GovernedProxy - able to upgrade existing contract to new version. accounts.go lists actual GovernedContracts’ addresses and their proxies: the ones named somethingV1 are an actual GovernedContract, but the ones named just something are a GovernedProxy. Also notice that their addresses differ by +10h: block reward is 0x300, block reward impl is 0x310 etc.

As the contracts are deployed in genesis block they take part in block generation and reward distribution, let's dive into the actual role the contracts have in achieving blockchain consensus and Energi philosophy. For the following pages we dive into each contract to understand its role and functionality on technical level.

IBlockRewardInterface and Reward Distribution

As depicted in the diagram above, IBlockReward is the interface which has to be implemented as a Genesis time governed proxy object that enables specific types of contracts to take participation into block reward distribution. It simply has two functions: getReward and reward. The former returns the amount of coins the entity requests to be rewarded and the latter makes actual transfer to predefined address(es) (actual rewarding process).

Energi will provide a small allocation to mining rewards and will take a bulk of the coin issuance and give it the treasury and masternodes. Energi will also allocate 10% on-going reward to leadership of the Energi Backbone. There are four pillars in the process: StakerReward, BackboneReward, MasternodeRegistry, Treasury. We go through each contract interface and do a detailed analysis of its functionality.

The IGovernedContract Scheme

alt_text

Dynamic blockchain checkpoints

Dynamic checkpoint registry contract is used during blockchain split for network reconciliation. for the correct block. The nodes internally check checkpoint contract events and validate locally if proposed checkpoint is valid for the chain or not.

Backbone as a reward for the creators

The idea behind having a Backbone contract is to reward Energi leadership with the modest share of block reward. The reward function transfers a predefined amount of coins to the backbone address.

StakerReward as a reward for coin stakers

Proof of stake (POS) is another common consensus algorithm that evolved as a low-cost, low-energy consuming alternative compared to Proof of Work. Instead of rewarding miners who solve mathematical problems with the goal of validating transactions and creating new blocks, POS algorithms achieve consensus by requiring users to stake a number of their coins or tokens so as to have a chance of being selected to validate blocks of transactions and get rewarded for doing so.

As the consensus algorithm checks and validates newly generated blocks there is a specific function called that handles block reward distribution. There is a particular transaction in each newly generated block called coinbase that is the address of the staker who is about to generate and broadcast a new block. StakerReward contract functions ensure to make transfer to coinbase address and thus distribute staker share to staker address.

Masternodes

The Energi masternodes keep a full copy of the blockchain and permanently act as active nodes. Additionally, these masternodes validate, store, and broadcast the transactions. In fact, masternodes are in the middle of the ecosystem and are designed with high staking rewards to encourage security and growth.

Masternodes are the pillars of the Energi ecosystem. Their primary role is to serve network stability by validating transactions across the network. Their secondary role is governance, which means helping our decentralized project to make decisions about treasury fund allocation and innovations to the protocol. As with a building, the more pillars it has, the stronger it is.

With Gen 3, masternodes are required to make collateral and will be able to be run with any amount from 1,000 NRG to 100,000 NRG (in increments of 1,000). This will lead to a better experience for masternode operators by enabling masternode rewards to be accumulated more efficiently. All in all, it will let them do their jobs better — as well as improving the security and quality of the entire network.

Importantly, among the masternode-based blockchains, it allocates a maximum number of token to the masternodes. The system releases close to 1 million NRG per month. Moreover, 40% of these NRG coins go to the masternode platform. The overall lifecycle for Masternodes are depicted on the diagram below:

alt_text

Masternode’s main function along with validating transactions and taking part into consensus is taking part in governance which means helping our decentralized project to make decisions about treasury fund allocation and innovations to the protocol. Masternodes represent living entities that must be registered in a MasternodeRegistry contract to be able to take part in Energi network functioning.

For the normal flow new IProposal is created depending on the proposal change it might have different implementation. Masternodes take part in voting or rejecting new proposals. As a result proposals might be finally accepted or rejected depending on masternodes’ activity. For upgrading existing implementation of IGovernedContract the following steps are necessary:

  1. Create new IGovernedContract (with upgraded implementation)
  2. With the GovernedProxy functionality (that is associated with current implementation) we create new UpgradeProposalV1
  3. After the voting process the proposal might be accepted or not.
  4. If a proposal is accepted, the Governed proxy calls upgrade function which makes actual upgrade of the IGovernedContract to new implementation

alt_text

On the diagram the UpgradeProposalV1 is the contract with IProposal interface to enable masternodes accept or deny new IGovernedContract implementation.

Treasury

As part of the block generation there is a finalization step which conducts reward distribution. processBlockRewards function is responsible for distributing rewards. There are two steps: first to calculate exactly the reward amount needed to reward all four entities (treasury, masternode, backbone, staker) and then actual reward distribution step.

The rewarded entities implement the IBlockReward interface that enables them to get information of how much to be rewarded and make actual reward operations. The BlockReward contract (which itself implements the IBlockReward interface) is the entry point from the go codebase during block finalization. It collects the sum of coins that need to be rewarded and then with the second transaction call the actual rewarding process takes place.

The amount of coins that is rewarded to the staker and backbone is currently hardcoded and static during block finalization. Treasury and Masternode systems calculate the reward amount. The masternode is rewarded during superblock generation which is checked with isSuperblock

function that resides in the Treasury contract interface.

Proof of Stake

Energi blockchain consensus is governed by a comparably new proof of stake algorithm which instead of using a proof of work approach (as seen in bitcoin based cryptocurrencies) is based on proof of stake (as share). Network is represented by set of staker and masternode nodes which create consensus.

Stakes run Energi core node application with given wallets (unlocked addresses with reasonable balances) that participate in mining/creating new blocks that are then broadcasted and validated by masternodes. Node to be able to produce a new PoS block needs to comply with certain requirements that enable stakers to produce and, thus, receive staker reward that is dictated by StakerReward smart contract with trivial implementation.

As new blocks are created they consist of one special transaction in the block called coinbase transaction that actually rewards the staker who, according to certain requirements as mentioned above, was able to produce a new PoS block that will become a part of the blockchain.

To analyse proof of stake algorithm let’s follow the main mineV2 function that is responsible for checking if the staking account can produce a new block that corresponds and satisfies the current block difficulty.

For creating a new block it’s necessary for Core Node to have access to staking accounts. Accordingly, the mining process starts with checking for accounts that can be used for claiming to create a new block and are stored in memory with their specific weight parameter which in other words represents how “powerful” current address/balance is to create a new block. We will talk about exactly what determines address weight and how it is calculated.

After storing existing staking accounts the mining process continues with retrieving recent block parameters. This information is used in calculating some parameters that need to be considered for creating the following block. For instance, it’s used to calculate time constraints for new blocks.

For example, maxTime defines the new block max timestamp which might be no more than 3 seconds in the future (as it’s allowed due to diverging time for nodes in the network). Accordingly, minTime defines minimal time after the last block, as it’s shown the new block can be created only after 60 seconds since the last block was mined(creation time). The most important parameter is target which is calculated by calcTimeTargetV2 function and represents the time the next block SHOULD be mined.

As from PoS general algorithm each staking address has a little range of possibility to claim a new block by iterating the time window and checking for each specific time the generated hash satisfies or not given difficulty which is calculated separately for each block. The only way to change the current hash (in order to mine a block), is thus to either change your "staking address", or to change the current block time.

A single wallet typically contains many staking addresses. This is of course the same in a PoS wallet. This is important though, because any address can be used for staking. One of these addresses is what can become the part of a staking transaction to form a valid PoS block.

Finally, there is one more aspect that is changed in the mining process of a PoS block. The difficulty is weighted against the number of coins in the staking transaction. The PoS difficulty ends up being twice as easy to achieve when staking 2 coins, compared to staking just 1 coin. If this were not the case, then it would encourage creating many tiny addresses for staking, which would bloat the size of the blockchain and ultimately cause the entire network to require more resources to maintain, as well as potentially compromise the blockchain's overall security.

So as for this point the target block time and block generation constraining parameters are ready now the algorithm is about to check for each time second allowed by the algorithm if it’s possible to create a new block for given/calculated difficulty. If the checked time is too far in the future we wait until it’s possible for the network to accept our newly created block with that timestamp in the future (which shouldn’t be with no more than 3 seconds in the future).

After checking time related parameters we prepare a PoS difficulty parameter that differs for different block headers for each time interval as it’s calculated by header.time parameter. This function specifically calculates PoS modifier (which is generated by previous/past block parameters’ hash) and difficulty for current block header.

After preparing difficulty parameters we need to check for each staking address/balance if we are able to create a new block. First we try addresses with little balance and move to higher balance addresses. For each address we have weight that is currently calculated and compared to difficulty as if it’s able to create a new block in calcPoSHash function. If the condition is satisfied we claim a new block with already set header parameters that are finally signed by staking address and then broadcast to the network.

The following article defines one of the PoS algorithms. All other variations share the same internal structure mentioned below

Proof of Stake's Protocol Structures and Rules

Now enter Proof of Stake. We have these goals for PoS:

  1. Impossible to counterfeit a block
  2. Big players do not get disproportionally bigger rewards
  3. More computing power is not useful for creating blocks
  4. No one member of the network can control the entire blockchain

The core concept of PoS is very similar to PoW, a lottery. However, the big difference is that it is not possible to "get more tickets" to the lottery by simply changing some data in the block. Instead of the "block hash" being the lottery ticket to judge against a target, PoS invents the notion of a "kernel hash".

The kernel hash is composed of several pieces of data that are not readily modifiable in the current block. And so, because the miners do not have an easy way to modify the kernel hash, they can not simply iterate through a large amount of hashes like in PoW.

Proof of Stake blocks add many additional consensus rules in order to realize it's goals. First, unlike in PoW, the coinbase transaction (the first transaction in the block) must be empty and reward 0 tokens. Instead, to reward stakers, there is a special "stake transaction" which must be the 2nd transaction in the block. A stake transaction is defined as any transaction that:

  1. Has at least 1 valid vin
  2. It's first vout must be an empty script
  3. It's second vout must not be empty

Furthermore, staking transactions must abide by these rules to be valid in a block:

  1. The second vout must be either a pubkey (not pubkeyhash!) script, or an OP_RETURN script that is unspendable (data-only) but stores data for a public key
  2. The timestamp in the transaction must be equal to the block timestamp
  3. the total output value of a stake transaction must be less than or equal to the total inputs plus the PoS block reward plus the block's total transaction fees. output <= (input + block_reward + tx_fees)
  4. The first spent vin's output must be confirmed by at least 500 blocks (in otherwords, the coins being spent must be at least 500 blocks old)
  5. Though more vins can used and spent in a staking transaction, the first vin is the only one used for consensus parameters.

These rules ensure that the stake transaction is easy to identify, and ensures that it gives enough info to the blockchain to validate the block. The empty vout method is not the only way staking transactions could have been identified, but this was the original design from Sunny King and has worked well enough.

Now that we understand what a staking transaction is, and what rules they must abide by, the next piece is to cover the rules for PoS blocks:

  • Must have exactly 1 staking transaction
  • The staking transaction must be the second transaction in the block
  • The coinbase transaction must have 0 output value and a single empty vout
  • The block timestamp must have it's bottom 4 bits set to 0 (referred to as a "mask" in the source code). This effectively means the blocktime can only be represented in 16 second intervals, decreasing it's granularity
  • The version of the block must be 7
  • A block's "kernel hash" must meet the weighted difficulty for PoS
  • The block hash must be signed by the public key in the staking transaction's second vout. The signature data is placed in the block (but is not included in the formal block hash)
  • The signature stored in the block must be "LowS", which means consisting only of a single piece of data and must be as compressed as possible (no extra leading 0s in the data, or other opcodes)
  • Most other rules for standard PoW blocks apply (valid merkle hash, valid transactions, timestamp is within time drift allowance, etc)

There are a lot of details here that we'll cover in a bit. The most important part that really makes PoS effective lies in the "kernel hash". The kernel hash is used similar to PoW (if hash meets difficulty, then block is valid). However, the kernel hash is not directly modifiable in the context of the current block. We will first cover exactly what goes into these structures and mechanisms, and later explain why this design is exactly this way, and what unexpected consequences can come from minor changes to it.

The Proof of Stake Kernel Hash

The kernel hash specifically consists of the following exact pieces of data (in order):

  • Previous block's "stake modifier" (more detail on this later)
  • Timestamp from "prevout" transaction (the transaction output that is spent by the first vin of the staking transaction)
  • The hash of the prevout transaction
  • The output number of the prevout (ie, which output of the transaction is spent by the staking transaction)
  • Current block time, with the bottom 4 bits set to 0 to reduce granularity. This is the only thing that changes during staking process

The stake modifier of a block is a hash of exactly:

  • The hash of the prevout transaction in PoS blocks, OR the block hash in PoW blocks.
  • The previous block's stake modifier (the genesis block's stake modifier is 0)

The only way to change the current kernel hash (in order to mine a block), is thus to either change your "prevout", or to change the current block time.

A single wallet typically contains many UTXOs. The balance of the wallet is basically the total amount of all the UTXOs that can be spent by the wallet. This is of course the same in a PoS wallet. This is important though, because any output can be used for staking. One of these outputs are what can become the "prevout" in a staking transaction to form a valid PoS block.

Finally, there is one more aspect that is changed in the mining process of a PoS block. The difficulty is weighted against the number of coins in the staking transaction. The PoS difficulty ends up being twice as easy to achieve when staking 2 coins, compared to staking just 1 coin. If this were not the case, then it would encourage creating many tiny UTXOs for staking, which would bloat the size of the blockchain and ultimately cause the entire network to require more resources to maintain, as well as potentially compromise the blockchain's overall security.

So, if we were to show some pseudo-code for finding a valid kernel hash now, it would look like:

while(true){
foreach(utxo in wallet){
blockTime = currentTime - currentTime % 16
posDifficulty = difficulty * utxo.value
hash = hash(previousStakeModifier << utxo.time << utxo.hash << utxo.n << blockTime)
if(hash < posDifficulty){
done
}
}
wait 16s -- wait 16 seconds, until the block time can be changed
}

This code isn't so easy to understand as our PoW example, so we will attempt to explain it in plain english:

Do the following over and over for infinity:

Calculate the blockTime to be the current time minus itself modulus 16 (modulus is like dividing by 16, but then only instead of taking the result, taking the remainder)

Calculate the posDifficulty as the network difficulty, multiplied by the number of coins held by the UTXO.

Cycle through each UTXO in the wallet. With each UTXO, calculate a SHA256 hash using the previous block's stake modifier, as well as some data from the the UTXO, and finally the blockTime. Compare this hash to the posDifficulty. If the hash is less than the posDifficulty, then the kernel hash is valid and you can create a new block.

After going through all UTXOs, if no hash produced is less than the posDifficulty, then wait 16 seconds and do it all over again.

Now that we have found a valid kernel hash using one of the UTXOs we can spend, we can create a staking transaction. This staking transaction will have 1 vin, which spends the UTXO we found that has a valid kernel hash. It will have (at least) 2 vouts. The first vout will be empty, identifying to the blockchain that it is a staking transaction. The second vout will either contain an OP_RETURN data transaction that contains a single public key, or it will contain a pay-to-pubkey script. The latter is usually used for simplicity, but using a data transaction for this allows for some advanced use cases (such as a separate block signing machine) without needlessly cluttering the UTXO set.

Finally, any transactions from the mempool are added to the block. The only thing left to do now is to create a signature, proving that we have approved the otherwise valid PoS block. The signature must use the public key that is encoded (either as pay-pubkey script, or as a data OP_RETURN script) in the second vout of the staking transaction. The actual data signed in the block hash. After the signature is applied, the block can be broadcast to the network. Nodes in the network will then validate the block and if it finds it valid and there is no better blockchain then it will accept it into it's own blockchain and broadcast the block to all the nodes it has connection to.

Now we have a fully functional and secure PoSv3 blockchain. PoSv3 is what we determined to be most resistant to attack while maintaining a pure decentralized consensus system (ie, without master nodes or currators). To understand why we approached this conclusion however, we must understand it's history.