AMO protocol version 4.
Although the current implementation of AMO blockchain depends heavily on Tendermint, AMO blockchain protocol itself is independent of Tendermint. It is described by several protocol messages and corresponding state transition in abstract internal database of each blockchain node. While the protocol messages are concretely defined(meaning and format), abstract internal database of a blockchain node is implementation-specific. But, note that every AMO blockchain node MUST incorporate a kind of database storing all kinds of data items described in Blockchain Data section.
Some notes related to Tendermint will be tagged with TM.
AMO blockchain uses ECDSA key pair to sign and verify various transactions and messages. AMO blockchain uses NIST P256 curve(aka. secp256r1) and SHA256 as its default ECDSA domain parameter.
A key pair in AMO blockchain is a pair of a private key and a public key. A private key is a sequence 32 bytes, and a public key is a sequence of 65 bytes(uncompressed form with 0x04 prefix). These byte sequences are represented by HEX encoding when transmitted over a communication channel or stored as a marshaled form, while they may reside as other format in a program's internal memory space.
A private key should NEVER be transmitted via a network communication channel. A public key must be HEX-encoded in a protocol message.
The following types are used in this document.
_HEX_encoded_public_key_bytes_
A signature field has the following form:
{
"pubkey": "_HEX_encoded_public_key_bytes_",
"sig_bytes": "_HEX_encoded_signature_bytes_"
}pubkey is the signer's public key, and sig_bytes is HEX-encoded ECDSA
signature bytes, which is a concatenation of r and s. (r, s) = ECDSA(privkey, sb) is output of ECDSA signature algorithm, where privkey is
the signer's private key.
A validator key pair is a ed25519 key pair and handled by Tendermint, but validator's public key is carried in a AMO blockchain protocol message when staking AMO coin to acquire stakes. In this case, a validator's public key must be HEX-encoded.
The following types are used in this document.
_HEX_encoded_ed25519_pubkey_
A key custody is a special form of key transfer medium. It is recommended to be
a public-key encryption of a data encryption key PKEnc(PK, DEK), where
PKEnc is a sort of a hybrid encryption (combination of public key encryption
and symmetric key encryption). For PKEnc, we use a combination of ECDH
ephemeral mode and AES-256. For ECDH ephemeral key generation, we reuse ECDSA
key generation algorithm. PK is a public key of a recipient and DEK is a
data encryption key of an encrypted data parcel.
The following types are used in this document.
_HEX_encoded_key_custody_
An address is a human-readable character string which is a hex-encoding of a
byte sequence with the length of 20 bytes (=160-bit). Hence, the opaque form of
an address is a 40-byte character string which consists of [0-9] and [A-F]
only.
An account address is derived from the public key of an account. First, take 32
bytes by applying SHA256 on the public key bytes. Next, take 20 bytes by
truncating the first 20 bytes from the 32-byte SHA256 output: addr_bin = trunc_20(SHA256(PK)). For the last step, convert this addr_bin by
HEX-encoding. An AMO-compliant program may utilize this addr_bin for its
internal purpose, but it should apply hex-encoding before sending to other
protocol party or storing to other medium outside the program.
NOTE: In Bitcoin, they use addr_bin = RIPEMD160(SHA256(PK)), but we
cannot use RIPEMD160. See Notes on Cryptography for more details
and reasons.
The following types are used in this document.
_account_address_=addr_bin_HEX_encoded_account_address_= HEX encoding of_account_address_"_HEX_encoded_account_address_"as a JSON string
As in other popular blockchain systems, AMO coin amount is expressed as an integer value, which is a multiple of the smallest transferable unit. In AMO blockchain the unit is called a mote. And one AMO is 1018 motes, where the number 18 is often called a decimals in the cryptocurrency community. In all protocol messages, AMO coin amount is expressed in mote unit.
Amount of AMO coin or user-defined coin must be expressed as a decimal number enclosed in double-quotes when included in a JSON-format message, i.e. all protocol messages. However it may be expressed in other formats in blockchain node's internal memory.
The following types are used in this document.
_currency_"_currency_"as a JSON string
A draft ID is a 32-bit unsigned integer. It is represented as a double-quoted decimal number without redundant leading zeroes when used in JSON, e.g. in protocol messages. However, it is represented as a 4-byte big-endian integer including leading zeroes when it is used to composite another identifier.
The following types are used in this document.
_draft_id_= alias of_decimal_number__draft_id_as a JSON number, e.g.1234not"1234"
A storage ID is a 32-bit unsigned integer. It is represented as a double-quoted decimal number without redundant leading zeroes when used in JSON, e.g. in protocol messages. However, it is represented as a 4-byte big-endian integer when it is used to composite another identifier.
The following types are used in this document.
_storage_id_= alias of_decimal_number__storage_id_as a JSON number, e.g.1234not"1234"
A parcel ID is a concatenation of a storage ID and in-storage ID. In-storage ID
is a 32-byte(256-bit) binary sequence. See AMO Storage
Specification for more detail. Be aware that a storage ID itself
is a 32-bit unsigned integer. But when forming a parcel ID, this storage ID is
converted as a four-byte binary sequence (big-endian integer). For example,
suppose a parcel in a storage with the id of 123456789 has the in-storage id
of 12ABEF23.... This parcel has a parcel id 075BCD1512ABEF23... in a HEX
encoding, where 075BCD15 is a HEX encoding of the integer 123456789 using
big-endian byte order.
The following types are used in this document.
_parcel_id__HEX_encoded_parcel_id_= HEX encoding of_parcel_id_"_HEX_encoded_parcel_id_"as a JSON string
A UDC ID is a 32-bit unsigned integer. It is represented as a double-quoted decimal number without redundant leading zeroes when used in JSON, e.g. in protocol messages. However, it is represented as a 4-byte big-endian integer when it is used to composite another identifier.
The following types are used in this document.
_udc_id_= alias of_decimal_number__udc_id_as a JSON number, e.g.1234not"1234"
register, request and grant tx may carry extra information. It must be a
JSON object, but its internal structure is application-specific. Internal DB of
a blockchain node must store extra information from the previous steps also,
i.e. parcel stores extra from register tx, request stores extra from
register tx and request tx, usage stores extra from register tx, request tx
and grant tx.
extra in parcel store
{
"register": {} // application-specific JSON object, optional
}extra in request store
{
"register": {}, // application-specific JSON object, optional
"request": {} // application-specific JSON object, optional
}extra in usage store
{
"register": {}, // application-specific JSON object, optional
"request": {}, // application-specific JSON object, optional
"grant": {} // application-specific JSON object, optional
}Since a JSON object must be enclosed by braces({ and }), it cannot be a
single JSON value. Each extra info must be an empty object({}) or a proper
JSON object with members.
{
"register": "boo", // wrong
"request": {"some":"value"}, // ok
"grant": {} // ok
}Each member is marked as optional, so an empty object({}) is valid extra
information for all of three state stores.
A transaction is a description of the state change in a blockchain node's internal database(i.e. blockchain state). In other words, sending a transaction to a blockchain node is the only way to trigger the change in a blockchain state. When a transaction is received by a node and eventually included in a block, a blockchain node shall modify the internal database according to each transaction type.
A transaction is represented by a JSON document which has the following context:
{
"type": "_tx_type_",
"sender": "_HEX_encoded_account_address_",
"fee": "_currency_",
"last_height": "_decimal_number",
"payload": {}, // tx-specific JSON object
"signature": {
"pubkey": "_HEX_encoded_public_key_bytes_",
"sig_bytes": "_HEX_encoded_signature_bytes_"
}
}It is irrelevant whether it is in compact or pretty form.
type identifies a transaction type. The value _tx_type_ is one of the
following:
- coins and stakes
transferstakewithdrawdelegateretract
- governance
proposevote
- storage
setupclose
- parcels
registerrequestgrantdiscardcancelrevoke
- did
claimdismiss
- user-defined coin
issueburnlock
sender identifies the sender or originator of this transaction. fee is
amount of AMO coin expected to get transferred to a block proposer after the
transaction is committed to a block. last_height is the last height of AMO
blockchain at the time creating the transaction. payload is a JSON object,
which is specific for each transaction type.
signature is an ECDSA signature of the sender on the compact JSON
representation of a transaction with all the HEX-encoded string in upper
case as the following:
{"type":"transfer","sender":"662E3DD1C6470CFE12C8EDBCE5F44C08E2763753","fee":"0","last_height":"4052","payload":{"to":"614A9F2FC4E6B119D7612C35BC150E33CB38BB40","amount":"100"}}A signed transaction is as the following:
{"type":"transfer","sender":"662E3DD1C6470CFE12C8EDBCE5F44C08E2763753","fee":"0","last_height":"4052","payload":{"to":"614A9F2FC4E6B119D7612C35BC150E33CB38BB40","amount":"100"},"signature":{"pubkey":"04DBCEC2C0F52018606F588713305E1DA49367037281B960F51C46BE64E3144977009A811A865B3CB3331B788147C03853C7920C4C8FB6FFB5B0D435DAEB3F59A4","sig_bytes":"50A8307AAFF6611AE67ADD09EA813F37668072A214230DF375CFA25FB368B0EBD861943661EC690AE0E5D789E738B3C4518F78D768E5E006C9EB53E81821671D"}}TM: Tendermint receives transactions via tendermint-specific RPC channel. For the exact RPC message format, see AMO Client RPC Specification.
A payload format for each transaction type is as the following.
-
transferpayload:{ "udc": _udc_id_, // optional "to": "_HEX_encoded_account_address_", "amount": "_currency_" }where
udcis an optional identifier of a user-defined coin,tois recipient of the transfer, andamountis amount AMO coin or user-defined coin._udc_id_must be one of registered user-defined coin ID. Ifudcis omitted, transfer AMO coin, which is the default._currency_is a string representation of a decimal number. -
stakepayload:{ "validator": "_HEX_encoded_ed25519_pubkey_", "amount": "_currency_" }where
validatoris the only public key type other than P256 public key used in AMO blockchain protocol. It must be obtained from underlying Tendermint node, but in HEX encoding, not Base64 encoding.amountis amount of AMO coin to be locked as stake. -
withdrawpayload:{ "amount": "_currency_" }where
amountis amount of AMO coin to be withdrawn from stake. -
delegatepayload:{ "to": "_HEX_encoded_account_address_", "amount": "_currency_" }where
tois an address of an account which has stakes already andamountis amount of AMO coin to be delegated. -
retractpayload:{ "amount": "_currency_" }where
amountis amount of AMO coin to be retracted from delegated stake. -
proposepayload{ "draft_id": "_draft_id_", "config": {}, // application-specific JSON object "desc": "human-readable string describing this draft" }where
configis an optional field which is necessary for a proposal of applying of new configuration on-chain. -
votepayload{ "draft_id": "_draft_id_", "approve": true // boolean }where
approveindicatessender's opinion ondraft_id;truefor approval orfalsefor rejection. -
setuppayload{ "storage": _storage_id_, // integer "url": "_url_", "registration_fee": "_currency_", "hosting_fee": "_currency_" } -
closepayload{ "storage": _storage_id_ // integer } -
registerpayload:{ "target": "_HEX_encoded_parcel_id_", "custody": "_HEX_encoded_key_custody_", "proxy_account": "_HEX_encoded_account_address_", // optional "extra": {} // application-specific JSON object, optional }where
targetis the id of a parcel currently being registered,custodyis a encrypted key material used to encrypt the data parcel body, and the key material is encrypted by the owner(seller)'s public key. -
requestpayload:{ "target": "_HEX_encoded_parcel_id_", "payment": "_currency_", "recipient": "_HEX_encoded_account_address_", // optional "dealer": "_HEX_encoded_account_address_", // optional "dealer_fee": "_currency_", // optional "extra": {} // application-specific JSON object, optional }where
targetis the id of a parcel for which the sender wants usage grant,paymentis amount of AMO coin to be collected by the seller,recipientis the address of a recipient explictly designated to get granted a usage on the parcel. In order fordealer_feeto work, both ofdealeranddealer_feemust be valid. -
grantpayload{ "target": "_HEX_encoded_parcel_id_", "recipient": "_HEX_encoded_account_address_", "custody": "_HEX_encoded_key_custody_", "extra": {} // application-specific JSON object, optional }where
targetis the id of a parcel currently being granted,recipientis the address of a recipient,custodyis a encrypted key material used to encrypt the data parcel body, and the key material is encrypted by the buyer's public key. -
discardpayload{ "target": "_HEX_encoded_parcel_id_" }where
targetis the id of a parcel currently being discarded. -
cancelpayload{ "target": "_HEX_encoded_parcel_id_", "recipient": "_HEX_encoded_account_address_" // optional }where
targetis the id of a parcel which the sender requested previously,recipientis the address of a recipient designated to get granted a usage on the parcel. -
revokepayload{ "target": "_HEX_encoded_parcel_id_", "recipient": "_HEX_encoded_account_address_" }where
targetis the id of a parcel currently being revoked,recipientis the address of a buyer which is previously granted a usage on the parcel. -
claimpayload{ "target": "_string_", "document": {} // DID document in the form of JSON object }where
targetis a JSON string conforming toidcharin DID syntax.documentvalue will be stored as a compact representation. Whenclaimtx is received on a previously claimedtarget,documentwill be replaced with a new one. -
dismisspayload{ "target": "_string_", }effectively removes the DID document from the DID registry.
-
issuepayload{ "udc": _udc_id_, "desc": "human-readable string describing this user-defined coin", "operators": [ "_HEX_encoded_account_address_", ... ], "amount": "_currency_" }where
operatorsis an optional list of operator addresses, andamountis the amount of UDC balance to be created. -
lockpayload{ "udc": _udc_id_, "holder": "_HEX_encoded_account_address_", "amount": "_currency_" }where
udcis an identifier of a user-defined coin, andamountis the amount of UDC coin to be locked. -
burnpayload{ "udc": _udc_id_, "amount": "_currency_" }where
udcis an identifier of a user-defined coin, andamountis the amount of UDC balance to burn.
AMO blockchain state is an exact snapshot of all the active data items.
Typically, this state is stored as a key-value database, but the exact method
for managing this database may be different for each implementation. One
obligation is that every blockchain node must be able to calculate the same
app_hash for a given block height, and all kinds of implementation must have
the same semantic meaning. This section describes the data format suitable for
calculating app_hash.
The state database stores data items in separate logical state stores according to the data type. To distinguish between logical state stores, each state store has unique prefix for the database key. A prefix is a human-readable ASCII string, but it is treated as a byte array when concatenating with the in-store data item key.
There is a top-level data item not associated with any logical state store. This
item has the key as config and the value is a JSON marshaled blockchain
configuration.
{
"max_validators": 100,
"weight_validator": 2,
"weight_delegator": 1,
"min_staking_unit": "_currency_",
"blk_reward": "_currency_",
"tx_reward": "_currency_",
"penalty_ratio_m": 0.1,
"penalty_ratio_l": 0.01,
"laziness_window": 100,
"laziness_threshold": 90,
"hibernate_threshold": 10,
"hibernate_period": 1000,
"block_binding_window": 100,
"lockup_period": 3600,
"draft_open_count": 500000,
"draft_close_count": 100000,
"draft_apply_count": 500000,
"draft_deposit": "_currency_",
"draft_quorum_rate": 0.1,
"draft_pass_rate": 0.7,
"draft_refund_rate": 0.2,
"upgrade_protocol_height": 1,
"upgrade_protocol_version": 1
}| key | value type | value constraint |
|---|---|---|
max_validators |
uint64 | > 0 |
weight_validator |
float64 | > 0 |
weight_delegator |
float64 | > 0 |
min_staking_unit |
currency | > 0 |
blk_reward |
currency | >= 0 |
tx_reward |
currency | >= 0 |
penalty_ratio_m |
float64 | > 0 |
penalty_ratio_l |
float64 | > 0 |
laziness_window |
int64 | >= 10000 |
laziness_threshold |
int64 | > 0 |
hibernate_threshold |
int64 | >= 10000 |
hibernate_period |
int64 | > 0 |
block_binding_window |
int64 | >= 10000 |
lockup_period |
int64 | >= 10000 |
draft_open_count |
int64 | >= 10000 |
draft_close_count |
int64 | >= 10000 |
draft_apply_count |
int64 | >= 10000 |
draft_deposit |
currency | >= 0 |
draft_quorum_rate |
float64 | > 0 |
draft_pass_rate |
float64 | > 0 |
draft_refund_rate |
float64 | > 0 |
upgrade_protocol_height |
int64 | > app.state.Height + draft_open_count + draft_close_count + draft_apply_count |
upgrade_protocol_version |
uint64 | == app.state.ProtocolVersion + 1 |
It is mandatory to restrict proper type and value of configurations in order to
make AMO blockchain protocol keep operating as it has to, even after modifying
their values since genesis block. The currency-related configurations' type is
restricted to string as it can store values without limit. Even though it is
highly recommended to use uint64 on configurations for its better space
availability than int64, laziness_window, block_binding_window,
lockup_period, draft_*_count, and upgrade_protocol_height have to use
int64 as it is an tendermint-dependant configuration.
There are 12 default state stores and optional UDC(user-defined coin) balance and balance lock stores.
| tier | category | store | prefix |
|---|---|---|---|
| 0 | fungible asset | AMO coin balance | balance: |
| 0 | fungible asset | stake | stake: |
| 0 | fungible asset | delegate | delegate: |
| 0 | maintenance | hibernate | hibernate: |
| 1 | governance | draft | draft: |
| 1 | governance | vote | vote: |
| 2 | maintenance | storage | storage: |
| 2 | non-fungible asset | parcel | parcel: |
| 2 | non-fungible asset | request | request: |
| 2 | non-fungible asset | usage | usage: |
| 2 | non-fungible asset | did | did: |
| 3 | maintenance | UDC | udc: |
| 3 | maintenance | UDC balance lock | udclock:<udc_id>: |
| 3 | fungible asset | UDC balance | balance:<udc_id>: |
Tier 0 items are essential for the operations of a DPoS-based blockchain. Tier 1 items are important as much as the tier 0 items, but the chain may be still called a functional blockchain without them. Tier 2 items defines the core business data items, while tier 3 items are pretty much optional.
- default coin balance
- key:
_account_address_ - value: JSON string
"_currency_" - key is the owner of a coin balance
- key:
- stake
- key:
_account_address_ - value: compact representation of a JSON object
{ "validator": "_HEX_encoded_ed25519_pubkey_", "amount": "_currency" } - key is the sender of a stake tx
- key:
- delegate
- key:
_account_address_ - value: compact representation of a JSON object
{ "delegatee": "_HEX_encoded_accont_address_", "amount": "_currency_" } - key is the sender of a delegate tx
- NOTE: For delegate store, a key to the database is just
_account_address_, instead of a concatenation of holder address and delegatee address. This means that a user can have only one delegated stake. In other words, a user cannot delegate his/her stakes to multiple delegatees. While an AMO-compliant node can freely choose the actual database implementation, this constraint must be enforced in any way. An implementor may choose to keep this_account_address_as a unique key, or use more loose database implementation with an application code or a wrapper layer to keep this constraint on top of it.
- key:
- hibernate
- key:
_validator_address_ - value: compact representation of a JSON object
{ "start": _block_height_, "end": _block_height_ }
- key:
- draft
- key:
_draft_id_(big-endian) - value: compact representation of a JSON object
{ "proposer": "_HEX_encoded_account_address_", "config": {}, "desc": "_human_readable_string_describing_this_draft_", "open_count": "_decimal_number_", "close_count": "_decimal_number_", "apply_count": "_decimal_number_", "deposit": "_currency_", "tally_approve": "_currency_", "tally_reject": "_currency_" } configkeys should be a subset of the top-levelconfigitem. The values may be omitted if they should remain the same. There should be no multiple live drafts having config change items conflicting with each other.*_countcontrol overall voting process until the draft being passed and applied to the blockchain configuration. They are initialized according to the configuration at the time of being proposed.open_countis decremented at each block progress, and when it reaches zeroclose_countis decremented afterwards. Whenclose_countreaches zero and the vote summary is approval, thenapply_countis decremented until the new configuration is applied.tally_*fields count votes cast upon this draft.tally_approveandtally_rejectare as the names imply.
- key:
- vote
- key:
_draft_id_(big-endian) +_account_address_ - value: compact representation of a JSON object
{ "approve": true // boolean }
- key:
- storage
- key:
_storage_id_(big-endian) - value: compact representation of a JSON object
{ "owner": "_HEX_encoded_account_address_", "url": "_url_", "registration_fee": "_currency_", "hosting_fee": "_currency_", "active": _bool_ }
- key:
- parcel
- key:
_parcel_id_ - value: compact representation of a JSON object
{ "owner": "_HEX_encoded_account_address_", "custody": "_HEX_encoded_key_custody_", "proxy_account": "_HEX_encoded_account_address_", "extra": {} // application-specific JSON object } - key is the
targetof a register tx owneris the sender of a register tx
- key:
- request
- key:
_account_address_+_parcel_id_ - value: compact representation of a JSON object
{ "payment": "_currency_", "agency": "_HEX_encoded_account_address_", // optional "dealer": "_HEX_encoded_account_address_", // optional "dealer_fee": "_currency_", // optional "extra": {} // application-specific JSON object } - key is a concatenation of (sender or
recipient) andtargetof a request tx
- key:
- usage
- key:
_account_address_+_parcel_id_ - value: compact representation of a JSON object
{ "custody": "_HEX_encoded_key_custody_", "extra": {} // application-specific JSON object } - key is a concatenation of
recipientandtargetof a grant tx
- key:
- did
- key:
_did_address_ - value: compact representation of a JSON object
{ "owner": "_HEX_encoded_account_address_", "document": {} // DID documentn in the form of JSON object } - key:
- udc(user-defined coin)
- key:
_udc_id_ - value: compact representation of a JSON object
{ "owner": "_HEX_encoded_account_address_", "desc": "human-readable string describing this user-defined coin", "operators": [ "_HEX_encoded_account_address_", ... ], "total": "_currency_" } - key is
idof an issue tx owneris the sender of an initial issue tx
- key:
- udc balance
- key:
_account_address_ - value: JSON string
"_currency_"
- key:
- udc balance lock
- key:
_account_address_ - value: JSON string
"_currency_" - UDC balance of an account cannot be lowered under this value via transfer tx. This lock value may be higher than the UDC balance of an account at the time of processing lock tx
- key:
Although the internal state DB is composed of top-level data items and several logical state stores, its actual form is a linear key-value database. In viewpoint of state management, it suffices to manage this database in any form as long as the contents are equivalent. However, in order to interact with the underlying Tendermint consensus engine, we need to calculate app hash from the state DB contents. Every blockchain node must be able to calculate the same app hash from the equivalent state DB contents. To calculate this app hash, we assume that the database is stored as a Merkle tree.
Every data item in the database is stored as a leaf node in a Merkle tree with
the key as the concatenation of the prefix and in-store key. The leaf nodes are
sorted by the key and they are labeled with a hash derived from hash(key + value). A pair of leaf nodes generates a one-level higher inner node labeled
with hash(ln1_hash + ln2_hash). In the similar way, another one-level higher
inner node is added to the merkle tree with the label of hash(in1_hash + in2_hash). The above process is repeated until only one single root node
appears at the top of the merkle tree. The resulting app hash is the hash label
of the root node.
TM: This app hash is calculated every time a new block is committed and stored as app hash in the next block. App hash is to provide an evidence that every blockchain node hash the same state DB contents for a given block height.
This section describes how the AMO blockchain state is changed when a transaction is included in a block or a block is completed. There shall be no other state change than described in this section.
In following subsections, blk.incentive is accumulated from the beginning of
a block until the end of a block. When completing a block, this incentive is
distributed among the validator who produced a block and the users who
delegated stakes to the validator.
TM: These operations are implemented by DeliverTx and EndBlock method
in the ABCI application.
Upon receiving a transfer transaction from an account, an AMO blockchain node
performs a validity check and transfers coins from sender's balance to
recipient's balance when the transaction is valid.
- validity check
tx.amount>0sender.balance≥tx.amount+tx.fee
- state change
sender.balance←sender.balance-tx.amount-tx.feeto.balance←to.balance+tx.amountblk.incentive←blk.incentive+tx.fee
When optional parameter udc is given, the operation is changed as follows.
- validity check
- UDC id
<udc>is registered tx.amount>0<udc>.sender.balance≥<udc>.sender.lock+tx.amountsender.balance≥tx.fee
- UDC id
- state change
<udc>.sender.balance←<udc>.sender.balance-tx.amount<udc>.to.balance←<udc>.to.balance+tx.amountsender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
Upon receiving a stake transaction from an account, an AMO blockchain node
performs a validity check and locks requested coins to stake store and
decreases the sender's balance when the transaction is valid.
- validity check
tx.amount>0tx.amount % config.minimum_staking_unit==0(check staking unit restriction)sender.balance≥tx.amount+tx.fee- There is no other account
holderhavingholder.stake.validator==tx.validator sender.stake.validator==tx.validatorifsender.stakeexists
- state change
sender.balance←sender.balance-tx.amount-tx.feesender.stake.amount←sender.stake.amount+tx.amountsender.stake.locked_height←config.lockup_periodblk.incentive←blk.incentive+tx.fee
Upon receiving a withdraw transaction from an account, an AMO blockchain node
performs a validity check and relieves requested coins from stake store and
increases the account's balance when the transaction is valid.
- validity check
tx.amount>0sender.balance≥tx.feesender.stake.unlocked==truesender.stake.amount≥tx.amountsender.stake.amount>tx.amountif this account is a delegatee for any of delegated stakessender.stake.delegateis empty
- state change
sender.stake.amount←sender.stake.amount-tx.amountsender.balance←sender.balance-tx.fee+tx.amountblk.incentive←blk.incentive+tx.fee
TODO: need rounding? or currency to stake ratio?
Stake Lock-up
This feature locks a newly added stake for a certain period of time. The time
is measured in terms of number of blocks. If a stake is set at the block height
h, the stake can be withdrawn after the block height reaches h + l, where
l is the pre-configured lock-up period.
Upon receiving a stake transaction from an account, an AMO blockchain node
records the stake in LockedStake with l. Then, the stake's l decreases by
1 block height per block creation. When l becomes 0, the stake gets removed
from LockedStake and put into UnlockedStake.
Block Progress(Creation) Condition
As tendermint's create_empty_blocks config is set to false on an AMO
blockchain node, the block is progressed only if there is a change of
appHash, the root hash value of State merkle tree. The conditions in which
the appHash can change are as follows:
- Successfully delivered(processed) transactions
- Stakes in
LockedStake
Even though there is no transaction to process on an AMO blockchain node, the
appHash can change. The lock-period l of locked stakes decrease by 1 block
height and it is written in State, resulting in the change of appHash.
There may be users who have the intention to participate in the block production but don't have enough stake value or computing power to competent in the validator selection race. In this case, a user can delegate his/her stake to a more competent validator.
Upon receiving a delegate transaction from an account, an AMO blockchain node
performs a validity check and locks requested coins to delegate store and
decreases the account's balance when the transaction is valid.
- validity check
tx.amount>0tx.amount%config.minimum_staking_unit==0(check staking unit restriction)sender.balance≥tx.fee+tx.amounttx.toaddress already has a positive stake instakestore- the
senderhas no previous delegatee ortx.tois the same as the previous delegatee
- state change
sender.balance←sender.balance-tx.fee-tx.amountblk.incentive←blk.incentive+tx.feesender.delegate.amount←sender.delegate.amount+tx.amount
Upon receiving a retract transaction from an account, an AMO blockchain node
performs a validity check and relieves requested coins from delegate store
and increases the account's balance when the transaction is valid.
- validity check
tx.amount>0sender.balance≥tx.feesender.delegate.amount≥tx.amount
- state change
sender.delegate.amount←sender.delegate.amount-tx.amountsender.balance←sender.balance-tx.fee+tx.amountblk.incentive←blk.incentive+tx.fee
NOTE: sender.delegate is a stake value in the delegate store where
the address is the sender account.
When it is necessary to modify the configuration of AMO blockchain without hard-forking the chain, one of the validators can propose a draft containing the configuration to get applied with its description and deposit. Then, the validators vote for or against it. For the draft to get processed further after the vote is closed, the draft must have a quorum for voting. If not, the votes for draft are ignored no matter what the final result of votes is. On the other hand, if quorum is met, the draft would get applied or not, according to its final result. Also, if turnout of voters is below refund rate, the draft deposit is distributed among the validators who participate in voting. Otherwise, it is returned to the proposer.
Upon receiving a propose transaction from an account, an AMO blockchain node
performs a validity check and add a record in draft store.
- validity check
- there is no other draft in progress
- there is no record having
tx.draft_idas a key indraftstore senderis one ofblk.validatorssender.balance≥config.draft_deposit+tx.feetx.draft_id==state.latest_draft_id+ 1
- state change
- add new record having
tx.draft_idas a key indraftstore sender.balance←sender.balance-config.draft_deposit-tx.feeblk.incentive←blk.incentive+tx.fee
- add new record having
Upon receiving a vote transaction from an account, an AMO blockchain node
performs a validity check and add a record in vote store.
- validity check
tx.draft_idis in progresssender!=draft.proposer- there is no record having
tx.draft_id+senderas a key invotestore senderis one ofblk.validatorssender.balance≥tx.fee
- state change
- add new record having
tx.draft_id+senderas a key invotestore sender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
- add new record having
Upon close_count reaches zero and the vote gets closed, an AMO blockchain
node calculates and updates draft.tally_* values.
- state change
- for
validatorinvalidators:draft.tally_quorum←draft.tally_quorum+validator.effective_stake draft.tally_quorum←draft.tally_quorum*config.draft_quorum_ratedraft.tally_approve←draft.tally_approve+draft.proposer.effective_stake- for
voteindraft.votes:draft.tally_approve←draft.tally_approve+vote.voter.effective_stake, ifvote.approveistrue - for
voteindraft.votes:draft.tally_reject←draft.tally_reject+vote.voter.effective_stake, ifvote.approveisfalse
- for
In order to register a data parcel in AMO blockchain, there must be an already registered data storage in the blockchain.
Upon receiving a setup transaction from an account, an AMO blockchain node
performs a validity check and add or update an item in storage store.
- validity check
sender.balance≥tx.feeprev.owner==tx.senderifprevwithprev.id==tx.storageexists instoragestore
- state change
- add new record or update existing record having
tx.storageas a key instoragestore sender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
- add new record or update existing record having
Upon receiving a close transaction from an account, an AMO blockchain node
performs a validity check and remove a record from store store.
- validity check
sender.balance≥tx.feeprevwithprev.id==tx.storageexists instoragestoreprev.owner==tx.sender
- state change
- remove record having
tx.storageas a key fromstoragestore sender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
- remove record having
Upon receiving a register transaction from an account, an AMO blockchain node
performs a validity check and add a new record with its extra information in
parcel store.
- validity check
- extract storage ID
storagefromtx.target storageshould exist in storage store andstorage.activeshould be truetx.targetshould NOT exist inparcelstoresender.balance≥storage.registration_fee+tx.fee
- extract storage ID
- state change
- add new record having
tx.targetas a key inparcelstore sender.balance←sender.balance-storage.registration_fee-tx.feestorage.owner.balance←storage.owner.balance+storage.registration_feeblk.incentive←blk.incentive+tx.fee
- add new record having
Upon receiving a discard transaction from an account, an AMO blockchain node
performs a validity check and remove record in parcel store.
- validity check
tx.targetshould exist inparcelstoresender.balance≥tx.feesender==tx.target.ownerorsender==tx.target.proxy_account
- state change
- remove record having
tx.targetas a key inparcelstore sender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
- remove record having
NOTE: proxy_account refers to an account which has a owner-equivalent
permission to control over tx.target record.
Upon receiving a request transaction from an account, an AMO blockchain node
performs a validity check and add a new record with its extra information in
request store.
- validity check
tx.targetshould exist inparcelstore- if
tx.recipientis valid, then
form request IDrequestfromtx.recipient+tx.target - else
form request IDrequestfromsender+tx.target requestshould NOT exist inrequeststorerequestshould NOT exist inusagestoretx.recipient≠tx.target.owner- if
tx.dealerandtx.dealer_feeis valid, then
sender.balance≥tx.fee+tx.payment+tx.dealer_fee - else
sender.balance≥tx.fee+tx.payment
- state change
- if
tx.recipientis valid, then
add new record havingtx.recipient+tx.targetas a key inrequeststore whererequest.agencyissender - else
add new record havingsender+tx.targetas a key inrequeststore whererequest.agencyis left empty - if
tx.dealerandtx.dealer_feeis valid, then
sender.balance←sender.balance-tx.fee-tx.payment-tx.dealer_fee - else
sender.balance←sender.balance-tx.fee-tx.payment blk.incentive←blk.incentive+tx.fee
- if
Upon receiving a cancel transaction from an account, an AMO blockchain node
performs a validity check and remove record in request store.
- validity check
tx.targetshould exist inparcelstore- if
tx.recipientis valid, then
form request IDrequestfromtx.recipient+tx.target - else
form request IDrequestfromsender+tx.target requestshould exist inrequeststore- if
tx.recipientis valid, then
sender==request.agency sender.balance≥tx.fee
- state change
- if
tx.recipientis valid, then
delete record having id astx.recipient+tx.targetinrequeststore - else
delete record having id assender+tx.targetinrequeststore - if
tx.dealerandtx.dealer_feeis valid, then
sender.balance←sender.balance-tx.fee+request.payment+request.dealer_fee - else
sender.balance←sender.balance-tx.fee+tx.target.payment blk.incentive←blk.incentive+tx.fee
- if
NOTE: payment refers to the amount of coins sender is willing to pay
for tx.target to tx.target.owner.
Upon receiving a grant transaction from an account, an AMO blockchain node
performs a validity check and add a new record with its extra information in
usage store.
- validity check
tx.targetshould exist inparcelstoretx.targetshould exist inrequeststoretx.targetshould NOT exist inusagestoresender==tx.target.ownerorsender==tx.target.proxy_account- extract storage ID
storagefromtx.target storageshould exist in storage store andstorage.activeshould be true- find
requesthaving id astx.recipient+tx.targetinrequeststore - if
sender==tx.target.owner, then
sender.balance+request.payment≥storage.hosting_fee+tx.fee - if
sender≠tx.target.owner, then
sender.balance≥tx.feeand
tx.target.owner.balance+request.payment≥storage.hosting_fee
- state change
- delete record having id as
tx.recipient+tx.targetinrequeststore - add new record having id as
tx.recipient+tx.targetinusagestore tx.target.owner.balance←tx.target.owner.balance+tx.target.payment-storage.hosting_feestorage.owner.balance←storage.owner.balance+storage.hosting_feerequest.dealer.balance←request.dealer.balance+request.dealer_feesender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
- delete record having id as
Upon receiving a revoke transaction from an account, an AMO blockchain node
performs a validity check and remove record in usage store.
- validity check
tx.targetshould exist inparcelstoretx.targetshould exist inusagestoresender==tx.target.ownerorsender==tx.target.proxy_accountsender.balance≥tx.fee
- state change
- delete record having id as
tx.recipient+tx.targetinusagestore sender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
- delete record having id as
Upon receiving a claim transaction from an account, an AMO blockchain node
performs a validity check and add new record in did store.
- validity check
- if
tx.targetalready exists indidstore,did.target.ownermust be the same astx.sender. sender.balance≥tx.fee
- if
- state change
- add new record or replace the record with key
tx.target sender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
- add new record or replace the record with key
Upon receiving a dismiss transaction from an account, an AMO blockchain node
performs a validity check and remove record from did store.
- validity check
tx.targetshould exist indidstoredid.target.ownermust be the same astx.sendersender.balance≥tx.fee
- state change
- remove record with key
tx.targetfromdidstore sender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
- remove record with key
In order to transfer user-defined coin balance in AMO blockchain, there must be an user-defined coin registry in the blockchain.
Upon receiving an issue transaction from an account, an AMO blockchain node
performs a validity check and add a new record in udc store.
- validity check
- if
udcexists havingtx.udcas a key in udc store, thensender==udc.ownerorsendershould be one ofudc.operators
- else
senderis one ofblk.validators
sender.balance≥tx.fee
- if
- state change
- if
udcexists in udc store, thenudc.operators←tx.operatorsudc.desc←tx.descudc.total←udc.total+tx.amount
- else add a new record
udcwith the followingudc.owner←senderudc.operators←tx.operatorsudc.desc←tx.descudc.total←tx.amount
<udc>.sender.balance←<udc>.sender.balance+tx.amountsender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
- if
Upon receiving a lock transaction from an account, an AMO blockchain node
performs a validity check and add or update a record in udc balance lock store.
- validity check
udcexists havingtx.udcas a key in udc storesender==udc.ownerorsendershould be one ofudc.operatorstx.amount> 0sender.balance≥tx.fee
- state change
- if
tx.amount> 0, then
add new<udc>.holder.lockor update existing<udc>.holder.lockin udc balance lock store - else
delete existing<udc>.holder.lockfrom udc balance lock store sender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
- if
Upon receiving a burn transaction from an account, an AMO blockchain node
performs a validity check and reduce sender's designated UDC balance.
- validity check
udcexists havingtx.udcas a key in udc storetx.amount> 0<udc>.sender.balance≥<udc>.sender.lock+tx.amountsender.balance≥tx.fee
- state change
<udc>.sender.balance←<udc>.sender.balance-tx.amountsender.balance←sender.balance-tx.feeblk.incentive←blk.incentive+tx.fee
After processing state changes triggered by users' transactions in
DeliverTx(), the nodes complete a block in EndBlock() by applying
additional state changes described in this section.
tx.fee is collected while transactions are processed and it gets included in
blk.incentive. Then, blk.incentive is distributed among the stakers and the
delegators at the end of block creation. The process is explained in incentive
distribution section, in more detail.
A lazy validator is a validator who is in the validator set until a block, but
did not vote in the consensus process for the block. We say the validator
missed the block. If a validator missed consecutive blks_lazy blocks and
blks_lazy >= hibernate_threshold, it is removed from the validator set for
the next hibernate_period blocks. New record is created in hibernate store
with start to be the current height, and end current height plug
hibernate_period. We say the validator enters hibernation state. If a
validator missed consecutive blks_lazy blocks and blks_lazy < hibernate_threshold but wakes up and comes back to the consensus process,
blks_lazy resets to zero.
If a validator stayed hibernation state for blks_hib blocks and blks_hib >= hibernate_period, i.e. hibernate.address.end = current_height, then
hibernate.address is removed and the validator leaves the hibernation state
and may be included in the validator set again.
TM validator set may change along with the progress of the chain. There are two reasons for the change:
- Validator hibernation state change
- Stake or effective stake change
In order to select new set of validators, first top n_val accounts with
highest effective stakes are selected excluding accounts associated to the
validators in hibernation state. Extract validator public keys for the
accounts, and inform the list of validator public keys to Tendermint layer.
NOTE: Effective stake value is the sum of his/her own stake in the stake
store and all items in the delegate store having the same delegatee field
as the account address in question
NOTE: n_val is a global parameter fixed across nodes and blocks (and so
the time). So, it shall be set at the genesis time.
TM: New list of validator pubkeys shall be transferred to the Tendermint
daemon via EndBlock response. Each validator has the voting power in
proportion to the effective stake value.
TM: According to the official documentation of tendermint and several experimental results, to maintain a blockchain network, it is mandatory for over 2/3 validator(MUST-ONLINE) nodes to be online. Also, the voting power of a validator node matters to the ratio of MUST-ONLINE nodes. That is, stopping validator nodes of which the sum of voting power is over 1/3 breaks the consensus algorithm of tendermint and results in the interruption of generating blocks on the chain.
TM: In tendermint, a voting power has a similar role as a stake in PoS
or DPoS consensus mechanism. One limitation is that sum of voting powers of all
validators must not exceed the value MaxTotalVotingPower, which is 2^60 - 1.
When we use one-to-one relation between stake value and voting power, exceeding
this max limit is not very likely, but possible anyway. So, the validator set
update mechanism must adjust voting power of each validator, so that total sum
of voting power does not exceed MaxTotalVotingPower:
- For each validator
Val_i, set voting powervp_ito bestakeofVal_i. - Calculate
TotalVotingPower, which is the sum ofvp_is of all validators in the new validator set. adjFactor← 0 (use this as a persistent factor)- While
TotalVotingPower>MaxTotalVotingPoweradjFactor←adjFactor+ 1TotalVotingPower←TotalVotingPower/ 2
(implemented as right-shift)- For each validator
Val_i,vp_i←vp_i/ 2
(implemented as right-shift)
NOTE: When vp_i reaches to zero, then Val_i shall be removed from the
new validator set.
At the beginning of block creation BeginBlock(), AMO ABCI app receives a list
of convicts from tendermint. The convicts get penalized in EndBlock() for its
malicious attempts to harm the blockchain network. The detailed penalization
process is explained in penalty section.
TM: Tendermint provides a block information, in BeginBlock() method which
is called at the beginning of a block creation, including a block proposer
address. This address is derived from the validator pubkey who proposes the
block. In AMO ABCI app, we can look up the original stake holder in the stake
store having the same validator pubkey.
Incentive refers to the sum of a block reward and transaction fees. The fees of
transactions which are successfully verified(delivered) by the block proposer
are accumulated and then transferred to the stake holder at the end of a block
creation in EndBlock().
A stake holder who proposes a block receives an incentive. This is the only
step in which there is a state change in balance store without involving any
transaction:
R ← b_reward + n_delivered_txs * tx_reward
I ← R + acc_fee
where R is the final block reward, b_reward a block reward rate,
n_delivered_txs the number of delivered transactions in the block,
tx_reward a transaction reward rate, I the final incentive and acc_fee
the accumulated fee.
When the incentive is I, this incentive shall be distributed among the stake
holder and the delegated stake holders. The distribution mechanism is as the
following:
wStakes←w_val*stake_0(stake of the proposer)- For each delegated stake
stake_i,wStakes←wStakes+w_ds*stake_i - Calculate the incentive for the proposer
I_0←I*w_val*stake_0/wStakes. - For each delegated stake holder, calculate the incentive for
i-th delegated stake,I_i←I*w_ds*stake_i/wStakes.
where w_val is the validator stake weight, and w_ds is the delegated stake
weight.
TODO: Eliminate ambiguity in float number arithmetic.
TODO: Take care of overflow situation.
To maintain the DPoS blockchain as healthy as possible, it is essential to encourage block validators to participate in creating and verifying blocks with incentive, but also to impose responsibilities on their misbehavior with penalty. The penalty shall be distributed among the stake holder and the delegated stake holders according to the distribution mechanism presented in Incentive Distribution.
The types of abnormal behavior and parameters are defined as follows:
- Convict
- Malicious Validator:
PenaltyRatioM - Lazy Validator:
PenaltyRatioL
- Malicious Validator:
TM: The evidence of validators' misbehavior is provided by Tendermint in
BeginBlock() method which is called at the beginning of a block creation.
Tendermint supports currently only a single type of evidence, the
DuplicateVoteEvidence.
The relevant validators pay the price for misbehavior by burning the specific amount of coins staked and delegated to them, immediately at the moment when their misbehavior is caught. The penalty shall be distributed amount the stake holder and the delegated stake holders according to the distribution mechanism presented in Incentive Distribution.
PenaltyRatioM
If a validator missed n_blks blocks within last laziness_window
blocks and n_blks >= laziness_threshold, then this validator gets penalized
accordingly. The penalty is calculated by penalty_ratio_l * eff_stake.
To enhance the stability of AMO's overall system, it is required to upgrade its application protocol consistently. To apply a new protocol on alive blockchain, 'hard-fork' is an inevitable process necessary to be done externally. AMO provides a feature which helps 'hard-fork' get processed more smoothly.
AMO ABCI app's protocol version is recorded as ProtocolVersion in app's
state. ProtocolVersion gets initiated with the value of genesis.json. If
not specified, it is set with current app's hard-coded protocol version.
The specific time when a new protocol gets applied and its version is decided
among validators through proposing and voting draft.
The time is recorded as UpgradeProtocolHeight and the version as
UpgradeProtocolVersion in app's config.
At the beginning of block creation BeginBlock(), AMO ABCI app checks
conditions and processes operations as follows:
- if
blk.height==app.config.UpgradeProtocolHeightapp.state.ProtocolVersion←app.config.UpgradeProtocolVersion
- if
sw.ProtocolVersion!=app.state.ProtocolVersion- abort and exit current sw
- if
blk.height==app.config.UpgradeProtocolHeight- execute
app.MigrateToX()(Xrefers tosw.ProtocolVersion)
- execute
Initial state of the app (genesis app state) is defined by genesis document
(genesis.json file in tendermint config directory, typically
$HOME/.tendermint/config/genesis.json). Initial app state is described in
app_state field in a genesis document. For example:
"app_state": {
"balances": [
{
"owner": "7CECB223B976F27D77B0E03E95602DABCC28D876",
"amount": "100"
}
]
}TM: In order to reset and apply new genesis state, run the following command in command line:
amod tendermint unsafe_reset_allAn AMO-compliant blockchain node should have some mechanisms to modify internal database for this operation.
In order to prevent replay
attack (in some sense,
double-spending), every AMO transaction is checked for whether it is already
introduced or processed in previous blocks. Basic idea is that when a
blockchain node sees a transaction that is already presented in the blockchain
network, it immediately discards the transaction. Here, every transaction has a
tx hash in Tendermint context. This tx hash is a hash of whole byte
sequence representing the transaction. Since we incorporated ECDSA signature to
authenticate the sender's identity, this gives randomness to the transaction,
and it can prevent replay attacks. However, AMO blockchain protocol itself is
independent of Tendermint. Moreover a future version AMO blockchain may not use
Tendermint as a base platform. So, in order to provide some generic
countermeasure against replay attacks, we use ReplayPreventer, a module which
monitors every incoming transaction to prevent its replay attacks by checking
its existence in the blockchain network.
If a user wants to send the same amount of coin to the same recipient again,
then the user must put into the transaction a signature different from the one
used for the previous transaction. If so, the transaction would have a
different tx hash and be treated as a different one, passing the transaction
check process of ReplayPreventer successfully.