Skip to content

Commit 0edc9d5

Browse files
authored
Track ptc assignment (#7757)
* track ptc assignment * update All-Tests-mainnet.md * reduce registerDuties stack usage * fix stackoverflow in PTCDuties registration by using closure iterators * fix: address reviews
1 parent 3155e88 commit 0edc9d5

File tree

5 files changed

+133
-5
lines changed

5 files changed

+133
-5
lines changed

AllTests-mainnet.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,9 +1166,11 @@ AllTests-mainnet
11661166
```
11671167
## subnet tracker
11681168
```diff
1169+
+ should register and prune PTC duties OK
11691170
+ should register stability subnets on attester duties OK
11701171
+ should register sync committee duties OK
11711172
+ should subscribe to all subnets when flag is enabled OK
1173+
+ should track PTC duties in slot bitmaps OK
11721174
```
11731175
## test_fixture_ssz_generic_types.nim
11741176
```diff

beacon_chain/spec/beaconstate.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2763,8 +2763,8 @@ func can_advance_slots*(
27632763
withState(state): forkyState.can_advance_slots(block_root, target_slot)
27642764

27652765
# https://github.com/ethereum/consensus-specs/blob/v1.6.0-alpha.6/specs/gloas/beacon-chain.md#new-get_ptc
2766-
iterator get_ptc(state: gloas.BeaconState, slot: Slot, cache: var StateCache):
2767-
ValidatorIndex =
2766+
iterator get_ptc*(state: gloas.BeaconState, slot: Slot, cache: var StateCache):
2767+
ValidatorIndex {.closure.} =
27682768
## Get the payload timeliness committee for the given ``slot``
27692769
let epoch = slot.epoch()
27702770
var buffer {.noinit.}: array[40, byte]

beacon_chain/validators/action_tracker.nim

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ type
3838
subnet_id*: SubnetId
3939
slot*: Slot
4040

41+
PTCDuty* = object
42+
slot*: Slot
43+
validator_index: ValidatorIndex
44+
4145
ActionTracker* = object
4246
nodeId: UInt256
4347

@@ -73,6 +77,9 @@ type
7377
lastSyncUpdate*: Opt[SyncCommitteePeriod]
7478
syncDuties*: Table[ValidatorPubKey, Epoch]
7579

80+
ptcSlots*: array[2, uint32]
81+
ptcDuties*: HashSet[PTCDuty]
82+
7683
func hash*(x: AggregatorDuty): Hash =
7784
hashAllFields(x)
7885

@@ -116,6 +123,32 @@ func hasSyncDuty*(
116123
tracker: ActionTracker, pubkey: ValidatorPubKey, epoch: Epoch): bool =
117124
epoch < tracker.syncDuties.getOrDefault(pubkey, GENESIS_EPOCH)
118125

126+
proc registerPTCDuty*(
127+
tracker: var ActionTracker, slot: Slot, vidx: ValidatorIndex) =
128+
if slot < tracker.currentSlot or
129+
slot >= Slot(uint64(tracker.currentSlot) + (SLOTS_PER_EPOCH * 2)):
130+
debug "Irrelevant PTC duty", slot, vidx
131+
return
132+
133+
tracker.knownValidators[vidx] = slot
134+
135+
let newDuty = PTCDuty(slot: slot, validator_index: vidx)
136+
137+
debug "Registering PTC duty", slot, vidx
138+
tracker.ptcDuties.incl(newDuty)
139+
140+
from std/sequtils import anyIt, toSeq
141+
142+
func hasPTCDuty*(tracker: ActionTracker, slot: Slot): bool =
143+
tracker.ptcDuties.anyIt(it.slot == slot)
144+
145+
func getPTCDuties*(tracker: ActionTracker, slot: Slot): seq[ValidatorIndex] =
146+
var duties: seq[ValidatorIndex]
147+
for duty in tracker.ptcDuties:
148+
if duty.slot == slot:
149+
duties.add(duty.validator_index)
150+
duties
151+
119152
func aggregateSubnets*(tracker: ActionTracker, wallSlot: Slot): AttnetBits =
120153
var res: AttnetBits
121154
# Subscribe to subnets for upcoming duties
@@ -146,6 +179,7 @@ proc updateSlot*(tracker: var ActionTracker, wallSlot: Slot) =
146179
# are only so many slot/subnet combos - prune both internal and API-supplied
147180
# duties at the same time
148181
tracker.duties.keepItIf(it.slot >= wallSlot)
182+
tracker.ptcDuties.keepItIf(it.slot >= wallSlot)
149183

150184
block:
151185
var dels: seq[ValidatorPubKey]
@@ -211,8 +245,6 @@ func needsUpdate*(
211245
tracker.attesterDepRoot !=
212246
state.dependent_root(if epoch > Epoch(0): epoch - 1 else: epoch)
213247

214-
from std/sequtils import toSeq
215-
216248
func updateActions*(
217249
tracker: var ActionTracker, shufflingRef: ShufflingRef,
218250
beaconProposers: openArray[Opt[ValidatorIndex]]) =
@@ -232,6 +264,7 @@ func updateActions*(
232264
tracker.proposingSlots[epoch mod 2] or (1'u32 shl i)
233265

234266
tracker.attestingSlots[epoch mod 2] = 0
267+
tracker.ptcSlots[epoch mod 2] = 0
235268

236269
# The relevant bitmaps are 32 bits each.
237270
static: doAssert SLOTS_PER_EPOCH <= 32
@@ -257,6 +290,12 @@ func updateActions*(
257290
tracker.attestingSlots[epoch mod 2] or
258291
(1'u32 shl (slot mod SLOTS_PER_EPOCH))
259292

293+
for duty in tracker.ptcDuties:
294+
if duty.slot.epoch == epoch:
295+
tracker.ptcSlots[epoch mod 2] =
296+
tracker.ptcSlots[epoch mod 2] or
297+
(1'u32 shl (duty.slot mod SLOTS_PER_EPOCH))
298+
260299
func init*(
261300
T: type ActionTracker, nodeId: UInt256, subscribeAllAttnets: bool): T =
262301
T(

beacon_chain/validators/beacon_validators.nim

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import
4343
validator_pool,
4444
]
4545

46-
from std/sequtils import mapIt
46+
from std/sequtils import mapIt, toSeq
4747
from eth/async_utils import awaitWithTimeout
4848
from ./message_router_mev import unblindAndRouteBlockMEV
4949

@@ -1282,6 +1282,30 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async: (ra
12821282
sendAggregatedAttestations(node, head, slot)
12831283
sendSyncCommitteeContributions(node, head, slot)
12841284

1285+
proc registerPTCDuties(node: BeaconNode, epoch: Epoch) =
1286+
if node.dag.cfg.consensusForkAtEpoch(epoch) < ConsensusFork.Gloas:
1287+
return
1288+
1289+
let validatorIndices = block:
1290+
var res: HashSet[ValidatorIndex]
1291+
for idx in node.attachedValidators[].indices():
1292+
res.incl(idx)
1293+
res
1294+
1295+
withState(node.dag.headState):
1296+
when consensusFork >= ConsensusFork.Gloas:
1297+
var cache: StateCache
1298+
1299+
for slot in epoch.slots():
1300+
for validator_index in get_ptc(forkyState.data, slot, cache):
1301+
if validator_index in validatorIndices:
1302+
node.consensusManager[].actionTracker.registerPTCDuty(
1303+
slot, validator_index)
1304+
1305+
debug "PTC duty registered",
1306+
slot = slot,
1307+
epoch = epoch
1308+
12851309
proc registerDuties*(node: BeaconNode, wallSlot: Slot) {.async: (raises: [CancelledError]).} =
12861310
## Register upcoming duties of attached validators with the duty tracker
12871311

@@ -1327,3 +1351,6 @@ proc registerDuties*(node: BeaconNode, wallSlot: Slot) {.async: (raises: [Cancel
13271351

13281352
node.consensusManager[].actionTracker.registerDuty(
13291353
slot, subnet_id, validator_index, isAggregator)
1354+
1355+
if wallSlot == wallSlot.epoch.start_slot():
1356+
node.registerPTCDuties(wallSlot.epoch + 1)

tests/test_action_tracker.nim

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import
1212
unittest2,
1313
../beacon_chain/validators/action_tracker
1414

15+
from ../beacon_chain/consensus_object_pools/block_pools_types import
16+
ShufflingRef
17+
1518
suite "subnet tracker":
1619
test "should register stability subnets on attester duties":
1720
var tracker = ActionTracker.init(default(UInt256), false)
@@ -98,3 +101,60 @@ suite "subnet tracker":
98101
check:
99102
tracker.stabilitySubnets(Slot(0)).countOnes() == 64 # All 64 subnets
100103
tracker.aggregateSubnets(Slot(0)).countOnes() == 0
104+
105+
test "should register and prune PTC duties":
106+
var tracker = ActionTracker.init(default(UInt256), false)
107+
tracker.updateSlot(Slot(100))
108+
109+
check:
110+
not tracker.hasPTCDuty(Slot(100))
111+
112+
# Register past duty
113+
tracker.registerPTCDuty(Slot(99), ValidatorIndex(0))
114+
check not tracker.hasPTCDuty(Slot(99))
115+
116+
# Register duty too far in future
117+
tracker.registerPTCDuty(Slot(100 + SLOTS_PER_EPOCH * 2), ValidatorIndex(0))
118+
check not tracker.hasPTCDuty(Slot(100 + SLOTS_PER_EPOCH * 2))
119+
120+
tracker.registerPTCDuty(Slot(105), ValidatorIndex(100))
121+
tracker.registerPTCDuty(Slot(105), ValidatorIndex(101))
122+
tracker.registerPTCDuty(Slot(110), ValidatorIndex(102))
123+
124+
check:
125+
tracker.hasPTCDuty(Slot(105))
126+
tracker.hasPTCDuty(Slot(110))
127+
not tracker.hasPTCDuty(Slot(107))
128+
tracker.knownValidators.len() == 3
129+
130+
# Update slot to prune old duties
131+
tracker.updateSlot(Slot(107))
132+
check:
133+
not tracker.hasPTCDuty(Slot(105))
134+
tracker.hasPTCDuty(Slot(110))
135+
136+
# Validator decays after a long time
137+
tracker.updateSlot(Slot(110 + KNOWN_VALIDATOR_DECAY + 1))
138+
check tracker.knownValidators.len() == 0
139+
140+
test "should track PTC duties in slot bitmaps":
141+
var
142+
tracker = ActionTracker.init(default(UInt256), false)
143+
shufflingRef = ShufflingRef(
144+
epoch: Epoch(1),
145+
attester_dependent_root: ZERO_HASH,
146+
shuffled_active_validator_indices: @[]
147+
)
148+
beaconProposers: array[SLOTS_PER_EPOCH, Opt[ValidatorIndex]]
149+
150+
tracker.registerPTCDuty(Slot(32), ValidatorIndex(0)) # First slot of epoch
151+
tracker.registerPTCDuty(Slot(47), ValidatorIndex(1)) # Mid epoch
152+
tracker.registerPTCDuty(Slot(63), ValidatorIndex(2)) # Last slot of epoch
153+
154+
# Update actions to populate bitmaps
155+
tracker.updateActions(shufflingRef, beaconProposers)
156+
157+
check:
158+
(tracker.ptcSlots[1] and (1'u32 shl 0)) != 0 # Slot 32
159+
(tracker.ptcSlots[1] and (1'u32 shl 15)) != 0 # Slot 47
160+
(tracker.ptcSlots[1] and (1'u32 shl 31)) != 0 # Slot 63

0 commit comments

Comments
 (0)