rename hash tree roots as root

This commit is contained in:
Terence Tsao 2018-12-11 09:53:56 -08:00
parent defe2668a0
commit e0e2fed1b5
3 changed files with 30 additions and 30 deletions

View File

@ -64,7 +64,7 @@
- [`clamp`](#clamp) - [`clamp`](#clamp)
- [`get_new_shuffling`](#get_new_shuffling) - [`get_new_shuffling`](#get_new_shuffling)
- [`get_shard_committees_at_slot`](#get_shard_committees_at_slot) - [`get_shard_committees_at_slot`](#get_shard_committees_at_slot)
- [`get_block_hash`](#get_block_hash) - [`get_block_root`](#get_block_root)
- [`get_beacon_proposer_index`](#get_beacon_proposer_index) - [`get_beacon_proposer_index`](#get_beacon_proposer_index)
- [`get_updated_ancestor_hashes`](#get_updated_ancestor_hashes) - [`get_updated_ancestor_hashes`](#get_updated_ancestor_hashes)
- [`get_attestation_participants`](#get_attestation_participants) - [`get_attestation_participants`](#get_attestation_participants)
@ -73,7 +73,7 @@
- [`get_new_validator_registry_delta_chain_tip`](#get_new_validator_registry_delta_chain_tip) - [`get_new_validator_registry_delta_chain_tip`](#get_new_validator_registry_delta_chain_tip)
- [`get_fork_version`](#get_fork_version) - [`get_fork_version`](#get_fork_version)
- [`get_domain`](#get_domain) - [`get_domain`](#get_domain)
- [`SSZTreeHash`](#ssztreehash) - [`hash_tree_root`](#hash_tree_root)
- [`verify_casper_votes`](#verify_casper_votes) - [`verify_casper_votes`](#verify_casper_votes)
- [`integer_squareroot`](#integer_squareroot) - [`integer_squareroot`](#integer_squareroot)
- [`BLSVerify`](#blsverify) - [`BLSVerify`](#blsverify)
@ -322,7 +322,7 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted
# Slot of the last justified beacon block # Slot of the last justified beacon block
'justified_slot': 'uint64', 'justified_slot': 'uint64',
# Hash of the last justified beacon block # Hash of the last justified beacon block
'justified_block_hash': 'hash32', 'justified_block_root': 'hash32',
} }
``` ```
@ -655,7 +655,7 @@ Processing the beacon chain is similar to processing the Ethereum 1.0 chain. Cli
For a beacon chain block, `block`, to be processed by a node, the following conditions must be met: For a beacon chain block, `block`, to be processed by a node, the following conditions must be met:
* The parent block with `SSZTreeHash` `block.ancestor_hashes[0]` has been processed and accepted. * The parent block with `hash_tree_root` `block.ancestor_hashes[0]` has been processed and accepted.
* The node has processed its `state` up to slot, `block.slot - 1`. * The node has processed its `state` up to slot, `block.slot - 1`.
* The Ethereum 1.0 block pointed to by the `state.processed_pow_receipt_root` has been processed and accepted. * The Ethereum 1.0 block pointed to by the `state.processed_pow_receipt_root` has been processed and accepted.
* The node's local clock time is greater than or equal to `state.genesis_time + block.slot * SLOT_DURATION`. * The node's local clock time is greater than or equal to `state.genesis_time + block.slot * SLOT_DURATION`.
@ -869,10 +869,10 @@ def get_shard_committees_at_slot(state: BeaconState,
return state.shard_committees_at_slots[slot - earliest_slot_in_array] return state.shard_committees_at_slots[slot - earliest_slot_in_array]
``` ```
#### `get_block_hash` #### `get_block_root`
```python ```python
def get_block_hash(state: BeaconState, def get_block_root(state: BeaconState,
slot: int) -> Hash32: slot: int) -> Hash32:
""" """
Returns the block hash at a recent ``slot``. Returns the block hash at a recent ``slot``.
@ -882,7 +882,7 @@ def get_block_hash(state: BeaconState,
return state.latest_block_hashes[slot - earliest_slot_in_array] return state.latest_block_hashes[slot - earliest_slot_in_array]
``` ```
`get_block_hash(_, s)` should always return `SSZTreeHash` of the block in the beacon chain at slot `s`, and `get_shard_committees_at_slot(_, s)` should not change unless the [validator](#dfn-validator) registry changes. `get_block_root(_, s)` should always return `hash_tree_root` of the block in the beacon chain at slot `s`, and `get_shard_committees_at_slot(_, s)` should not change unless the [validator](#dfn-validator) registry changes.
#### `get_beacon_proposer_index` #### `get_beacon_proposer_index`
@ -987,9 +987,9 @@ def get_domain(fork_data: ForkData,
) * 2**32 + domain_type ) * 2**32 + domain_type
``` ```
#### `SSZTreeHash` #### `hash_tree_root`
`SSZTreeHash` is a function for hashing objects into a single root utilizing a hash tree structure. `SSZTreeHash` is defined in the [SimpleSerialize spec](https://github.com/ethereum/eth2.0-specs/blob/master/specs/simple-serialize.md#tree-hash). `hash_tree_root` is a function for hashing objects into a single root utilizing a hash tree structure. `hash_tree_root` is defined in the [SimpleSerialize spec](https://github.com/ethereum/eth2.0-specs/blob/master/specs/simple-serialize.md#tree-hash).
#### `verify_casper_votes` #### `verify_casper_votes`
@ -1000,7 +1000,7 @@ def verify_casper_votes(state: BeaconState, votes: SlashableVoteData) -> bool:
pubs = [aggregate_pubkey([state.validators[i].pubkey for i in votes.aggregate_signature_poc_0_indices]), pubs = [aggregate_pubkey([state.validators[i].pubkey for i in votes.aggregate_signature_poc_0_indices]),
aggregate_pubkey([state.validators[i].pubkey for i in votes.aggregate_signature_poc_1_indices])] aggregate_pubkey([state.validators[i].pubkey for i in votes.aggregate_signature_poc_1_indices])]
return BLSMultiVerify(pubkeys=pubs, msgs=[SSZTreeHash(votes)+bytes1(0), SSZTreeHash(votes)+bytes1(1), sig=aggregate_signature) return BLSMultiVerify(pubkeys=pubs, msgs=[hash_tree_root(votes)+bytes1(0), hash_tree_root(votes)+bytes1(1), sig=aggregate_signature)
``` ```
#### `integer_squareroot` #### `integer_squareroot`
@ -1292,9 +1292,9 @@ def exit_validator(state: BeaconState,
Below are the processing steps that happen at every slot. Below are the processing steps that happen at every slot.
* Let `latest_block` be the latest `BeaconBlock` that was processed in the chain. * Let `latest_block` be the latest `BeaconBlock` that was processed in the chain.
* Let `latest_hash` be the `SSZTreeHash` of `latest_block`. * Let `latest_hash` be the `hash_tree_root` of `latest_block`.
* Set `state.slot += 1` * Set `state.slot += 1`
* Set `state.latest_block_hashes = state.latest_block_hashes + [latest_hash]`. (The output of `get_block_hash` should not change, except that it will no longer throw for `state.slot - 1`). * Set `state.latest_block_hashes = state.latest_block_hashes + [latest_hash]`. (The output of `get_block_root` should not change, except that it will no longer throw for `state.slot - 1`).
If there is a block from the proposer for `state.slot`, we process that incoming block: If there is a block from the proposer for `state.slot`, we process that incoming block:
@ -1309,8 +1309,8 @@ If there is no block from the proposer at state.slot:
### Proposer signature ### Proposer signature
* Let `block_hash_without_sig` be the `SSZTreeHash` of `block` where `block.signature` is set to `[0, 0]`. * Let `block_hash_without_sig` be the `hash_tree_root` of `block` where `block.signature` is set to `[0, 0]`.
* Let `proposal_hash = SSZTreeHash(ProposalSignedData(state.slot, BEACON_CHAIN_SHARD_NUMBER, block_hash_without_sig))`. * Let `proposal_hash = hash_tree_root(ProposalSignedData(state.slot, BEACON_CHAIN_SHARD_NUMBER, block_hash_without_sig))`.
* Verify that `BLSVerify(pubkey=state.validator_registry[get_beacon_proposer_index(state, state.slot)].pubkey, data=proposal_hash, sig=block.signature, domain=get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL))`. * Verify that `BLSVerify(pubkey=state.validator_registry[get_beacon_proposer_index(state, state.slot)].pubkey, data=proposal_hash, sig=block.signature, domain=get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL))`.
### RANDAO ### RANDAO
@ -1336,8 +1336,8 @@ Verify that `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`.
For each `proposer_slashing` in `block.body.proposer_slashings`: For each `proposer_slashing` in `block.body.proposer_slashings`:
* Let `proposer = state.validator_registry[proposer_slashing.proposer_index]`. * Let `proposer = state.validator_registry[proposer_slashing.proposer_index]`.
* Verify that `BLSVerify(pubkey=proposer.pubkey, msg=SSZTreeHash(proposer_slashing.proposal_data_1), sig=proposer_slashing.proposal_signature_1, domain=get_domain(state.fork_data, proposer_slashing.proposal_data_1.slot, DOMAIN_PROPOSAL))`. * Verify that `BLSVerify(pubkey=proposer.pubkey, msg=hash_tree_root(proposer_slashing.proposal_data_1), sig=proposer_slashing.proposal_signature_1, domain=get_domain(state.fork_data, proposer_slashing.proposal_data_1.slot, DOMAIN_PROPOSAL))`.
* Verify that `BLSVerify(pubkey=proposer.pubkey, msg=SSZTreeHash(proposer_slashing.proposal_data_2), sig=proposer_slashing.proposal_signature_2, domain=get_domain(state.fork_data, proposer_slashing.proposal_data_2.slot, DOMAIN_PROPOSAL))`. * Verify that `BLSVerify(pubkey=proposer.pubkey, msg=hash_tree_root(proposer_slashing.proposal_data_2), sig=proposer_slashing.proposal_signature_2, domain=get_domain(state.fork_data, proposer_slashing.proposal_data_2.slot, DOMAIN_PROPOSAL))`.
* Verify that `proposer_slashing.proposal_data_1.slot == proposer_slashing.proposal_data_2.slot`. * Verify that `proposer_slashing.proposal_data_1.slot == proposer_slashing.proposal_data_2.slot`.
* Verify that `proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard`. * Verify that `proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard`.
* Verify that `proposer_slashing.proposal_data_1.block_hash != proposer_slashing.proposal_data_2.block_hash`. * Verify that `proposer_slashing.proposal_data_1.block_hash != proposer_slashing.proposal_data_2.block_hash`.
@ -1368,12 +1368,12 @@ For each `attestation` in `block.body.attestations`:
* Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`. * Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`.
* Verify that `attestation.data.slot + EPOCH_LENGTH >= state.slot`. * Verify that `attestation.data.slot + EPOCH_LENGTH >= state.slot`.
* Verify that `attestation.data.justified_slot` is equal to `state.justified_slot if attestation.data.slot >= state.slot - (state.slot % EPOCH_LENGTH) else state.previous_justified_slot`. * Verify that `attestation.data.justified_slot` is equal to `state.justified_slot if attestation.data.slot >= state.slot - (state.slot % EPOCH_LENGTH) else state.previous_justified_slot`.
* Verify that `attestation.data.justified_block_hash` is equal to `get_block_hash(state, attestation.data.justified_slot)`. * Verify that `attestation.data.justified_block_root` is equal to `get_block_root(state, attestation.data.justified_slot)`.
* Verify that either `attestation.data.latest_crosslink_hash` or `attestation.data.shard_block_hash` equals `state.latest_crosslinks[shard].shard_block_hash`. * Verify that either `attestation.data.latest_crosslink_hash` or `attestation.data.shard_block_hash` equals `state.latest_crosslinks[shard].shard_block_hash`.
* `aggregate_signature` verification: * `aggregate_signature` verification:
* Let `participants = get_attestation_participants(state, attestation.data, attestation.participation_bitfield)`. * Let `participants = get_attestation_participants(state, attestation.data, attestation.participation_bitfield)`.
* Let `group_public_key = BLSAddPubkeys([state.validator_registry[v].pubkey for v in participants])`. * Let `group_public_key = BLSAddPubkeys([state.validator_registry[v].pubkey for v in participants])`.
* Verify that `BLSVerify(pubkey=group_public_key, msg=SSZTreeHash(attestation.data) + bytes1(0), sig=attestation.aggregate_signature, domain=get_domain(state.fork_data, attestation.data.slot, DOMAIN_ATTESTATION))`. * Verify that `BLSVerify(pubkey=group_public_key, msg=hash_tree_root(attestation.data) + bytes1(0), sig=attestation.aggregate_signature, domain=get_domain(state.fork_data, attestation.data.slot, DOMAIN_ATTESTATION))`.
* [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.shard_block_hash == ZERO_HASH`. * [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.shard_block_hash == ZERO_HASH`.
* Append `PendingAttestationRecord(data=attestation.data, participation_bitfield=attestation.participation_bitfield, custody_bitfield=attestation.custody_bitfield, slot_included=state.slot)` to `state.latest_attestations`. * Append `PendingAttestationRecord(data=attestation.data, participation_bitfield=attestation.participation_bitfield, custody_bitfield=attestation.custody_bitfield, slot_included=state.slot)` to `state.latest_attestations`.
@ -1457,7 +1457,7 @@ All [validators](#dfn-validator):
[Validators](#dfn-Validator) justifying the epoch boundary block at the start of the current epoch: [Validators](#dfn-Validator) justifying the epoch boundary block at the start of the current epoch:
* Let `this_epoch_attestations = [a for a in state.latest_attestations if state.slot - EPOCH_LENGTH <= a.data.slot < state.slot]`. (Note: this is the set of attestations of slots in the epoch `state.slot-EPOCH_LENGTH...state.slot-1`, _not_ attestations that got included in the chain during the epoch `state.slot-EPOCH_LENGTH...state.slot-1`.) * Let `this_epoch_attestations = [a for a in state.latest_attestations if state.slot - EPOCH_LENGTH <= a.data.slot < state.slot]`. (Note: this is the set of attestations of slots in the epoch `state.slot-EPOCH_LENGTH...state.slot-1`, _not_ attestations that got included in the chain during the epoch `state.slot-EPOCH_LENGTH...state.slot-1`.)
* Let `this_epoch_boundary_attestations = [a for a in this_epoch_attestations if a.data.epoch_boundary_hash == get_block_hash(state, state.slot-EPOCH_LENGTH) and a.justified_slot == state.justified_slot]`. * Let `this_epoch_boundary_attestations = [a for a in this_epoch_attestations if a.data.epoch_boundary_hash == get_block_root(state, state.slot-EPOCH_LENGTH) and a.justified_slot == state.justified_slot]`.
* Let `this_epoch_boundary_attester_indices` be the union of the [validator](#dfn-validator) index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in this_epoch_boundary_attestations]`. * Let `this_epoch_boundary_attester_indices` be the union of the [validator](#dfn-validator) index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in this_epoch_boundary_attestations]`.
* Let `this_epoch_boundary_attesters = [state.validator_registry[i] for indices in this_epoch_boundary_attester_indices for i in indices]`. * Let `this_epoch_boundary_attesters = [state.validator_registry[i] for indices in this_epoch_boundary_attester_indices for i in indices]`.
* Let `this_epoch_boundary_attesting_balance = sum([get_effective_balance(v) for v in this_epoch_boundary_attesters])`. * Let `this_epoch_boundary_attesting_balance = sum([get_effective_balance(v) for v in this_epoch_boundary_attesters])`.
@ -1465,7 +1465,7 @@ All [validators](#dfn-validator):
[Validators](#dfn-Validator) justifying the epoch boundary block at the start of the previous epoch: [Validators](#dfn-Validator) justifying the epoch boundary block at the start of the previous epoch:
* Let `previous_epoch_attestations = [a for a in state.latest_attestations if state.slot - 2 * EPOCH_LENGTH <= a.slot < state.slot - EPOCH_LENGTH]`. * Let `previous_epoch_attestations = [a for a in state.latest_attestations if state.slot - 2 * EPOCH_LENGTH <= a.slot < state.slot - EPOCH_LENGTH]`.
* Let `previous_epoch_boundary_attestations = [a for a in this_epoch_attestations + previous_epoch_attestations if a.epoch_boundary_hash == get_block_hash(state, state.slot - 2 * EPOCH_LENGTH) and a.justified_slot == state.previous_justified_slot]`. * Let `previous_epoch_boundary_attestations = [a for a in this_epoch_attestations + previous_epoch_attestations if a.epoch_boundary_hash == justified_block_root(state, state.slot - 2 * EPOCH_LENGTH) and a.justified_slot == state.previous_justified_slot]`.
* Let `previous_epoch_boundary_attester_indices` be the union of the validator index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in previous_epoch_boundary_attestations]`. * Let `previous_epoch_boundary_attester_indices` be the union of the validator index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in previous_epoch_boundary_attestations]`.
* Let `previous_epoch_boundary_attesters = [state.validator_registry[i] for indices in previous_epoch_boundary_attester_indices for i in indices]`. * Let `previous_epoch_boundary_attesters = [state.validator_registry[i] for indices in previous_epoch_boundary_attester_indices for i in indices]`.
* Let `previous_epoch_boundary_attesting_balance = sum([get_effective_balance(v) for v in previous_epoch_boundary_attesters])`. * Let `previous_epoch_boundary_attesting_balance = sum([get_effective_balance(v) for v in previous_epoch_boundary_attesters])`.
@ -1660,7 +1660,7 @@ while len(state.persistent_committee_reassignments) > 0 and state.persistent_com
## State root processing ## State root processing
Verify `block.state_root == SSZTreeHash(state)` if there exists a `block` for the slot being processed. Verify `block.state_root == hash_tree_root(state)` if there exists a `block` for the slot being processed.
# Appendix # Appendix
## Appendix A - Hash function ## Appendix A - Hash function

View File

@ -41,7 +41,7 @@ A `ShardBlock` object has the following fields:
# What shard is it on # What shard is it on
'shard_id': 'uint64', 'shard_id': 'uint64',
# Parent block hash # Parent block hash
'parent_hash': 'hash32', 'parent_root': 'hash32',
# Beacon chain block # Beacon chain block
'beacon_chain_ref': 'hash32', 'beacon_chain_ref': 'hash32',
# Depth of the Merkle tree # Depth of the Merkle tree
@ -62,16 +62,16 @@ A `ShardBlock` object has the following fields:
For a block on a shard to be processed by a node, the following conditions must be met: For a block on a shard to be processed by a node, the following conditions must be met:
* The `ShardBlock` pointed to by `parent_hash` has already been processed and accepted * The `ShardBlock` pointed to by `parent_root` has already been processed and accepted
* The signature for the block from the _proposer_ (see below for definition) of that block is included along with the block in the network message object * The signature for the block from the _proposer_ (see below for definition) of that block is included along with the block in the network message object
To validate a block header on shard `shard_id`, compute as follows: To validate a block header on shard `shard_id`, compute as follows:
* Verify that `beacon_chain_ref` is the hash of a block in the beacon chain with slot less than or equal to `slot`. Verify that `beacon_chain_ref` is equal to or a descendant of the `beacon_chain_ref` specified in the `ShardBlock` pointed to by `parent_hash`. * Verify that `beacon_chain_ref` is the hash of a block in the beacon chain with slot less than or equal to `slot`. Verify that `beacon_chain_ref` is equal to or a descendant of the `beacon_chain_ref` specified in the `ShardBlock` pointed to by `parent_root`.
* Let `state` be the state of the beacon chain block referred to by `beacon_chain_ref`. Let `validators` be `[validators[i] for i in state.current_persistent_committees[shard_id]]`. * Let `state` be the state of the beacon chain block referred to by `beacon_chain_ref`. Let `validators` be `[validators[i] for i in state.current_persistent_committees[shard_id]]`.
* Assert `len(attester_bitfield) == ceil_div8(len(validators))` * Assert `len(attester_bitfield) == ceil_div8(len(validators))`
* Let `proposer_index = hash(state.randao_mix + bytes8(shard_id) + bytes8(slot)) % len(validators)`. Let `msg` be the block but with the `block.signature` set to `[0, 0]`. Verify that `BLSVerify(pub=validators[proposer_index].pubkey, msg=hash(msg), sig=block.signature, domain=get_domain(state, slot, SHARD_PROPOSER_DOMAIN))` passes. * Let `proposer_index = hash(state.randao_mix + bytes8(shard_id) + bytes8(slot)) % len(validators)`. Let `msg` be the block but with the `block.signature` set to `[0, 0]`. Verify that `BLSVerify(pub=validators[proposer_index].pubkey, msg=hash(msg), sig=block.signature, domain=get_domain(state, slot, SHARD_PROPOSER_DOMAIN))` passes.
* Generate the `group_public_key` by adding the public keys of all the validators for whom the corresponding position in the bitfield is set to 1. Verify that `BLSVerify(pub=group_public_key, msg=parent_hash, sig=block.aggregate_sig, domain=get_domain(state, slot, SHARD_ATTESTER_DOMAIN))` passes. * Generate the `group_public_key` by adding the public keys of all the validators for whom the corresponding position in the bitfield is set to 1. Verify that `BLSVerify(pub=group_public_key, msg=parent_root, sig=block.aggregate_sig, domain=get_domain(state, slot, SHARD_ATTESTER_DOMAIN))` passes.
### Block Merklization helper ### Block Merklization helper

View File

@ -390,7 +390,7 @@ return typ(**values), item_index
### Tree Hash ### Tree Hash
The below `SSZTreeHash` algorithm is defined recursively in the case of lists and containers, and it outputs a value equal to or less than 32 bytes in size. For the final output only (ie. not intermediate outputs), if the output is less than 32 bytes, right-zero-pad it to 32 bytes. The goal is collision resistance *within* each type, not between types. The below `hash_tree_root` algorithm is defined recursively in the case of lists and containers, and it outputs a value equal to or less than 32 bytes in size. For the final output only (ie. not intermediate outputs), if the output is less than 32 bytes, right-zero-pad it to 32 bytes. The goal is collision resistance *within* each type, not between types.
Refer to [Appendix A](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#appendix-a---hash-function) of Phase 0 of the [Eth2.0 specs](https://github.com/ethereum/eth2.0-specs) for a definition of the hash function used below, `hash(x)`. Refer to [Appendix A](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#appendix-a---hash-function) of Phase 0 of the [Eth2.0 specs](https://github.com/ethereum/eth2.0-specs) for a definition of the hash function used below, `hash(x)`.
@ -435,13 +435,13 @@ def merkle_hash(lst):
return hash(chunkz[0] + datalen) return hash(chunkz[0] + datalen)
``` ```
To `SSZTreeHash` a list, we simply do: To `hash_tree_root` a list, we simply do:
```python ```python
return merkle_hash([SSZTreeHash(item) for item in value]) return merkle_hash([hash_tree_root(item) for item in value])
``` ```
Where the inner `SSZTreeHash` is a recursive application of the tree-hashing function (returning less than 32 bytes for short single values). Where the inner `hash_tree_root` is a recursive application of the tree-hashing function (returning less than 32 bytes for short single values).
#### Container #### Container
@ -449,7 +449,7 @@ Where the inner `SSZTreeHash` is a recursive application of the tree-hashing fun
Recursively tree hash the values in the container in order sorted by key, and return the hash of the concatenation of the results. Recursively tree hash the values in the container in order sorted by key, and return the hash of the concatenation of the results.
```python ```python
return hash(b''.join([SSZTreeHash(getattr(x, field)) for field in sorted(value.fields))) return hash(b''.join([hash_tree_root(getattr(x, field)) for field in sorted(value.fields)))
``` ```