-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
EIP-7549: Attestation packing (#14238)
* EIP-7549: Attestation packing * new files * change var name * test fixes * enhance comment * unit test for Deneb state
- Loading branch information
Showing
6 changed files
with
313 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_electra.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package validator | ||
|
||
import ( | ||
"slices" | ||
|
||
"github.com/prysmaticlabs/go-bitfield" | ||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" | ||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" | ||
"github.com/prysmaticlabs/prysm/v5/crypto/bls" | ||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" | ||
) | ||
|
||
// computeOnChainAggregate constructs a final aggregate form a list of network aggregates with equal attestation data. | ||
// It assumes that each network aggregate has exactly one committee bit set. | ||
// | ||
// Spec definition: | ||
// | ||
// def compute_on_chain_aggregate(network_aggregates: Sequence[Attestation]) -> Attestation: | ||
// aggregates = sorted(network_aggregates, key=lambda a: get_committee_indices(a.committee_bits)[0]) | ||
// | ||
// data = aggregates[0].data | ||
// aggregation_bits = Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]() | ||
// for a in aggregates: | ||
// for b in a.aggregation_bits: | ||
// aggregation_bits.append(b) | ||
// | ||
// signature = bls.Aggregate([a.signature for a in aggregates]) | ||
// | ||
// committee_indices = [get_committee_indices(a.committee_bits)[0] for a in aggregates] | ||
// committee_flags = [(index in committee_indices) for index in range(0, MAX_COMMITTEES_PER_SLOT)] | ||
// committee_bits = Bitvector[MAX_COMMITTEES_PER_SLOT](committee_flags) | ||
// | ||
// return Attestation( | ||
// aggregation_bits=aggregation_bits, | ||
// data=data, | ||
// committee_bits=committee_bits, | ||
// signature=signature, | ||
// ) | ||
func computeOnChainAggregate(aggregates []ethpb.Att) ([]ethpb.Att, error) { | ||
aggsByDataRoot := make(map[[32]byte][]ethpb.Att) | ||
for _, agg := range aggregates { | ||
key, err := agg.GetData().HashTreeRoot() | ||
if err != nil { | ||
return nil, err | ||
} | ||
existing, ok := aggsByDataRoot[key] | ||
if ok { | ||
aggsByDataRoot[key] = append(existing, agg) | ||
} else { | ||
aggsByDataRoot[key] = []ethpb.Att{agg} | ||
} | ||
} | ||
|
||
result := make([]ethpb.Att, 0) | ||
|
||
for _, aggs := range aggsByDataRoot { | ||
slices.SortFunc(aggs, func(a, b ethpb.Att) int { | ||
return a.CommitteeBitsVal().BitIndices()[0] - b.CommitteeBitsVal().BitIndices()[0] | ||
}) | ||
|
||
sigs := make([]bls.Signature, len(aggs)) | ||
committeeIndices := make([]primitives.CommitteeIndex, len(aggs)) | ||
aggBitsIndices := make([]uint64, 0) | ||
aggBitsOffset := uint64(0) | ||
var err error | ||
for i, a := range aggs { | ||
for _, bi := range a.GetAggregationBits().BitIndices() { | ||
aggBitsIndices = append(aggBitsIndices, uint64(bi)+aggBitsOffset) | ||
} | ||
sigs[i], err = bls.SignatureFromBytes(a.GetSignature()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
committeeIndices[i] = helpers.CommitteeIndices(a.CommitteeBitsVal())[0] | ||
|
||
aggBitsOffset += a.GetAggregationBits().Len() | ||
} | ||
|
||
aggregationBits := bitfield.NewBitlist(aggBitsOffset) | ||
for _, bi := range aggBitsIndices { | ||
aggregationBits.SetBitAt(bi, true) | ||
} | ||
|
||
cb := primitives.NewAttestationCommitteeBits() | ||
att := ðpb.AttestationElectra{ | ||
AggregationBits: aggregationBits, | ||
Data: aggs[0].GetData(), | ||
CommitteeBits: cb, | ||
Signature: bls.AggregateSignatures(sigs).Marshal(), | ||
} | ||
for _, ci := range committeeIndices { | ||
att.CommitteeBits.SetBitAt(uint64(ci), true) | ||
} | ||
result = append(result, att) | ||
} | ||
|
||
return result, nil | ||
} |
163 changes: 163 additions & 0 deletions
163
beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_electra_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package validator | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/prysmaticlabs/go-bitfield" | ||
"github.com/prysmaticlabs/prysm/v5/config/params" | ||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" | ||
"github.com/prysmaticlabs/prysm/v5/crypto/bls/blst" | ||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" | ||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" | ||
"github.com/prysmaticlabs/prysm/v5/testing/assert" | ||
"github.com/prysmaticlabs/prysm/v5/testing/require" | ||
) | ||
|
||
func Test_computeOnChainAggregate(t *testing.T) { | ||
params.SetupTestConfigCleanup(t) | ||
cfg := params.MainnetConfig().Copy() | ||
cfg.MaxCommitteesPerSlot = 64 | ||
params.OverrideBeaconConfig(cfg) | ||
|
||
key, err := blst.RandKey() | ||
require.NoError(t, err) | ||
sig := key.Sign([]byte{'X'}) | ||
|
||
data1 := ðpb.AttestationData{ | ||
Slot: 123, | ||
CommitteeIndex: 123, | ||
BeaconBlockRoot: bytesutil.PadTo([]byte("root"), 32), | ||
Source: ðpb.Checkpoint{ | ||
Epoch: 123, | ||
Root: bytesutil.PadTo([]byte("root"), 32), | ||
}, | ||
Target: ðpb.Checkpoint{ | ||
Epoch: 123, | ||
Root: bytesutil.PadTo([]byte("root"), 32), | ||
}, | ||
} | ||
data2 := ðpb.AttestationData{ | ||
Slot: 456, | ||
CommitteeIndex: 456, | ||
BeaconBlockRoot: bytesutil.PadTo([]byte("root"), 32), | ||
Source: ðpb.Checkpoint{ | ||
Epoch: 456, | ||
Root: bytesutil.PadTo([]byte("root"), 32), | ||
}, | ||
Target: ðpb.Checkpoint{ | ||
Epoch: 456, | ||
Root: bytesutil.PadTo([]byte("root"), 32), | ||
}, | ||
} | ||
|
||
t.Run("single aggregate", func(t *testing.T) { | ||
cb := primitives.NewAttestationCommitteeBits() | ||
cb.SetBitAt(0, true) | ||
att := ðpb.AttestationElectra{ | ||
AggregationBits: bitfield.Bitlist{0b00011111}, | ||
Data: data1, | ||
CommitteeBits: cb, | ||
Signature: sig.Marshal(), | ||
} | ||
result, err := computeOnChainAggregate([]ethpb.Att{att}) | ||
require.NoError(t, err) | ||
require.Equal(t, 1, len(result)) | ||
assert.DeepEqual(t, att.AggregationBits, result[0].GetAggregationBits()) | ||
assert.DeepEqual(t, att.Data, result[0].GetData()) | ||
assert.DeepEqual(t, att.CommitteeBits, result[0].CommitteeBitsVal()) | ||
}) | ||
t.Run("all aggregates for one root", func(t *testing.T) { | ||
cb := primitives.NewAttestationCommitteeBits() | ||
cb.SetBitAt(0, true) | ||
att1 := ðpb.AttestationElectra{ | ||
AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1 | ||
Data: data1, | ||
CommitteeBits: cb, | ||
Signature: sig.Marshal(), | ||
} | ||
cb = primitives.NewAttestationCommitteeBits() | ||
cb.SetBitAt(1, true) | ||
att2 := ðpb.AttestationElectra{ | ||
AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1 | ||
Data: data1, | ||
CommitteeBits: cb, | ||
Signature: sig.Marshal(), | ||
} | ||
result, err := computeOnChainAggregate([]ethpb.Att{att1, att2}) | ||
require.NoError(t, err) | ||
require.Equal(t, 1, len(result)) | ||
assert.DeepEqual(t, bitfield.Bitlist{0b00110011, 0b00000001}, result[0].GetAggregationBits()) | ||
assert.DeepEqual(t, data1, result[0].GetData()) | ||
cb = primitives.NewAttestationCommitteeBits() | ||
cb.SetBitAt(0, true) | ||
cb.SetBitAt(1, true) | ||
assert.DeepEqual(t, cb, result[0].CommitteeBitsVal()) | ||
}) | ||
t.Run("aggregates for multiple roots", func(t *testing.T) { | ||
cb := primitives.NewAttestationCommitteeBits() | ||
cb.SetBitAt(0, true) | ||
att1 := ðpb.AttestationElectra{ | ||
AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1 | ||
Data: data1, | ||
CommitteeBits: cb, | ||
Signature: sig.Marshal(), | ||
} | ||
cb = primitives.NewAttestationCommitteeBits() | ||
cb.SetBitAt(1, true) | ||
att2 := ðpb.AttestationElectra{ | ||
AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1 | ||
Data: data1, | ||
CommitteeBits: cb, | ||
Signature: sig.Marshal(), | ||
} | ||
cb = primitives.NewAttestationCommitteeBits() | ||
cb.SetBitAt(0, true) | ||
att3 := ðpb.AttestationElectra{ | ||
AggregationBits: bitfield.Bitlist{0b00011001}, // aggregation bits 0,3 | ||
Data: data2, | ||
CommitteeBits: cb, | ||
Signature: sig.Marshal(), | ||
} | ||
cb = primitives.NewAttestationCommitteeBits() | ||
cb.SetBitAt(1, true) | ||
att4 := ðpb.AttestationElectra{ | ||
AggregationBits: bitfield.Bitlist{0b00010010}, // aggregation bits 1 | ||
Data: data2, | ||
CommitteeBits: cb, | ||
Signature: sig.Marshal(), | ||
} | ||
result, err := computeOnChainAggregate([]ethpb.Att{att1, att2, att3, att4}) | ||
require.NoError(t, err) | ||
require.Equal(t, 2, len(result)) | ||
cb = primitives.NewAttestationCommitteeBits() | ||
cb.SetBitAt(0, true) | ||
cb.SetBitAt(1, true) | ||
|
||
expectedAggBits := bitfield.Bitlist{0b00110011, 0b00000001} | ||
expectedData := data1 | ||
found := false | ||
for _, a := range result { | ||
if reflect.DeepEqual(expectedAggBits, a.GetAggregationBits()) && reflect.DeepEqual(expectedData, a.GetData()) && reflect.DeepEqual(cb, a.CommitteeBitsVal()) { | ||
found = true | ||
break | ||
} | ||
} | ||
if !found { | ||
t.Error("Expected aggregate not found") | ||
} | ||
|
||
expectedAggBits = bitfield.Bitlist{0b00101001, 0b00000001} | ||
expectedData = data2 | ||
found = false | ||
for _, a := range result { | ||
if reflect.DeepEqual(expectedAggBits, a.GetAggregationBits()) && reflect.DeepEqual(expectedData, a.GetData()) && reflect.DeepEqual(cb, a.CommitteeBitsVal()) { | ||
found = true | ||
break | ||
} | ||
} | ||
if !found { | ||
t.Error("Expected aggregate not found") | ||
} | ||
}) | ||
} |
Oops, something went wrong.