We need to implement a sustainable block-operations pool for storage of objects that may be included in blocks.
The following v0.4.0 block operations (blops, lol) are covered in the scope of this issue:
AttestationDepositProposerSlashingAttesterSlashingVoluntaryExitTransferFor each of these items, we need to provide a pool/queue so they can potentially be included in a future block.
This section provides the following information for each of the blops:
_Note: a pool should not contain duplicate items._
AttestationEasily the most complex case as it involves aggregating attestations and has the most complex validation logic.
Shall accept an Attestation and aggregate it with an existing Attestation if possible (ask for more details).
The Attestation must be valid for
Shall provide a function to return Attestations for some (beacon_block, beacon_state) pair such that each attestation passes the block inclusion validity conditions.
If the number of attestations for a block exceed MAX_ATTESTATIONS, then attestations should be included to maximize the attestation inclusion rewards for the block proposer.
Delete if:
state be the latest state.attestation.data.slot < state.slot - spec.slots_per_epoch: such an Attestation is too old to be included in a block.DepositNo validation conditions -- deposits are provided from our own Eth1 service so we assume all are valid.
Shall provide a function to return Deposits for some (beacon_block, beacon_state) pair which:
deposit.indexShall remove Deposits from the pool if:
state be the latest finalised state.state.deposit_index > deposit.indexProposerSlashing & AttesterSlashing_Both objects have the same conditions._
Must pass the following requirements:
state.validator_registryShall provide a function to return Deposits for some (beacon_block, beacon_state) pair where:
validator.slashed == false)Shall remove slashings from the pool if the implicated validators are slashed or withdrawn in the latest finalized state.
VoluntaryExitMust pass the following conditions:
validator_index is in the latest state.validator_registry.signature is valid.Shall provide a function to return VoluntaryExits for some (beacon_block, beacon_state) pair such that the exit passes the block inclusion validity conditions.
Shall delete Exits where the specified validator is exited in the latest finalized state.
TransferMust pass the following conditions:
transfer.slot is not in the past.from and to validators are known in the latest state.transfer.signature is valid.Shall provide a function to return Transfers for some (beacon_block, beacon_state) pair such that the transfer passes the block inclusion validity conditions.
Shall delete all Transfers where transfer.slot is in the past.
I'm thinking it might be sensible to leave Attestation out of scope now -- it's pretty significant. Apologies for the giant text.
Note: I just updated the deletion conditions for Attestation
To clarify, we're talking about an in-memory-only pool for now, yeah?
To clarify, we're talking about an in-memory-only pool for now, yeah?
Yep!
I'm doing a refactor of the block/state processing logic for v0.4.0 and it's producing a lot of functions that will be useful for the inclusion validator conditions here.
Hopefully I can get a WIP PR up this afternoon -- I'll point out the useful functions to you :)
Hey, related to our conversation a few minutes ago -- here's my step-by-step of how I think attestation aggregation should work.
_Note: I'm not 100% sure this is accurate. Keep an eye out for edge cases or oversights on my behalf._
Attestation (a) from the network.a.aggregate_signature against some BeaconState, discard if invalid.a.data exists in the pool (lets call it b) (see "Key-ing AttestationData")a with b (see "Can two attestations be aggregated")a and b into ab (see "Aggregating two attestations").a.b from the pool.ab to the pool.a as is, adding b to the pool as well.They can be aggregated if the following two conditions are satisfied:
a.data == b.data a.aggregation_bitfield and the set of bits on b.aggregration_bitfield are disjoint (a.k.a. don't aggregate two aggregate_signatures that both have the same signature on them).I assume you're going to build a HashMap of AttestationData -> Attestation so you can ask "do I have an attestation with the same attestation data?".
The naive way to do that would be to just Impl Hash for AttestationData and let HashMap deal with it. _However_, that does not have built in protection against forks. The same AttestationData on fork A would have the same key as the one on fork B.
As such, I would use this for keying Attestation (no guarantees this compiles, just for demonstration):
let epoch = attestation_data.slot.epoch(spec.slots_per_epoch);
let mut key: Vec<u8> = ssz_encode(&attestation_data);
key.append(&mut int_to_bytes8(spec.get_domain(epoch, Domain::Attestation, &state.fork)));
key // `HashMap` can just hash this however it wants.
ab.aggregate_signature = a.aggregate_signature.add_aggregate(b.aggregate_signature)*ab.custody_bitfield becomes the union of the custody_bitfields on a and b.ab.aggregation_bitfield becomes the union of the aggregation_bitfields on a and b.*: AggregateSignature::aggregate_signature(..) is not yet implemented, however it is present on our underlying library. I'll implement it on the v0.5.0 branch and ping you when it's done.