Skip to content

Commit d9106c7

Browse files
committed
Initial test of sync aggregates without publishing
1 parent 75501d5 commit d9106c7

File tree

7 files changed

+215
-4
lines changed

7 files changed

+215
-4
lines changed

lib/constants.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ defmodule Constants do
3636
@spec target_aggregators_per_committee() :: non_neg_integer()
3737
def target_aggregators_per_committee(), do: 16
3838

39+
@spec target_aggregators_per_sync_subcommittee() :: non_neg_integer()
40+
def target_aggregators_per_sync_subcommittee(), do: 16
41+
3942
### Withdrawal prefixes
4043

4144
@spec bls_withdrawal_prefix() :: Types.bytes1()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
defmodule LambdaEthereumConsensus.P2P.Gossip.SyncCommittee do
2+
@moduledoc """
3+
This module handles sync committee from specific gossip subnets.
4+
Used by validators to fulfill aggregation duties.
5+
"""
6+
alias LambdaEthereumConsensus.ForkChoice
7+
8+
require Logger
9+
10+
@spec publish(Types.SyncCommitteeMessage.t(), [non_neg_integer()]) :: :ok
11+
def publish(%Types.SyncCommitteeMessage{} = message, subnet_ids) do
12+
Enum.each(subnet_ids, fn subnet_id ->
13+
topic = topic(subnet_id)
14+
15+
Logger.info(
16+
"[SyncCommittee] Publishing attestation, topic: #{topic}, message #{inspect(message, pretty: true)}"
17+
)
18+
end)
19+
end
20+
21+
defp topic(subnet_id) do
22+
# TODO: this doesn't take into account fork digest changes
23+
fork_context = ForkChoice.get_fork_digest() |> Base.encode16(case: :lower)
24+
"/eth2/#{fork_context}/sync_committee_#{subnet_id}/ssz_snappy"
25+
end
26+
end

lib/lambda_ethereum_consensus/state_transition/misc.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,15 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do
281281
{committee_start, committee_end}
282282
end
283283

