Skip to content

Commit d4f5633

Browse files
ArkenanMegaRedHand
andauthored
feat: add bitlists and bitvectors natively in SSZ nif library (#785)
Co-authored-by: Tomás Grüner <[email protected]>
1 parent 415c1f2 commit d4f5633

File tree

12 files changed

+90
-85
lines changed

12 files changed

+90
-85
lines changed

lib/lambda_ethereum_consensus/p2p/gossip/handler.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule LambdaEthereumConsensus.P2P.Gossip.Handler do
77

88
alias LambdaEthereumConsensus.Beacon.BeaconChain
99
alias LambdaEthereumConsensus.Beacon.PendingBlocks
10-
alias LambdaEthereumConsensus.Utils.BitVector
10+
alias LambdaEthereumConsensus.Utils.BitField
1111
alias Types.{AggregateAndProof, SignedAggregateAndProof, SignedBeaconBlock}
1212

1313
def handle_beacon_block(%SignedBeaconBlock{message: block} = signed_block) do
@@ -25,11 +25,11 @@ defmodule LambdaEthereumConsensus.P2P.Gossip.Handler do
2525
def handle_beacon_aggregate_and_proof(%SignedAggregateAndProof{
2626
message: %AggregateAndProof{aggregate: aggregate}
2727
}) do
28-
votes = BitVector.count(aggregate.aggregation_bits)
28+
votes = BitField.count(aggregate.aggregation_bits)
2929
slot = aggregate.data.slot
3030
root = aggregate.data.beacon_block_root |> Base.encode16()
3131

32-
# We are getting ~500 attestations in half a second. This is overwheling the store GenServer at the moment.
32+
# We are getting ~500 attestations in half a second. This is overwhelming the store GenServer at the moment.
3333
# Store.on_attestation(aggregate)
3434

3535
Logger.debug(

lib/lambda_ethereum_consensus/state_transition/accessors.ex

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
66
alias LambdaEthereumConsensus.SszEx
77
alias LambdaEthereumConsensus.StateTransition.{Cache, Math, Misc, Predicates}
88
alias LambdaEthereumConsensus.Utils
9+
alias LambdaEthereumConsensus.Utils.BitList
910
alias LambdaEthereumConsensus.Utils.Randao
1011
alias Types.{Attestation, BeaconState, IndexedAttestation, SyncCommittee, Validator}
1112

@@ -510,13 +511,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
510511
|> Enum.sort()
511512
end
512513

513-
defp participated?(bits, index) do
514-
# The bit order inside the byte is reversed (e.g. bits[0] is the 8th bit).
515-
# Here we keep the byte index the same, but reverse the bit index.
516-
bit_index = index + 7 - 2 * rem(index, 8)
517-
<<_::size(bit_index), flag::1, _::bits>> = bits
518-
flag == 1
519-
end
514+
defp participated?(bits, index), do: BitList.set?(bits, index)
520515

521516
@doc """
522517
Return the combined effective balance of the ``indices``.

lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -358,16 +358,10 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
358358
end
359359

360360
defp update_first_bit(state) do
361-
bits =
362-
state.justification_bits
363-
|> BitVector.new(4)
364-
|> BitVector.shift_higher(1)
365-
|> BitVector.to_bytes()
366-
367361
%BeaconState{
368362
state
369363
| previous_justified_checkpoint: state.current_justified_checkpoint,
370-
justification_bits: bits
364+
justification_bits: BitVector.shift_higher(state.justification_bits, 1)
371365
}
372366
end
373367

@@ -377,13 +371,11 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
377371
with {:ok, block_root} <- Accessors.get_block_root(state, epoch) do
378372
new_checkpoint = %Types.Checkpoint{epoch: epoch, root: block_root}
379373

380-
bits =
381-
state.justification_bits
382-
|> BitVector.new(4)
383-
|> BitVector.set(index)
384-
|> BitVector.to_bytes()
385-
386-
%{state | current_justified_checkpoint: new_checkpoint, justification_bits: bits}
374+
%{
375+
state
376+
| current_justified_checkpoint: new_checkpoint,
377+
justification_bits: BitVector.set(state.justification_bits, index)
378+
}
387379
|> then(&{:ok, &1})
388380
end
389381
end
@@ -395,10 +387,7 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
395387
range,
396388
offset
397389
) do
398-
bits_set =
399-
state.justification_bits
400-
|> BitVector.new(4)
401-
|> BitVector.all?(range)
390+
bits_set = BitVector.all?(state.justification_bits, range)
402391

403392
if bits_set and old_justified_checkpoint.epoch + offset == current_epoch do
404393
%BeaconState{state | finalized_checkpoint: old_justified_checkpoint}

lib/lambda_ethereum_consensus/state_transition/operations.ex

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,10 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
117117
# Verify sync committee aggregate signature signing over the previous slot block root
118118
committee_pubkeys = state.current_sync_committee.pubkeys
119119

120-
sync_committee_bits =
121-
BitVector.new(aggregate.sync_committee_bits, ChainSpec.get("SYNC_COMMITTEE_SIZE"))
122-
123120
participant_pubkeys =
124121
committee_pubkeys
125122
|> Enum.with_index()
126-
|> Enum.filter(fn {_, index} -> BitVector.set?(sync_committee_bits, index) end)
123+
|> Enum.filter(fn {_, index} -> BitVector.set?(aggregate.sync_committee_bits, index) end)
127124
|> Enum.map(fn {public_key, _} -> public_key end)
128125

129126
previous_slot = max(state.slot, 1) - 1
@@ -138,15 +135,15 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
138135
# Compute participant and proposer rewards
139136
{participant_reward, proposer_reward} = compute_sync_aggregate_rewards(state)
140137

141-
total_proposer_reward = BitVector.count(sync_committee_bits) * proposer_reward
138+
total_proposer_reward = BitVector.count(aggregate.sync_committee_bits) * proposer_reward
142139

143140
# PERF: make Map with committee_index by pubkey, then
144141
# Enum.map validators -> new balance all in place, without map_reduce
145142
state.validators
146143
|> get_sync_committee_indices(committee_pubkeys)
147144
|> Stream.with_index()
148145
|> Stream.map(fn {validator_index, committee_index} ->
149-
if BitVector.set?(sync_committee_bits, committee_index),
146+
if BitVector.set?(aggregate.sync_committee_bits, committee_index),
150147
do: {validator_index, participant_reward},
151148
else: {validator_index, -participant_reward}
152149
end)
@@ -845,7 +842,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
845842
end
846843

847844
defp check_matching_aggregation_bits_length(attestation, beacon_committee) do
848-
if BitList.length_of_bitlist(attestation.aggregation_bits) == length(beacon_committee) do
845+
if BitList.length(attestation.aggregation_bits) == length(beacon_committee) do
849846
:ok
850847
else
851848
{:error, "Mismatched aggregation bits length"}

lib/ssz_ex.ex

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,7 @@ defmodule LambdaEthereumConsensus.SszEx do
300300
end
301301

302302
def pack_bits(value, :bitlist) do
303-
len = value |> bit_size()
304-
{value, len} |> BitList.to_packed_bytes() |> pack_bytes()
303+
value |> BitList.to_packed_bytes() |> pack_bytes()
305304
end
306305

307306
def chunk_count({:list, type, max_size}) do
@@ -354,7 +353,7 @@ defmodule LambdaEthereumConsensus.SszEx do
354353
if len > max_size do
355354
{:error, "excess bits"}
356355
else
357-
{:ok, BitList.to_bytes({bit_list, len})}
356+
{:ok, BitList.to_bytes(bit_list)}
358357
end
359358
end
360359

@@ -407,11 +406,12 @@ defmodule LambdaEthereumConsensus.SszEx do
407406

408407
defp decode_bitlist(bit_list, max_size) when bit_size(bit_list) > 0 do
409408
num_bytes = byte_size(bit_list)
410-
{decoded, len} = BitList.new(bit_list)
409+
decoded = BitList.new(bit_list)
410+
len = BitList.length(decoded)
411411

412412
cond do
413-
len < 0 ->
414-
{:error, "missing length information"}
413+
match?(<<_::binary-size(num_bytes - 1), 0>>, bit_list) ->
414+
{:error, "BitList has no length information."}
415415

416416
div(len, @bits_per_byte) + 1 != num_bytes ->
417417
{:error, "invalid byte count"}
@@ -652,7 +652,7 @@ defmodule LambdaEthereumConsensus.SszEx do
652652

653653
defp check_first_offset([{offset, _} | _rest], items_index, _binary_size) do
654654
cond do
655-
offset < items_index -> {:error, "OffsetIntoFixedPortion"}
655+
offset < items_index -> {:error, "OffsetIntoFixedPortion (#{offset})"}
656656
offset > items_index -> {:error, "OffsetSkipsVariableBytes"}
657657
true -> :ok
658658
end
@@ -738,7 +738,7 @@ defmodule LambdaEthereumConsensus.SszEx do
738738
defp sanitize_offset(offset, previous_offset, _num_bytes, num_fixed_bytes) do
739739
cond do
740740
offset < num_fixed_bytes ->
741-
{:error, "OffsetIntoFixedPortion"}
741+
{:error, "OffsetIntoFixedPortion #{offset}"}
742742

743743
previous_offset == nil && offset != num_fixed_bytes ->
744744
{:error, "OffsetSkipsVariableBytes"}

lib/types/beacon_chain/attestation.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ defmodule Types.Attestation do
33
Struct definition for `AttestationMainnet`.
44
Related definitions in `native/ssz_nif/src/types/`.
55
"""
6+
alias LambdaEthereumConsensus.Utils.BitList
7+
68
@behaviour LambdaEthereumConsensus.Container
79

810
fields = [
@@ -29,4 +31,12 @@ defmodule Types.Attestation do
2931
{:signature, TypeAliases.bls_signature()}
3032
]
3133
end
34+
35+
def encode(%__MODULE__{} = map) do
36+
Map.update!(map, :aggregation_bits, &BitList.to_bytes/1)
37+
end
38+
39+
def decode(%__MODULE__{} = map) do
40+
Map.update!(map, :aggregation_bits, &BitList.new/1)
41+
end
3242
end

lib/types/beacon_chain/beacon_state.ex

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ defmodule Types.BeaconState do
33
Struct definition for `BeaconState`.
44
Related definitions in `native/ssz_nif/src/types/`.
55
"""
6-
@behaviour LambdaEthereumConsensus.Container
76
alias LambdaEthereumConsensus.Utils.BitVector
87

8+
@behaviour LambdaEthereumConsensus.Container
9+
910
fields = [
1011
:genesis_time,
1112
:genesis_validators_root,
@@ -114,6 +115,7 @@ defmodule Types.BeaconState do
114115
|> Map.update!(:previous_epoch_participation, &Aja.Vector.to_list/1)
115116
|> Map.update!(:current_epoch_participation, &Aja.Vector.to_list/1)
116117
|> Map.update!(:latest_execution_payload_header, &Types.ExecutionPayloadHeader.encode/1)
118+
|> Map.update!(:justification_bits, &BitVector.to_bytes/1)
117119
end
118120

119121
def decode(%__MODULE__{} = map) do
@@ -124,6 +126,9 @@ defmodule Types.BeaconState do
124126
|> Map.update!(:previous_epoch_participation, &Aja.Vector.new/1)
125127
|> Map.update!(:current_epoch_participation, &Aja.Vector.new/1)
126128
|> Map.update!(:latest_execution_payload_header, &Types.ExecutionPayloadHeader.decode/1)
129+
|> Map.update!(:justification_bits, fn bits ->
130+
BitVector.new(bits, Constants.justification_bits_length())
131+
end)
127132
end
128133

129134
@doc """
@@ -261,7 +266,7 @@ defmodule Types.BeaconState do
261266
{:list, TypeAliases.participation_flags(), ChainSpec.get("VALIDATOR_REGISTRY_LIMIT")}},
262267
{:current_epoch_participation,
263268
{:list, TypeAliases.participation_flags(), ChainSpec.get("VALIDATOR_REGISTRY_LIMIT")}},
264-
{:justification_bits, {:bitvector, ChainSpec.get("JUSTIFICATION_BITS_LENGTH")}},
269+
{:justification_bits, {:bitvector, Constants.justification_bits_length()}},
265270
{:previous_justified_checkpoint, Types.Checkpoint},
266271
{:current_justified_checkpoint, Types.Checkpoint},
267272
{:finalized_checkpoint, Types.Checkpoint},

lib/types/beacon_chain/pending_attestation.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ defmodule Types.PendingAttestation do
33
Struct definition for `PendingAttestation`.
44
Related definitions in `native/ssz_nif/src/types/`.
55
"""
6+
alias LambdaEthereumConsensus.Utils.BitList
7+
68
@behaviour LambdaEthereumConsensus.Container
79

810
fields = [
@@ -32,4 +34,12 @@ defmodule Types.PendingAttestation do
3234
{:proposer_index, TypeAliases.validator_index()}
3335
]
3436
end
37+
38+
def encode(%__MODULE__{} = map) do
39+
Map.update!(map, :aggregation_bits, &BitList.to_bytes/1)
40+
end
41+
42+
def decode(%__MODULE__{} = map) do
43+
Map.update!(map, :aggregation_bits, &BitList.new/1)
44+
end
3545
end

lib/types/beacon_chain/sync_aggregate.ex

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ defmodule Types.SyncAggregate do
33
Struct definition for `SyncAggregate`.
44
Related definitions in `native/ssz_nif/src/types/`.
55
"""
6+
alias LambdaEthereumConsensus.Utils.BitVector
7+
68
@behaviour LambdaEthereumConsensus.Container
79

810
fields = [
@@ -15,7 +17,7 @@ defmodule Types.SyncAggregate do
1517

1618
@type t :: %__MODULE__{
1719
# max size SYNC_COMMITTEE_SIZE
18-
sync_committee_bits: Types.bitvector(),
20+
sync_committee_bits: BitVector.t(),
1921
sync_committee_signature: Types.bls_signature()
2022
}
2123

@@ -26,4 +28,14 @@ defmodule Types.SyncAggregate do
2628
{:sync_committee_signature, TypeAliases.bls_signature()}
2729
]
2830
end
31+
32+
def encode(%__MODULE__{} = map) do
33+
Map.update!(map, :sync_committee_bits, &BitVector.to_bytes/1)
34+
end
35+
36+
def decode(%__MODULE__{} = map) do
37+
Map.update!(map, :sync_committee_bits, fn bits ->
38+
BitVector.new(bits, ChainSpec.get("SYNC_COMMITTEE_SIZE"))
39+
end)
40+
end
2941
end

0 commit comments

Comments
 (0)