diff --git a/configs/mainnet/altair.yaml b/configs/mainnet/altair.yaml index f30e9fcdc5..1bebafe907 100644 --- a/configs/mainnet/altair.yaml +++ b/configs/mainnet/altair.yaml @@ -17,7 +17,7 @@ PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: 2 # 2**10 (= 1,024) SYNC_COMMITTEE_SIZE: 1024 # 2**6 (= 64) -SYNC_SUBCOMMITTEE_SIZE: 64 +SYNC_PUBKEYS_PER_AGGREGATE: 64 # 2**2 (= 4) INACTIVITY_SCORE_BIAS: 4 diff --git a/configs/minimal/altair.yaml b/configs/minimal/altair.yaml index afdaf9eb5b..f10e10bb97 100644 --- a/configs/minimal/altair.yaml +++ b/configs/minimal/altair.yaml @@ -17,7 +17,7 @@ PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: 2 # [customized] SYNC_COMMITTEE_SIZE: 32 # [customized] -SYNC_SUBCOMMITTEE_SIZE: 16 +SYNC_PUBKEYS_PER_AGGREGATE: 16 # 2**2 (= 4) INACTIVITY_SCORE_BIAS: 4 @@ -35,7 +35,8 @@ DOMAIN_SYNC_COMMITTEE: 0x07000000 # Fork # --------------------------------------------------------------- -ALTAIR_FORK_VERSION: 0x01000000 +# Highest byte set to 0x01 to avoid collisions with mainnet versioning +ALTAIR_FORK_VERSION: 0x01000001 # [customized] ALTAIR_FORK_SLOT: 0 diff --git a/setup.py b/setup.py index ef6c65289d..9de8ef9111 100644 --- a/setup.py +++ b/setup.py @@ -537,6 +537,7 @@ def finalize_options(self): specs/phase0/weak-subjectivity.md specs/altair/beacon-chain.md specs/altair/fork.md + specs/altair/validator.md specs/altair/sync-protocol.md """ else: diff --git a/specs/altair/beacon-chain.md b/specs/altair/beacon-chain.md index 58e82364ce..a004f3fb7d 100644 --- a/specs/altair/beacon-chain.md +++ b/specs/altair/beacon-chain.md @@ -117,7 +117,7 @@ This patch updates a few configuration values to move penalty parameters toward | Name | Value | | - | - | | `SYNC_COMMITTEE_SIZE` | `uint64(2**10)` (= 1,024) | -| `SYNC_SUBCOMMITTEE_SIZE` | `uint64(2**6)` (= 64) | +| `SYNC_PUBKEYS_PER_AGGREGATE` | `uint64(2**6)` (= 64) | | `INACTIVITY_SCORE_BIAS` | `uint64(4)` | ### Time parameters @@ -210,7 +210,7 @@ class SyncAggregate(Container): ```python class SyncCommittee(Container): pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] - pubkey_aggregates: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE // SYNC_SUBCOMMITTEE_SIZE] + pubkey_aggregates: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE // SYNC_PUBKEYS_PER_AGGREGATE] ``` ## Helper functions @@ -294,8 +294,8 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: """ indices = get_sync_committee_indices(state, epoch) pubkeys = [state.validators[index].pubkey for index in indices] - subcommitees = [pubkeys[i:i + SYNC_SUBCOMMITTEE_SIZE] for i in range(0, len(pubkeys), SYNC_SUBCOMMITTEE_SIZE)] - pubkey_aggregates = [bls.AggregatePKs(subcommitee) for subcommitee in subcommitees] + partition = [pubkeys[i:i + SYNC_PUBKEYS_PER_AGGREGATE] for i in range(0, len(pubkeys), SYNC_PUBKEYS_PER_AGGREGATE)] + pubkey_aggregates = [bls.AggregatePKs(preaggregate) for preaggregate in partition] return SyncCommittee(pubkeys=pubkeys, pubkey_aggregates=pubkey_aggregates) ``` @@ -575,7 +575,7 @@ def process_epoch(state: BeaconState) -> None: #### Justification and finalization -*Note*: The function `process_justification_and_finalization` is modified with `matching_target_attestations` replaced by `matching_target_indices`. +*Note*: The function `process_justification_and_finalization` is modified to adapt to the new participation records. ```python def process_justification_and_finalization(state: BeaconState) -> None: @@ -583,40 +583,12 @@ def process_justification_and_finalization(state: BeaconState) -> None: # Skip FFG updates in the first two epochs to avoid corner cases that might result in modifying this stub. if get_current_epoch(state) <= GENESIS_EPOCH + 1: return - previous_epoch = get_previous_epoch(state) - current_epoch = get_current_epoch(state) - old_previous_justified_checkpoint = state.previous_justified_checkpoint - old_current_justified_checkpoint = state.current_justified_checkpoint - - # Process justifications - state.previous_justified_checkpoint = state.current_justified_checkpoint - state.justification_bits[1:] = state.justification_bits[:JUSTIFICATION_BITS_LENGTH - 1] - state.justification_bits[0] = 0b0 - matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) - if get_total_balance(state, matching_target_indices) * 3 >= get_total_active_balance(state) * 2: - state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, - root=get_block_root(state, previous_epoch)) - state.justification_bits[1] = 0b1 - matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, current_epoch) - if get_total_balance(state, matching_target_indices) * 3 >= get_total_active_balance(state) * 2: - state.current_justified_checkpoint = Checkpoint(epoch=current_epoch, - root=get_block_root(state, current_epoch)) - state.justification_bits[0] = 0b1 - - # Process finalizations - bits = state.justification_bits - # The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source - if all(bits[1:4]) and old_previous_justified_checkpoint.epoch + 3 == current_epoch: - state.finalized_checkpoint = old_previous_justified_checkpoint - # The 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as source - if all(bits[1:3]) and old_previous_justified_checkpoint.epoch + 2 == current_epoch: - state.finalized_checkpoint = old_previous_justified_checkpoint - # The 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as source - if all(bits[0:3]) and old_current_justified_checkpoint.epoch + 2 == current_epoch: - state.finalized_checkpoint = old_current_justified_checkpoint - # The 1st/2nd most recent epochs are justified, the 1st using the 2nd as source - if all(bits[0:2]) and old_current_justified_checkpoint.epoch + 1 == current_epoch: - state.finalized_checkpoint = old_current_justified_checkpoint + previous_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)) + current_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state)) + total_active_balance = get_total_active_balance(state) + previous_target_balance = get_total_balance(state, previous_indices) + current_target_balance = get_total_balance(state, current_indices) + weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) ``` #### Inactivity scores diff --git a/specs/altair/validator.md b/specs/altair/validator.md index 59c1ab81f2..25897b97e0 100644 --- a/specs/altair/validator.md +++ b/specs/altair/validator.md @@ -18,6 +18,7 @@ This is an accompanying document to [Ethereum 2.0 Altair -- The Beacon Chain](./ - [`SyncCommitteeContribution`](#synccommitteecontribution) - [`ContributionAndProof`](#contributionandproof) - [`SignedContributionAndProof`](#signedcontributionandproof) + - [`SyncCommitteeSigningData`](#synccommitteesigningdata) - [Validator assignments](#validator-assignments) - [Sync Committee](#sync-committee) - [Lookahead](#lookahead) @@ -125,6 +126,14 @@ class SignedContributionAndProof(Container): signature: BLSSignature ``` +### `SyncCommitteeSigningData` + +```python +class SyncCommitteeSigningData(Container): + slot: Slot + subcommittee_index: uint64 +``` + ## Validator assignments A validator determines beacon committee assignments and beacon block proposal duties as defined in the Phase 0 document. @@ -137,7 +146,9 @@ This function is a predicate indicating the presence or absence of the validator ```python def compute_sync_committee_period(epoch: Epoch) -> uint64: return epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD +``` +```python def is_assigned_to_sync_committee(state: BeaconState, epoch: Epoch, validator_index: ValidatorIndex) -> bool: @@ -280,9 +291,10 @@ This function returns multiple subnets if a given validator index is included mu ```python def compute_subnets_for_sync_committee(state: BeaconState, validator_index: ValidatorIndex) -> Sequence[uint64]: target_pubkey = state.validators[validator_index].pubkey - sync_committee_indices = [index for index, pubkey in enumerate(state.current_sync_committee.pubkeys) if pubkey == target_pubkey] + sync_committee_indices = [index for index, pubkey in enumerate(state.current_sync_committee.pubkeys) + if pubkey == target_pubkey] return [ - uint64(index // (SYNC_COMMITEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) + uint64(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) for index in sync_committee_indices ] ``` @@ -301,13 +313,8 @@ A validator is selected to aggregate based on the computation in `is_sync_commit The signature function takes a `BeaconState` with the relevant sync committees for the queried `slot` (i.e. `state.slot` is within the span covered by the current or next sync committee period), the `subcommittee_index` equal to the `subnet_id`, and the `privkey` is the BLS private key associated with the validator. ```python -class SyncCommitteeSigningData(Container): - slot: Slot - subcommittee_index: uint64 -``` - -```python -def get_sync_committee_slot_signature(state: BeaconState, slot: Slot, subcommittee_index: uint64, privkey: int) -> BLSSignature: +def get_sync_committee_slot_signature(state: BeaconState, slot: Slot, + subcommittee_index: uint64, privkey: int) -> BLSSignature: domain = get_domain(state, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, compute_epoch_at_slot(slot)) signing_data = SyncCommitteeSigningData( slot=slot, diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 9fcd118523..e56a8ea223 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1320,7 +1320,19 @@ def process_justification_and_finalization(state: BeaconState) -> None: # Skip FFG updates in the first two epochs to avoid corner cases that might result in modifying this stub. if get_current_epoch(state) <= GENESIS_EPOCH + 1: return + previous_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) + current_attestations = get_matching_target_attestations(state, get_current_epoch(state)) + total_active_balance = get_total_active_balance(state) + previous_target_balance = get_attesting_balance(state, previous_attestations) + current_target_balance = get_attesting_balance(state, current_attestations) + weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) +``` +```python +def weigh_justification_and_finalization(state: BeaconState, + total_active_balance: Gwei, + previous_epoch_target_balance: Gwei, + current_epoch_target_balance: Gwei) -> None: previous_epoch = get_previous_epoch(state) current_epoch = get_current_epoch(state) old_previous_justified_checkpoint = state.previous_justified_checkpoint @@ -1330,13 +1342,11 @@ def process_justification_and_finalization(state: BeaconState) -> None: state.previous_justified_checkpoint = state.current_justified_checkpoint state.justification_bits[1:] = state.justification_bits[:JUSTIFICATION_BITS_LENGTH - 1] state.justification_bits[0] = 0b0 - matching_target_attestations = get_matching_target_attestations(state, previous_epoch) # Previous epoch - if get_attesting_balance(state, matching_target_attestations) * 3 >= get_total_active_balance(state) * 2: + if previous_epoch_target_balance * 3 >= total_active_balance * 2: state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, root=get_block_root(state, previous_epoch)) state.justification_bits[1] = 0b1 - matching_target_attestations = get_matching_target_attestations(state, current_epoch) # Current epoch - if get_attesting_balance(state, matching_target_attestations) * 3 >= get_total_active_balance(state) * 2: + if current_epoch_target_balance * 3 >= total_active_balance * 2: state.current_justified_checkpoint = Checkpoint(epoch=current_epoch, root=get_block_root(state, current_epoch)) state.justification_bits[0] = 0b1 diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 281078d209..1767d3d446 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -12,6 +12,10 @@ This is an accompanying document to [Ethereum 2.0 Phase 0 -- The Beacon Chain](. - [Prerequisites](#prerequisites) - [Constants](#constants) - [Misc](#misc) +- [Containers](#containers) + - [`Eth1Block`](#eth1block) + - [`AggregateAndProof`](#aggregateandproof) + - [`SignedAggregateAndProof`](#signedaggregateandproof) - [Becoming a validator](#becoming-a-validator) - [Initialization](#initialization) - [BLS public key](#bls-public-key) @@ -33,7 +37,6 @@ This is an accompanying document to [Ethereum 2.0 Phase 0 -- The Beacon Chain](. - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) - [Randao reveal](#randao-reveal) - [Eth1 Data](#eth1-data) - - [`Eth1Block`](#eth1block) - [`get_eth1_data`](#get_eth1_data) - [Proposer slashings](#proposer-slashings) - [Attester slashings](#attester-slashings) @@ -60,8 +63,6 @@ This is an accompanying document to [Ethereum 2.0 Phase 0 -- The Beacon Chain](. - [Aggregation bits](#aggregation-bits-1) - [Aggregate signature](#aggregate-signature-1) - [Broadcast aggregate](#broadcast-aggregate) - - [`AggregateAndProof`](#aggregateandproof) - - [`SignedAggregateAndProof`](#signedaggregateandproof) - [Phase 0 attestation subnet stability](#phase-0-attestation-subnet-stability) - [How to avoid slashing](#how-to-avoid-slashing) - [Proposer slashing](#proposer-slashing) @@ -92,6 +93,35 @@ All terminology, constants, functions, and protocol mechanics defined in the [Ph | `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` | `2**8` (= 256) | epochs | ~27 hours | | `ATTESTATION_SUBNET_COUNT` | `64` | The number of attestation subnets used in the gossipsub protocol. | +## Containers + +### `Eth1Block` + +```python +class Eth1Block(Container): + timestamp: uint64 + deposit_root: Root + deposit_count: uint64 + # All other eth1 block fields +``` + +### `AggregateAndProof` + +```python +class AggregateAndProof(Container): + aggregator_index: ValidatorIndex + aggregate: Attestation + selection_proof: BLSSignature +``` + +### `SignedAggregateAndProof` + +```python +class SignedAggregateAndProof(Container): + message: AggregateAndProof + signature: BLSSignature +``` + ## Becoming a validator ### Initialization @@ -302,20 +332,10 @@ If over half of the block proposers in the current Eth1 voting period vote for t `eth1_data` then `state.eth1_data` updates immediately allowing new deposits to be processed. Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`. -###### `Eth1Block` +###### `get_eth1_data` Let `Eth1Block` be an abstract object representing Eth1 blocks with the `timestamp` and depost contract data available. -```python -class Eth1Block(Container): - timestamp: uint64 - deposit_root: Root - deposit_count: uint64 - # All other eth1 block fields -``` - -###### `get_eth1_data` - Let `get_eth1_data(block: Eth1Block) -> Eth1Data` be the function that returns the Eth1 data for a given Eth1 block. An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state, eth1_chain)` where: @@ -581,23 +601,6 @@ def get_aggregate_and_proof_signature(state: BeaconState, return bls.Sign(privkey, signing_root) ``` -##### `AggregateAndProof` - -```python -class AggregateAndProof(Container): - aggregator_index: ValidatorIndex - aggregate: Attestation - selection_proof: BLSSignature -``` - -##### `SignedAggregateAndProof` - -```python -class SignedAggregateAndProof(Container): - message: AggregateAndProof - signature: BLSSignature -``` - ## Phase 0 attestation subnet stability Because Phase 0 does not have shards and thus does not have Shard Committees, there is no stable backbone to the attestation subnets (`beacon_attestation_{subnet_id}`). To provide this stability, each validator must: diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index ddb01adcb6..caf44e983d 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -97,8 +97,7 @@ def deco(fn): def entry(*args, spec: Spec, phases: SpecForks, **kw): # make a key for the state - # genesis fork version separates configs during test-generation runtime. - key = (spec.fork, spec.GENESIS_FORK_VERSION, spec.__file__, balances_fn, threshold_fn) + key = (spec.fork, spec.CONFIG_NAME, spec.__file__, balances_fn, threshold_fn) global _custom_state_cache_dict if key not in _custom_state_cache_dict: state = _prepare_state(balances_fn, threshold_fn, spec, phases) diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index 46bc62fe53..14fd9aa474 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -28,6 +28,11 @@ def create_genesis_state(spec, validator_balances, activation_threshold): deposit_count=len(validator_balances), block_hash=eth1_block_hash, ), + fork=spec.Fork( + previous_version=spec.GENESIS_FORK_VERSION, + current_version=spec.GENESIS_FORK_VERSION, + epoch=spec.GENESIS_EPOCH, + ), latest_block_header=spec.BeaconBlockHeader(body_root=spec.hash_tree_root(spec.BeaconBlockBody())), randao_mixes=[eth1_block_hash] * spec.EPOCHS_PER_HISTORICAL_VECTOR, ) diff --git a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py index 1ec40c56fc..41fbd77262 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py @@ -16,6 +16,13 @@ def get_post_altair_description(spec): return f"Although it's not phase 0, we may use {spec.fork} spec to start testnets." +def eth1_init_data(eth1_block_hash, eth1_timestamp): + yield 'eth1', { + 'eth1_block_hash': '0x' + eth1_block_hash.hex(), + 'eth1_timestamp': int(eth1_timestamp), + } + + @with_all_phases @spec_test @single_phase @@ -35,8 +42,7 @@ def test_initialize_beacon_state_from_eth1(spec): eth1_block_hash = b'\x12' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME - yield 'eth1_block_hash', eth1_block_hash - yield 'eth1_timestamp', eth1_timestamp + yield from eth1_init_data(eth1_block_hash, eth1_timestamp) yield 'deposits', deposits # initialize beacon_state @@ -79,8 +85,7 @@ def test_initialize_beacon_state_some_small_balances(spec): eth1_block_hash = b'\x12' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME - yield 'eth1_block_hash', eth1_block_hash - yield 'eth1_timestamp', eth1_timestamp + yield from eth1_init_data(eth1_block_hash, eth1_timestamp) yield 'deposits', deposits # initialize beacon_state @@ -136,8 +141,7 @@ def test_initialize_beacon_state_one_topup_activation(spec): eth1_block_hash = b'\x13' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME - yield 'eth1_block_hash', eth1_block_hash - yield 'eth1_timestamp', eth1_timestamp + yield from eth1_init_data(eth1_block_hash, eth1_timestamp) yield 'deposits', deposits # initialize beacon_state @@ -165,8 +169,7 @@ def test_initialize_beacon_state_random_invalid_genesis(spec): eth1_block_hash = b'\x14' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME + 1 - yield 'eth1_block_hash', eth1_block_hash - yield 'eth1_timestamp', eth1_timestamp + yield from eth1_init_data(eth1_block_hash, eth1_timestamp) yield 'deposits', deposits # initialize beacon_state @@ -205,8 +208,7 @@ def test_initialize_beacon_state_random_valid_genesis(spec): eth1_block_hash = b'\x15' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME + 2 - yield 'eth1_block_hash', eth1_block_hash - yield 'eth1_timestamp', eth1_timestamp + yield from eth1_init_data(eth1_block_hash, eth1_timestamp) yield 'deposits', deposits # initialize beacon_state diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py index dd4f302ae1..198ada6b90 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py @@ -10,7 +10,7 @@ def test_slots_1(spec, state): yield 'pre', state slots = 1 - yield 'slots', slots + yield 'slots', int(slots) spec.process_slots(state, state.slot + slots) yield 'post', state @@ -23,7 +23,7 @@ def test_slots_1(spec, state): def test_slots_2(spec, state): yield 'pre', state slots = 2 - yield 'slots', slots + yield 'slots', int(slots) spec.process_slots(state, state.slot + slots) yield 'post', state @@ -33,7 +33,7 @@ def test_slots_2(spec, state): def test_empty_epoch(spec, state): yield 'pre', state slots = spec.SLOTS_PER_EPOCH - yield 'slots', slots + yield 'slots', int(slots) spec.process_slots(state, state.slot + slots) yield 'post', state @@ -43,7 +43,7 @@ def test_empty_epoch(spec, state): def test_double_empty_epoch(spec, state): yield 'pre', state slots = spec.SLOTS_PER_EPOCH * 2 - yield 'slots', slots + yield 'slots', int(slots) spec.process_slots(state, state.slot + slots) yield 'post', state @@ -55,6 +55,6 @@ def test_over_epoch_boundary(spec, state): spec.process_slots(state, state.slot + (spec.SLOTS_PER_EPOCH // 2)) yield 'pre', state slots = spec.SLOTS_PER_EPOCH - yield 'slots', slots + yield 'slots', int(slots) spec.process_slots(state, state.slot + slots) yield 'post', state diff --git a/tests/formats/genesis/initialization.md b/tests/formats/genesis/initialization.md index 7a791dcfa3..73630de51c 100644 --- a/tests/formats/genesis/initialization.md +++ b/tests/formats/genesis/initialization.md @@ -4,13 +4,13 @@ Tests the initialization of a genesis state based on Eth1 data. ## Test case format -### `eth1_block_hash.ssz_snappy` +### `eth1.yaml` -An SSZ-snappy encoded root of the Eth1 block. - -### `eth1_timestamp.yaml` +```yaml +eth1_block_hash: Bytes32 -- A `Bytes32` hex encoded, with prefix 0x. The root of the Eth1 block. E.g. "0x4242424242424242424242424242424242424242424242424242424242424242" +eth1_timestamp: int -- An integer. The timestamp of the block, in seconds. +``` -An integer. The timestamp of the block, in seconds. ### `meta.yaml`