284+
@doc """
285+
Compute the sync committee period for the given ``epoch``. This is used to determine the
286+
period in which a validator is assigned to the sync committee.
287+
"""
288+
@spec compute_sync_committee_period(Types.epoch()) :: Types.uint64()
289+
def compute_sync_committee_period(epoch) do
290+
div(epoch, ChainSpec.get("EPOCHS_PER_SYNC_COMMITTEE_PERIOD"))
291+
end
292+
284293
@doc """
285294
Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``.
286295
This is used primarily in signature domains to avoid collisions across forks/chains.

lib/lambda_ethereum_consensus/validator/duties.ex

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,26 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
2222
index_in_committee: Types.uint64()
2323
}
2424

25-
@type proposer_duty :: Types.slot()
25+
@type proposer_duty :: Types.validator_index()
26+
27+
@type sync_committee_duty :: %{
28+
last_slot_broadcasted: Types.slot(),
29+
subnet_ids: [Types.uint64()],
30+
validator_index: Types.validator_index()
31+
}
2632

2733
@type attester_duties :: [attester_duty()]
2834
@type proposer_duties :: [proposer_duty()]
35+
@type sync_committee_duties :: [sync_committee_duty()]
2936

3037
@type attester_duties_per_slot :: %{Types.slot() => attester_duties()}
3138
@type proposer_duties_per_slot :: %{Types.slot() => proposer_duties()}
3239

33-
@type kind :: :proposers | :attesters
34-
@type duties :: %{kind() => attester_duties_per_slot() | proposer_duties_per_slot()}
40+
@type kind :: :proposers | :attesters | :sync_committees
41+
@type duties :: %{
42+
kind() =>
43+
attester_duties_per_slot() | proposer_duties_per_slot() | sync_committee_duties()
44+
}
3545

3646
############################
3747
# Accessors
@@ -40,6 +50,15 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
4050
def current_proposer(duties, epoch, slot),
4151
do: get_in(duties, [epoch, :proposers, slot])
4252

53+
@spec current_sync_committee(duties(), Types.epoch(), Types.slot()) ::
54+
sync_committee_duties()
55+
def current_sync_committee(duties, epoch, slot) do
56+
for %{last_slot_broadcasted: last_slot} = duty <- sync_committee(duties, epoch),
57+
last_slot < slot do
58+
duty
59+
end
60+
end
61+
4362
@spec current_attesters(duties(), Types.epoch(), Types.slot()) :: attester_duties()
4463
def current_attesters(duties, epoch, slot) do
4564
for %{attested?: false} = duty <- attesters(duties, epoch, slot) do
@@ -54,6 +73,7 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
5473
end
5574
end
5675

76+
defp sync_committee(duties, epoch), do: get_in(duties, [epoch, :sync_committees]) || []
5777
defp attesters(duties, epoch, slot), do: get_in(duties, [epoch, :attesters, slot]) || []
5878

5979
############################
@@ -75,6 +95,9 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
7595
@spec aggregated(attester_duty()) :: attester_duty()
7696
def aggregated(duty), do: Map.put(duty, :should_aggregate?, false)
7797

98+
@spec sync_committee_broadcasted(sync_committee_duty(), Types.slot()) :: sync_committee_duty()
99+
def sync_committee_broadcasted(duty, slot), do: Map.put(duty, :last_slot_broadcasted, slot)
100+
78101
############################
79102
# Main functions
80103

@@ -92,6 +115,19 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
92115
end
93116
end
94117

118+
@spec compute_current_sync_committees(BeaconState.t(), ValidatorSet.validators()) ::
119+
sync_committee_duties()
120+
def compute_current_sync_committees(%BeaconState{} = state, validators) do
121+
for validator_index <- Map.keys(validators),
122+
subnet_ids = Utils.compute_subnets_for_sync_committee(state, validator_index) do
123+
%{
124+
last_slot_broadcasted: -1,
125+
subnet_ids: subnet_ids,
126+
validator_index: validator_index
127+
}
128+
end
129+
end
130+
95131
@spec compute_attesters_for_epoch(BeaconState.t(), Types.epoch(), ValidatorSet.validators()) ::
96132
attester_duties_per_slot()
97133
def compute_attesters_for_epoch(%BeaconState{} = state, epoch, validators) do

lib/lambda_ethereum_consensus/validator/utils.ex

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,67 @@ defmodule LambdaEthereumConsensus.Validator.Utils do
5252
|> :binary.decode_unsigned(:little)
5353
|> rem(modulo) == 0
5454
end
55+
56+
@spec compute_subnets_for_sync_committee(BeaconState.t(), Types.validator_index()) :: [
57+
Types.uint64()
58+
]
59+
def compute_subnets_for_sync_committee(%BeaconState{} = state, validator_index) do
60+
target_pubkey = state.validators[validator_index].pubkey
61+
current_epoch = Accessors.get_current_epoch(state)
62+
next_slot_epoch = Misc.compute_epoch_at_slot(state.slot + 1)
63+
current_sync_committee_period = Misc.compute_sync_committee_period(current_epoch)
64+
next_slot_sync_committee_period = Misc.compute_sync_committee_period(next_slot_epoch)
65+
66+
sync_committee =
67+
if current_sync_committee_period == next_slot_sync_committee_period,
68+
do: state.current_sync_committee,
69+
else: state.next_sync_committee
70+
71+
sync_committee_subnet_size =
72+
div(ChainSpec.get("SYNC_COMMITTEE_SIZE"), Constants.sync_committee_subnet_count())
73+
74+
for {pubkey, index} <- Enum.with_index(sync_committee.pubkeys),
75+
pubkey == target_pubkey do
76+
div(index, sync_committee_subnet_size)
77+
end
78+
|> Enum.dedup()
79+
end
80+
81+
# `is_assigned_to_sync_committee` equivalent
82+
@spec assigned_to_sync_committee?(BeaconState.t(), Types.epoch(), Types.validator_index()) ::
83+
boolean()
84+
def assigned_to_sync_committee?(%BeaconState{} = state, epoch, validator_index) do
85+
sync_committee_period = Misc.compute_sync_committee_period(epoch)
86+
current_epoch = Accessors.get_current_epoch(state)
87+
current_sync_committee_period = Misc.compute_sync_committee_period(current_epoch)
88+
next_sync_committee_period = current_sync_committee_period + 1
89+
90+
pubkey = state.validators[validator_index].pubkey
91+
92+
case sync_committee_period do
93+
^current_sync_committee_period ->
94+
Enum.member?(state.current_sync_committee.pubkeys, pubkey)
95+
96+
^next_sync_committee_period ->
97+
Enum.member?(state.next_sync_committee.pubkeys, pubkey)
98+
99+
_ ->
100+
raise ArgumentError,
101+
"Invalid epoch #{epoch}, should be in the current or next sync committee period"
102+
end
103+
end
104+
105+
@spec sync_committee_aggregator?(Types.bls_signature()) :: boolean()
106+
def sync_committee_aggregator?(signature) do
107+
modulo =
108+
ChainSpec.get("SYNC_COMMITTEE_SIZE")
109+
|> div(Constants.sync_committee_subnet_count())
110+
|> div(Constants.target_aggregators_per_sync_subcommittee())
111+
|> max(1)
112+
113+
SszEx.hash(signature)
114+
|> binary_part(0, 8)
115+
|> :binary.decode_unsigned(:little)
116+
|> rem(modulo) == 0
117+
end
55118
end

lib/lambda_ethereum_consensus/validator/validator.ex

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,54 @@ defmodule LambdaEthereumConsensus.Validator do
228228
Enum.find_index(beacon.validators, &(&1.pubkey == pubkey))
229229
end
230230

231+
################################
232+
# Sync Committee
233+
234+
@spec sync_committee_message_broadcast(
235+
t(),
236+
Duties.sync_committee_duty(),
237+
Types.slot(),
238+
Types.root()
239+
) ::
240+
:ok
241+
def sync_committee_message_broadcast(
242+
%{index: validator_index, keystore: keystore},
243+
current_duty,
244+
slot,
245+
head_root
246+
) do
247+
%{subnet_ids: subnet_ids} = current_duty
248+
249+
head_state = BlockStates.get_state_info!(head_root).beacon_state |> go_to_slot(slot)
250+
log_debug(validator_index, "broadcasting sync committee message", slot: slot)
251+
252+
head_state
253+
|> get_sync_committee_message(head_root, validator_index, keystore.privkey)
254+
|> Gossip.SyncCommittee.publish(subnet_ids)
255+
|> log_info_result(validator_index, "published sync committee message", slot: slot)
256+
end
257+
258+
@spec get_sync_committee_message(
259+
Types.BeaconState.t(),
260+
Types.root(),
261+
Types.validator_index(),
262+
Bls.privkey()
263+
) ::
264+
Types.SyncCommitteeMessage.t()
265+
def get_sync_committee_message(head_state, head_root, validator_index, privkey) do
266+
epoch = Accessors.get_current_epoch(head_state)
267+
domain = Accessors.get_domain(head_state, Constants.domain_sync_committee(), epoch)
268+
signing_root = Misc.compute_signing_root(head_root, domain)
269+
{:ok, signature} = Bls.sign(privkey, signing_root)
270+
271+
%Types.SyncCommitteeMessage{
272+
slot: head_state.slot,
273+
beacon_block_root: head_root,
274+
validator_index: validator_index,
275+
signature: signature
276+
}
277+
end
278+
231279
################################
232280
# Payload building and proposing
233281

lib/lambda_ethereum_consensus/validator/validator_set.ex

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
8080
|> update_state(epoch, slot, head_root)
8181
|> attests(epoch, slot, head_root)
8282
|> build_payload(slot + 1, head_root)
83+
|> sync_committee_broadcasts(epoch, slot, head_root)
8384
end
8485

8586
@doc """
@@ -103,6 +104,7 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
103104
set
104105
|> attests(epoch, slot, head_root)
105106
|> build_payload(slot + 1, head_root)
107+
|> sync_committee_broadcasts(epoch, slot, head_root)
106108
end
107109

