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.1234
not"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.1234
not"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.1234
not"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
transfer
stake
withdraw
delegate
retract
- governance
propose
vote
- storage
setup
close
- parcels
register
request
grant
discard
cancel
revoke
- did
claim
dismiss
- user-defined coin
issue
burn
lock
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.
-
transfer
payload:{ "udc": _udc_id_, // optional "to": "_HEX_encoded_account_address_", "amount": "_currency_" }
where
udc
is an optional identifier of a user-defined coin,to
is recipient of the transfer, andamount
is amount AMO coin or user-defined coin._udc_id_
must be one of registered user-defined coin ID. Ifudc
is omitted, transfer AMO coin, which is the default._currency_
is a string representation of a decimal number. -
stake
payload:{ "validator": "_HEX_encoded_ed25519_pubkey_", "amount": "_currency_" }
where
validator
is 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.amount
is amount of AMO coin to be locked as stake. -
withdraw
payload:{ "amount": "_currency_" }
where
amount
is amount of AMO coin to be withdrawn from stake. -
delegate
payload:{ "to": "_HEX_encoded_account_address_", "amount": "_currency_" }
where
to
is an address of an account which has stakes already andamount
is amount of AMO coin to be delegated. -
retract
payload:{ "amount": "_currency_" }
where
amount
is amount of AMO coin to be retracted from delegated stake. -
propose
payload{ "draft_id": "_draft_id_", "config": {}, // application-specific JSON object "desc": "human-readable string describing this draft" }
where
config
is an optional field which is necessary for a proposal of applying of new configuration on-chain. -
vote
payload{ "draft_id": "_draft_id_", "approve": true // boolean }
where
approve
indicatessender
's opinion ondraft_id
;true
for approval orfalse
for rejection. -
setup
payload{ "storage": _storage_id_, // integer "url": "_url_", "registration_fee": "_currency_", "hosting_fee": "_currency_" }
-
close
payload{ "storage": _storage_id_ // integer }
-
register
payload:{ "target": "_HEX_encoded_parcel_id_", "custody": "_HEX_encoded_key_custody_", "proxy_account": "_HEX_encoded_account_address_", // optional "extra": {} // application-specific JSON object, optional }
where
target
is the id of a parcel currently being registered,custody
is a encrypted key material used to encrypt the data parcel body, and the key material is encrypted by the owner(seller)'s public key. -
request
payload:{ "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
target
is the id of a parcel for which the sender wants usage grant,payment
is amount of AMO coin to be collected by the seller,recipient
is the address of a recipient explictly designated to get granted a usage on the parcel. In order fordealer_fee
to work, both ofdealer
anddealer_fee
must be valid. -
grant
payload{ "target": "_HEX_encoded_parcel_id_", "recipient": "_HEX_encoded_account_address_", "custody": "_HEX_encoded_key_custody_", "extra": {} // application-specific JSON object, optional }
where
target
is the id of a parcel currently being granted,recipient
is the address of a recipient,custody
is a encrypted key material used to encrypt the data parcel body, and the key material is encrypted by the buyer's public key. -
discard
payload{ "target": "_HEX_encoded_parcel_id_" }
where
target
is the id of a parcel currently being discarded. -
cancel
payload{ "target": "_HEX_encoded_parcel_id_", "recipient": "_HEX_encoded_account_address_" // optional }
where
target
is the id of a parcel which the sender requested previously,recipient
is the address of a recipient designated to get granted a usage on the parcel. -
revoke
payload{ "target": "_HEX_encoded_parcel_id_", "recipient": "_HEX_encoded_account_address_" }
where
target
is the id of a parcel currently being revoked,recipient
is the address of a buyer which is previously granted a usage on the parcel. -
claim
payload{ "target": "_string_", "document": {} // DID document in the form of JSON object }
where
target
is a JSON string conforming toidchar
in DID syntax.document
value will be stored as a compact representation. Whenclaim
tx is received on a previously claimedtarget
,document
will be replaced with a new one. -
dismiss
payload{ "target": "_string_", }
effectively removes the DID document from the DID registry.
-
issue
payload{ "udc": _udc_id_, "desc": "human-readable string describing this user-defined coin", "operators": [ "_HEX_encoded_account_address_", ... ], "amount": "_currency_" }
where
operators
is an optional list of operator addresses, andamount
is the amount of UDC balance to be created. -
lock
payload{ "udc": _udc_id_, "holder": "_HEX_encoded_account_address_", "amount": "_currency_" }
where
udc
is an identifier of a user-defined coin, andamount
is the amount of UDC coin to be locked. -
burn
payload{ "udc": _udc_id_, "amount": "_currency_" }
where
udc
is an identifier of a user-defined coin, andamount
is 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_" }
config
keys should be a subset of the top-levelconfig
item. 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.*_count
control 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_count
is decremented at each block progress, and when it reaches zeroclose_count
is decremented afterwards. Whenclose_count
reaches zero and the vote summary is approval, thenapply_count
is decremented until the new configuration is applied.tally_*
fields count votes cast upon this draft.tally_approve
andtally_reject
are 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
target
of a register tx owner
is 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
) andtarget
of 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
recipient
andtarget
of 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
id
of an issue tx owner
is 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
>0
sender.balance
≥tx.amount
+tx.fee
- state change
sender.balance
←sender.balance
-tx.amount
-tx.fee
to.balance
←to.balance
+tx.amount
blk.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.amount
sender.balance
≥tx.fee
- UDC id
- state change
<udc>.sender.balance
←<udc>.sender.balance
-tx.amount
<udc>.to.balance
←<udc>.to.balance
+tx.amount
sender.balance
←sender.balance
-tx.fee
blk.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
>0
tx.amount % config.minimum_staking_unit
==0
(check staking unit restriction)sender.balance
≥tx.amount
+tx.fee
- There is no other account
holder
havingholder.stake.validator
==tx.validator
sender.stake.validator
==tx.validator
ifsender.stake
exists
- state change
sender.balance
←sender.balance
-tx.amount
-tx.fee
sender.stake.amount
←sender.stake.amount
+tx.amount
sender.stake.locked_height
←config.lockup_period
blk.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
>0
sender.balance
≥tx.fee
sender.stake.unlocked
==true
sender.stake.amount
≥tx.amount
sender.stake.amount
>tx.amount
if this account is a delegatee for any of delegated stakessender.stake.delegate
is empty
- state change
sender.stake.amount
←sender.stake.amount
-tx.amount
sender.balance
←sender.balance
-tx.fee
+tx.amount
blk.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
>0
tx.amount
%config.minimum_staking_unit
==0
(check staking unit restriction)sender.balance
≥tx.fee
+tx.amount
tx.to
address already has a positive stake instake
store- the
sender
has no previous delegatee ortx.to
is the same as the previous delegatee
- state change
sender.balance
←sender.balance
-tx.fee
-tx.amount
blk.incentive
←blk.incentive
+tx.fee
sender.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
>0
sender.balance
≥tx.fee
sender.delegate.amount
≥tx.amount
- state change
sender.delegate.amount
←sender.delegate.amount
-tx.amount
sender.balance
←sender.balance
-tx.fee
+tx.amount
blk.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_id
as a key indraft
store sender
is one ofblk.validators
sender.balance
≥config.draft_deposit
+tx.fee
tx.draft_id
==state.latest_draft_id
+ 1
- state change
- add new record having
tx.draft_id
as a key indraft
store sender.balance
←sender.balance
-config.draft_deposit
-tx.fee
blk.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_id
is in progresssender
!=draft.proposer
- there is no record having
tx.draft_id
+sender
as a key invote
store sender
is one ofblk.validators
sender.balance
≥tx.fee
- state change
- add new record having
tx.draft_id
+sender
as a key invote
store sender.balance
←sender.balance
-tx.fee
blk.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
validator
invalidators
:draft.tally_quorum
←draft.tally_quorum
+validator.effective_stake
draft.tally_quorum
←draft.tally_quorum
*config.draft_quorum_rate
draft.tally_approve
←draft.tally_approve
+draft.proposer.effective_stake
- for
vote
indraft.votes
:draft.tally_approve
←draft.tally_approve
+vote.voter.effective_stake
, ifvote.approve
istrue
- for
vote
indraft.votes
:draft.tally_reject
←draft.tally_reject
+vote.voter.effective_stake
, ifvote.approve
isfalse
- 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.fee
prev.owner
==tx.sender
ifprev
withprev.id
==tx.storage
exists instorage
store
- state change
- add new record or update existing record having
tx.storage
as a key instorage
store sender.balance
←sender.balance
-tx.fee
blk.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.fee
prev
withprev.id
==tx.storage
exists instorage
storeprev.owner
==tx.sender
- state change
- remove record having
tx.storage
as a key fromstorage
store sender.balance
←sender.balance
-tx.fee
blk.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
storage
fromtx.target
storage
should exist in storage store andstorage.active
should be truetx.target
should NOT exist inparcel
storesender.balance
≥storage.registration_fee
+tx.fee
- extract storage ID
- state change
- add new record having
tx.target
as a key inparcel
store sender.balance
←sender.balance
-storage.registration_fee
-tx.fee
storage.owner.balance
←storage.owner.balance
+storage.registration_fee
blk.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.target
should exist inparcel
storesender.balance
≥tx.fee
sender
==tx.target.owner
orsender
==tx.target.proxy_account
- state change
- remove record having
tx.target
as a key inparcel
store sender.balance
←sender.balance
-tx.fee
blk.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.target
should exist inparcel
store- if
tx.recipient
is valid, then
form request IDrequest
fromtx.recipient
+tx.target
- else
form request IDrequest
fromsender
+tx.target
request
should NOT exist inrequest
storerequest
should NOT exist inusage
storetx.recipient
≠tx.target.owner
- if
tx.dealer
andtx.dealer_fee
is valid, then
sender.balance
≥tx.fee
+tx.payment
+tx.dealer_fee
- else
sender.balance
≥tx.fee
+tx.payment
- state change
- if
tx.recipient
is valid, then
add new record havingtx.recipient
+tx.target
as a key inrequest
store whererequest.agency
issender
- else
add new record havingsender
+tx.target
as a key inrequest
store whererequest.agency
is left empty - if
tx.dealer
andtx.dealer_fee
is 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.target
should exist inparcel
store- if
tx.recipient
is valid, then
form request IDrequest
fromtx.recipient
+tx.target
- else
form request IDrequest
fromsender
+tx.target
request
should exist inrequest
store- if
tx.recipient
is valid, then
sender
==request.agency
sender.balance
≥tx.fee
- state change
- if
tx.recipient
is valid, then
delete record having id astx.recipient
+tx.target
inrequest
store - else
delete record having id assender
+tx.target
inrequest
store - if
tx.dealer
andtx.dealer_fee
is 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.target
should exist inparcel
storetx.target
should exist inrequest
storetx.target
should NOT exist inusage
storesender
==tx.target.owner
orsender
==tx.target.proxy_account
- extract storage ID
storage
fromtx.target
storage
should exist in storage store andstorage.active
should be true- find
request
having id astx.recipient
+tx.target
inrequest
store - if
sender
==tx.target.owner
, then
sender.balance
+request.payment
≥storage.hosting_fee
+tx.fee
- if
sender
≠tx.target.owner
, then
sender.balance
≥tx.fee
and
tx.target.owner.balance
+request.payment
≥storage.hosting_fee
- state change
- delete record having id as
tx.recipient
+tx.target
inrequest
store - add new record having id as
tx.recipient
+tx.target
inusage
store tx.target.owner.balance
←tx.target.owner.balance
+tx.target.payment
-storage.hosting_fee
storage.owner.balance
←storage.owner.balance
+storage.hosting_fee
request.dealer.balance
←request.dealer.balance
+request.dealer_fee
sender.balance
←sender.balance
-tx.fee
blk.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.target
should exist inparcel
storetx.target
should exist inusage
storesender
==tx.target.owner
orsender
==tx.target.proxy_account
sender.balance
≥tx.fee
- state change
- delete record having id as
tx.recipient
+tx.target
inusage
store sender.balance
←sender.balance
-tx.fee
blk.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.target
already exists indid
store,did.target.owner
must 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.fee
blk.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.target
should exist indid
storedid.target.owner
must be the same astx.sender
sender.balance
≥tx.fee
- state change
- remove record with key
tx.target
fromdid
store sender.balance
←sender.balance
-tx.fee
blk.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
udc
exists havingtx.udc
as a key in udc store, thensender
==udc.owner
orsender
should be one ofudc.operators
- else
sender
is one ofblk.validators
sender.balance
≥tx.fee
- if
- state change
- if
udc
exists in udc store, thenudc.operators
←tx.operators
udc.desc
←tx.desc
udc.total
←udc.total
+tx.amount
- else add a new record
udc
with the followingudc.owner
←sender
udc.operators
←tx.operators
udc.desc
←tx.desc
udc.total
←tx.amount
<udc>.sender.balance
←<udc>.sender.balance
+tx.amount
sender.balance
←sender.balance
-tx.fee
blk.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
udc
exists havingtx.udc
as a key in udc storesender
==udc.owner
orsender
should be one ofudc.operators
tx.amount
> 0sender.balance
≥tx.fee
- state change
- if
tx.amount
> 0, then
add new<udc>.holder.lock
or update existing<udc>.holder.lock
in udc balance lock store - else
delete existing<udc>.holder.lock
from udc balance lock store sender.balance
←sender.balance
-tx.fee
blk.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
udc
exists havingtx.udc
as a key in udc storetx.amount
> 0<udc>.sender.balance
≥<udc>.sender.lock
+tx.amount
sender.balance
≥tx.fee
- state change
<udc>.sender.balance
←<udc>.sender.balance
-tx.amount
sender.balance
←sender.balance
-tx.fee
blk.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_i
to bestake
ofVal_i
. - Calculate
TotalVotingPower
, which is the sum ofvp_i
s of all validators in the new validator set. adjFactor
← 0 (use this as a persistent factor)- While
TotalVotingPower
>MaxTotalVotingPower
adjFactor
←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.UpgradeProtocolHeight
app.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()
(X
refers 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_all
An 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.