108110
defp process_tick(set, epoch, {slot, :last_third}) do
@@ -140,7 +142,8 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
140142

141143
duties = %{
142144
proposers: Duties.compute_proposers_for_epoch(beacon, epoch, set.validators),
143-
attesters: Duties.compute_attesters_for_epoch(beacon, epoch, set.validators)
145+
attesters: Duties.compute_attesters_for_epoch(beacon, epoch, set.validators),
146+
sync_committees: Duties.compute_current_sync_committees(beacon, set.validators)
144147
}
145148

146149
Duties.log_duties_for_epoch(duties, epoch)
@@ -188,6 +191,29 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
188191

189192
defp update_validators(new_validators, set), do: %{set | validators: new_validators}
190193

194+
##############################
195+
# Sync committee
196+
197+
defp sync_committee_broadcasts(set, epoch, slot, head_root) do
198+
case Duties.current_sync_committee(set.duties, epoch, slot) do
199+
[] ->
200+
set
201+
202+
sync_committee_duties ->
203+
sync_committee_duties
204+
|> Enum.map(&sync_committee_broadcast(&1, slot, head_root, set.validators))
205+
|> update_duties(set, epoch, :sync_committees, slot)
206+
end
207+
end
208+
209+
defp sync_committee_broadcast(duty, slot, head_root, validators) do
210+
validators
211+
|> Map.get(duty.validator_index)
212+
|> Validator.sync_committee_message_broadcast(duty, slot, head_root)
213+
214+
Duties.sync_committee_broadcasted(duty, slot)
215+
end
216+
191217
##############################
192218
# Attestation
193219

0 commit comments

Comments
 (0)