mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-21 03:08:14 -05:00
Compare commits
15 Commits
process-at
...
longer-e2e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d9e4aaaab | ||
|
|
361cf6f617 | ||
|
|
055c6eb784 | ||
|
|
d33389fb54 | ||
|
|
ce72deb3c0 | ||
|
|
ec48e6340c | ||
|
|
a135a336c3 | ||
|
|
5f189f002e | ||
|
|
bca6166e82 | ||
|
|
b6818853b4 | ||
|
|
5a56bfcf98 | ||
|
|
a08f185170 | ||
|
|
15b1d68249 | ||
|
|
885d9cc478 | ||
|
|
511248213c |
10
BUILD.bazel
10
BUILD.bazel
@@ -1,5 +1,4 @@
|
||||
load("@bazel_gazelle//:def.bzl", "gazelle")
|
||||
load("@com_github_atlassian_bazel_tools//gometalinter:def.bzl", "gometalinter")
|
||||
load("@com_github_atlassian_bazel_tools//goimports:def.bzl", "goimports")
|
||||
load("@io_kubernetes_build//defs:run_in_workspace.bzl", "workspace_binary")
|
||||
load("@io_bazel_rules_go//go:def.bzl", "nogo")
|
||||
@@ -55,15 +54,6 @@ alias(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
gometalinter(
|
||||
name = "gometalinter",
|
||||
config = "//:.gometalinter.json",
|
||||
paths = [
|
||||
"./...",
|
||||
],
|
||||
prefix = prefix,
|
||||
)
|
||||
|
||||
goimports(
|
||||
name = "goimports",
|
||||
display_diffs = True,
|
||||
|
||||
@@ -423,10 +423,6 @@ load("@prysm//testing/endtoend:deps.bzl", "e2e_deps")
|
||||
|
||||
e2e_deps()
|
||||
|
||||
load("@com_github_atlassian_bazel_tools//gometalinter:deps.bzl", "gometalinter_dependencies")
|
||||
|
||||
gometalinter_dependencies()
|
||||
|
||||
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
|
||||
|
||||
gazelle_dependencies(go_sdk = "go_sdk")
|
||||
|
||||
@@ -20,7 +20,6 @@ go_library(
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/epoch:go_default_library",
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
"//beacon-chain/core/gloas:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
@@ -17,7 +16,6 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1/attestation"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -77,12 +75,7 @@ func ProcessAttestationNoVerifySignature(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
beaconState, err = gloas.UpdatePendingPaymentWeight(beaconState, att, indices, participatedFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return SetParticipationAndRewardProposer(ctx, beaconState, att.GetData().Target.Epoch, indices, participatedFlags, totalBalance, att)
|
||||
return SetParticipationAndRewardProposer(ctx, beaconState, att.GetData().Target.Epoch, indices, participatedFlags, totalBalance)
|
||||
}
|
||||
|
||||
// SetParticipationAndRewardProposer retrieves and sets the epoch participation bits in state. Based on the epoch participation, it rewards
|
||||
@@ -112,9 +105,7 @@ func SetParticipationAndRewardProposer(
|
||||
beaconState state.BeaconState,
|
||||
targetEpoch primitives.Epoch,
|
||||
indices []uint64,
|
||||
participatedFlags map[uint8]bool,
|
||||
totalBalance uint64,
|
||||
att ethpb.Att) (state.BeaconState, error) {
|
||||
participatedFlags map[uint8]bool, totalBalance uint64) (state.BeaconState, error) {
|
||||
var proposerRewardNumerator uint64
|
||||
currentEpoch := time.CurrentEpoch(beaconState)
|
||||
var stateErr error
|
||||
@@ -308,40 +299,6 @@ func AttestationParticipationFlagIndices(beaconState state.ReadOnlyBeaconState,
|
||||
participatedFlags[targetFlagIndex] = true
|
||||
}
|
||||
matchedSrcTgtHead := matchedHead && matchedSrcTgt
|
||||
|
||||
// Spec v1.6.1 (pseudocode excerpt):
|
||||
//
|
||||
// # [New in Gloas:EIP7732]
|
||||
// if is_attestation_same_slot(state, data):
|
||||
// assert data.index == 0
|
||||
// payload_matches = True
|
||||
// else:
|
||||
// slot_index = data.slot % SLOTS_PER_HISTORICAL_ROOT
|
||||
// payload_index = state.execution_payload_availability[slot_index]
|
||||
// payload_matches = data.index == payload_index
|
||||
//
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// is_matching_head = is_matching_target and head_root_matches and payload_matches
|
||||
var matchingPayload bool
|
||||
if beaconState.Version() >= version.Gloas {
|
||||
sameSlot, err := gloas.SameSlotAttestation(beaconState, [32]byte(data.BeaconBlockRoot), data.Slot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sameSlot {
|
||||
if data.CommitteeIndex != 0 {
|
||||
return nil, fmt.Errorf("committee index %d for same slot attestation must be 0", data.CommitteeIndex)
|
||||
}
|
||||
matchingPayload = true
|
||||
} else {
|
||||
executionPayloadAvail, err := beaconState.ExecutionPayloadAvailability(data.Slot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchingPayload = executionPayloadAvail == uint64(data.CommitteeIndex)
|
||||
}
|
||||
matchedSrcTgtHead = matchedSrcTgtHead && matchingPayload
|
||||
}
|
||||
if matchedSrcTgtHead && delay == cfg.MinAttestationInclusionDelay {
|
||||
participatedFlags[headFlagIndex] = true
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package altair_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/go-bitfield"
|
||||
@@ -558,7 +556,7 @@ func TestSetParticipationAndRewardProposer(t *testing.T) {
|
||||
|
||||
b, err := helpers.TotalActiveBalance(beaconState)
|
||||
require.NoError(t, err)
|
||||
st, err := altair.SetParticipationAndRewardProposer(t.Context(), beaconState, test.epoch, test.indices, test.participatedFlags, b, ðpb.Attestation{})
|
||||
st, err := altair.SetParticipationAndRewardProposer(t.Context(), beaconState, test.epoch, test.indices, test.participatedFlags, b)
|
||||
require.NoError(t, err)
|
||||
|
||||
i, err := helpers.BeaconProposerIndex(t.Context(), st)
|
||||
@@ -777,67 +775,11 @@ func TestAttestationParticipationFlagIndices(t *testing.T) {
|
||||
headFlagIndex: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gloas same-slot committee index non-zero errors",
|
||||
inputState: func() state.BeaconState {
|
||||
stateSlot := primitives.Slot(5)
|
||||
slot := primitives.Slot(3)
|
||||
targetRoot := bytes.Repeat([]byte{0xAA}, 32)
|
||||
headRoot := bytes.Repeat([]byte{0xBB}, 32)
|
||||
prevRoot := bytes.Repeat([]byte{0xCC}, 32)
|
||||
return buildGloasStateForFlags(t, stateSlot, slot, targetRoot, headRoot, prevRoot, 0, 0)
|
||||
}(),
|
||||
inputData: ðpb.AttestationData{
|
||||
Slot: 3,
|
||||
CommitteeIndex: 1, // invalid for same-slot
|
||||
BeaconBlockRoot: bytes.Repeat([]byte{0xBB}, 32),
|
||||
Source: ðpb.Checkpoint{Root: bytes.Repeat([]byte{0xDD}, 32)},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 0,
|
||||
Root: bytes.Repeat([]byte{0xAA}, 32),
|
||||
},
|
||||
},
|
||||
inputDelay: 1,
|
||||
participationIndices: nil,
|
||||
},
|
||||
{
|
||||
name: "gloas payload availability matches committee index",
|
||||
inputState: func() state.BeaconState {
|
||||
stateSlot := primitives.Slot(5)
|
||||
slot := primitives.Slot(3)
|
||||
targetRoot := bytes.Repeat([]byte{0xAA}, 32)
|
||||
headRoot := bytes.Repeat([]byte{0xBB}, 32)
|
||||
// Same prev root to make SameSlotAttestation false and use payload availability.
|
||||
return buildGloasStateForFlags(t, stateSlot, slot, targetRoot, headRoot, headRoot, 1, slot)
|
||||
}(),
|
||||
inputData: ðpb.AttestationData{
|
||||
Slot: 3,
|
||||
CommitteeIndex: 1,
|
||||
BeaconBlockRoot: bytes.Repeat([]byte{0xBB}, 32),
|
||||
Source: ðpb.Checkpoint{Root: bytes.Repeat([]byte{0xDD}, 32)},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 0,
|
||||
Root: bytes.Repeat([]byte{0xAA}, 32),
|
||||
},
|
||||
},
|
||||
inputDelay: 1,
|
||||
participationIndices: map[uint8]bool{
|
||||
sourceFlagIndex: true,
|
||||
targetFlagIndex: true,
|
||||
headFlagIndex: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
flagIndices, err := altair.AttestationParticipationFlagIndices(test.inputState, test.inputData, test.inputDelay)
|
||||
if test.participationIndices == nil {
|
||||
require.ErrorContains(t, "committee index", err)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if !reflect.DeepEqual(test.participationIndices, flagIndices) {
|
||||
t.Fatalf("unexpected participation indices: got %v want %v", flagIndices, test.participationIndices)
|
||||
}
|
||||
require.DeepEqual(t, test.participationIndices, flagIndices)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -916,61 +858,3 @@ func TestMatchingStatus(t *testing.T) {
|
||||
require.Equal(t, test.matchedHead, head)
|
||||
}
|
||||
}
|
||||
|
||||
func buildGloasStateForFlags(t *testing.T, stateSlot, slot primitives.Slot, targetRoot, headRoot, prevRoot []byte, availabilityBit uint8, availabilitySlot primitives.Slot) state.BeaconState {
|
||||
t.Helper()
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
blockRoots[0] = targetRoot
|
||||
blockRoots[slot%cfg.SlotsPerHistoricalRoot] = headRoot
|
||||
blockRoots[(slot-1)%cfg.SlotsPerHistoricalRoot] = prevRoot
|
||||
|
||||
stateRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range stateRoots {
|
||||
stateRoots[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
randaoMixes := make([][]byte, cfg.EpochsPerHistoricalVector)
|
||||
for i := range randaoMixes {
|
||||
randaoMixes[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
|
||||
execPayloadAvailability := make([]byte, cfg.SlotsPerHistoricalRoot/8)
|
||||
idx := availabilitySlot % cfg.SlotsPerHistoricalRoot
|
||||
byteIndex := idx / 8
|
||||
bitIndex := idx % 8
|
||||
if availabilityBit == 1 {
|
||||
execPayloadAvailability[byteIndex] |= 1 << bitIndex
|
||||
}
|
||||
|
||||
checkpointRoot := bytes.Repeat([]byte{0xDD}, fieldparams.RootLength)
|
||||
justified := ðpb.Checkpoint{Root: checkpointRoot}
|
||||
|
||||
stProto := ðpb.BeaconStateGloas{
|
||||
Slot: stateSlot,
|
||||
GenesisValidatorsRoot: bytes.Repeat([]byte{0x11}, fieldparams.RootLength),
|
||||
BlockRoots: blockRoots,
|
||||
StateRoots: stateRoots,
|
||||
RandaoMixes: randaoMixes,
|
||||
ExecutionPayloadAvailability: execPayloadAvailability,
|
||||
CurrentJustifiedCheckpoint: justified,
|
||||
PreviousJustifiedCheckpoint: justified,
|
||||
Validators: []*ethpb.Validator{
|
||||
{
|
||||
EffectiveBalance: cfg.MinActivationBalance,
|
||||
WithdrawalCredentials: append([]byte{cfg.ETH1AddressWithdrawalPrefixByte}, bytes.Repeat([]byte{0x01}, 31)...),
|
||||
},
|
||||
},
|
||||
Balances: []uint64{cfg.MinActivationBalance},
|
||||
BuilderPendingPayments: make([]*ethpb.BuilderPendingPayment, cfg.SlotsPerEpoch*2),
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
}
|
||||
|
||||
beaconState, err := state_native.InitializeFromProtoGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
return beaconState
|
||||
}
|
||||
|
||||
@@ -111,16 +111,7 @@ func VerifyAttestationNoVerifySignature(
|
||||
var indexedAtt ethpb.IndexedAtt
|
||||
|
||||
if att.Version() >= version.Electra {
|
||||
// Spec v1.6.1 (pseudocode excerpt):
|
||||
//
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// assert data.index < 2
|
||||
//
|
||||
if beaconState.Version() >= version.Gloas {
|
||||
if att.GetData().CommitteeIndex >= 2 {
|
||||
return fmt.Errorf("incorrect committee index %d", att.GetData().CommitteeIndex)
|
||||
}
|
||||
} else if att.GetData().CommitteeIndex != 0 {
|
||||
if att.GetData().CommitteeIndex != 0 {
|
||||
return errors.New("committee index must be 0 post-Electra")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package blocks_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
@@ -315,75 +314,6 @@ func TestVerifyAttestationNoVerifySignature_Electra(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerifyAttestationNoVerifySignature_GloasCommitteeIndexLimit(t *testing.T) {
|
||||
cfg := params.BeaconConfig()
|
||||
stateSlot := cfg.MinAttestationInclusionDelay + 1
|
||||
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range blockRoots {
|
||||
blockRoots[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
stateRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range stateRoots {
|
||||
stateRoots[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
randaoMixes := make([][]byte, cfg.EpochsPerHistoricalVector)
|
||||
for i := range randaoMixes {
|
||||
randaoMixes[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
|
||||
checkpointRoot := bytes.Repeat([]byte{0xAA}, fieldparams.RootLength)
|
||||
justified := ðpb.Checkpoint{Epoch: 0, Root: checkpointRoot}
|
||||
|
||||
gloasStateProto := ðpb.BeaconStateGloas{
|
||||
Slot: stateSlot,
|
||||
GenesisValidatorsRoot: bytes.Repeat([]byte{0x11}, fieldparams.RootLength),
|
||||
BlockRoots: blockRoots,
|
||||
StateRoots: stateRoots,
|
||||
RandaoMixes: randaoMixes,
|
||||
ExecutionPayloadAvailability: make([]byte, cfg.SlotsPerHistoricalRoot/8),
|
||||
CurrentJustifiedCheckpoint: justified,
|
||||
PreviousJustifiedCheckpoint: justified,
|
||||
Validators: []*ethpb.Validator{
|
||||
{
|
||||
EffectiveBalance: cfg.MinActivationBalance,
|
||||
WithdrawalCredentials: append([]byte{cfg.ETH1AddressWithdrawalPrefixByte}, bytes.Repeat([]byte{0x01}, 31)...),
|
||||
},
|
||||
},
|
||||
Balances: []uint64{cfg.MinActivationBalance},
|
||||
BuilderPendingPayments: make([]*ethpb.BuilderPendingPayment, cfg.SlotsPerEpoch*2),
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
}
|
||||
|
||||
beaconState, err := state_native.InitializeFromProtoGloas(gloasStateProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
committeeBits := bitfield.NewBitvector64()
|
||||
committeeBits.SetBitAt(0, true)
|
||||
aggBits := bitfield.NewBitlist(1)
|
||||
aggBits.SetBitAt(0, true)
|
||||
|
||||
att := ðpb.AttestationElectra{
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 0,
|
||||
CommitteeIndex: 2, // invalid for Gloas (must be <2)
|
||||
BeaconBlockRoot: blockRoots[0],
|
||||
Source: justified,
|
||||
Target: justified,
|
||||
},
|
||||
AggregationBits: aggBits,
|
||||
CommitteeBits: committeeBits,
|
||||
Signature: bytes.Repeat([]byte{0x00}, fieldparams.BLSSignatureLength),
|
||||
}
|
||||
|
||||
err = blocks.VerifyAttestationNoVerifySignature(context.TODO(), beaconState, att)
|
||||
assert.ErrorContains(t, "incorrect committee index 2", err)
|
||||
}
|
||||
|
||||
func TestConvertToIndexed_OK(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
validators := make([]*ethpb.Validator, 2*params.BeaconConfig().SlotsPerEpoch)
|
||||
@@ -653,7 +583,6 @@ func TestVerifyAttestations_HandlesPlannedFork(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetrieveAttestationSignatureSet_VerifiesMultipleAttestations(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
ctx := t.Context()
|
||||
numOfValidators := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(4))
|
||||
validators := make([]*ethpb.Validator, numOfValidators)
|
||||
@@ -661,25 +590,17 @@ func TestRetrieveAttestationSignatureSet_VerifiesMultipleAttestations(t *testing
|
||||
require.NoError(t, err)
|
||||
for i := range validators {
|
||||
validators[i] = ðpb.Validator{
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
PublicKey: keys[i].PublicKey().Marshal(),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
||||
ActivationEligibilityEpoch: 0,
|
||||
ActivationEpoch: 0,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
PublicKey: keys[i].PublicKey().Marshal(),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
}
|
||||
}
|
||||
balances := make([]uint64, numOfValidators)
|
||||
for i := range balances {
|
||||
balances[i] = uint64(params.BeaconConfig().MaxEffectiveBalance)
|
||||
}
|
||||
|
||||
t.Run("pre-Electra", func(t *testing.T) {
|
||||
st, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, st.SetSlot(5))
|
||||
require.NoError(t, st.SetValidators(validators))
|
||||
require.NoError(t, st.SetBalances(balances))
|
||||
|
||||
comm1, err := helpers.BeaconCommitteeFromState(t.Context(), st, 1 /*slot*/, 0 /*committeeIndex*/)
|
||||
require.NoError(t, err)
|
||||
@@ -729,7 +650,6 @@ func TestRetrieveAttestationSignatureSet_VerifiesMultipleAttestations(t *testing
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, st.SetSlot(5))
|
||||
require.NoError(t, st.SetValidators(validators))
|
||||
require.NoError(t, st.SetBalances(balances))
|
||||
|
||||
comm1, err := helpers.BeaconCommitteeFromState(t.Context(), st, 1 /*slot*/, 0 /*committeeIndex*/)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -2,30 +2,53 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["attestation.go"],
|
||||
srcs = [
|
||||
"bid.go",
|
||||
"pending_payment.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/bls/common:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["attestation_test.go"],
|
||||
srcs = [
|
||||
"bid_test.go",
|
||||
"pending_payment_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/bls/common:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_prysmaticlabs_fastssz//:go_default_library",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
)
|
||||
|
||||
// SameSlotAttestation checks if the attestation is for the same slot as the block root in the state.
|
||||
// Spec v1.6.1 (pseudocode excerpt):
|
||||
//
|
||||
// is_attestation_same_slot(state, data):
|
||||
// block_root_at_slot = get_block_root_at_slot(state, data.slot)
|
||||
// block_root_at_prev_slot = get_block_root_at_slot(state, data.slot - 1)
|
||||
// return block_root_at_slot == data.beacon_block_root and block_root_at_prev_slot != data.beacon_block_root
|
||||
func SameSlotAttestation(state state.ReadOnlyBeaconState, blockRoot [32]byte, slot primitives.Slot) (bool, error) {
|
||||
if slot == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
blockRootAtSlot, err := helpers.BlockRootAtSlot(state, slot)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
matchingBlockRoot := bytes.Equal(blockRoot[:], blockRootAtSlot)
|
||||
|
||||
blockRootAtPrevSlot, err := helpers.BlockRootAtSlot(state, slot-1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
matchingPrevBlockRoot := bytes.Equal(blockRoot[:], blockRootAtPrevSlot)
|
||||
|
||||
return matchingBlockRoot && !matchingPrevBlockRoot, nil
|
||||
}
|
||||
|
||||
// UpdatePendingPaymentWeight updates the builder pending payment weight based on attestation participation.
|
||||
// Spec v1.6.1 (pseudocode excerpt):
|
||||
//
|
||||
// if data.target.epoch == get_current_epoch(state):
|
||||
// epoch_participation = state.current_epoch_participation
|
||||
// payment = state.builder_pending_payments[SLOTS_PER_EPOCH + data.slot % SLOTS_PER_EPOCH]
|
||||
// else:
|
||||
// epoch_participation = state.previous_epoch_participation
|
||||
// payment = state.builder_pending_payments[data.slot % SLOTS_PER_EPOCH]
|
||||
//
|
||||
// for index in get_attesting_indices(state, attestation):
|
||||
// will_set_new_flag = False
|
||||
// for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
|
||||
// if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index):
|
||||
// epoch_participation[index] = add_flag(epoch_participation[index], flag_index)
|
||||
// proposer_reward_numerator += get_base_reward(state, index) * weight
|
||||
// will_set_new_flag = True
|
||||
// if will_set_new_flag and is_attestation_same_slot(state, data) and payment.withdrawal.amount > 0:
|
||||
// payment.weight += state.validators[index].effective_balance
|
||||
//
|
||||
// if current_epoch_target:
|
||||
// state.builder_pending_payments[SLOTS_PER_EPOCH + data.slot % SLOTS_PER_EPOCH] = payment
|
||||
// else:
|
||||
// state.builder_pending_payments[data.slot % SLOTS_PER_EPOCH] = payment
|
||||
func UpdatePendingPaymentWeight(beaconState state.BeaconState, att ethpb.Att, indices []uint64, participatedFlags map[uint8]bool) (state.BeaconState, error) {
|
||||
if beaconState.Version() < version.Gloas {
|
||||
return beaconState, nil
|
||||
}
|
||||
|
||||
data := att.GetData()
|
||||
epoch := slots.ToEpoch(beaconState.Slot())
|
||||
|
||||
isSameSlot, err := SameSlotAttestation(beaconState, [32]byte(data.BeaconBlockRoot), data.Slot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isSameSlot {
|
||||
return beaconState, nil
|
||||
}
|
||||
|
||||
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||
var (
|
||||
paymentSlot primitives.Slot
|
||||
payment *ethpb.BuilderPendingPayment
|
||||
epochParticipation []byte
|
||||
)
|
||||
if data.Target.Epoch == epoch {
|
||||
paymentSlot = slotsPerEpoch + (data.Slot % slotsPerEpoch)
|
||||
payment, err = beaconState.BuilderPendingPayment(uint64(paymentSlot))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
epochParticipation, err = beaconState.CurrentEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
paymentSlot = data.Slot % slotsPerEpoch
|
||||
payment, err = beaconState.BuilderPendingPayment(uint64(paymentSlot))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
epochParticipation, err = beaconState.PreviousEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if payment.Withdrawal.Amount == 0 {
|
||||
return beaconState, nil
|
||||
}
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
flagIndices := []uint8{cfg.TimelySourceFlagIndex, cfg.TimelyTargetFlagIndex, cfg.TimelyHeadFlagIndex}
|
||||
hasNewFlag := func(idx uint64) bool {
|
||||
participation := epochParticipation[idx]
|
||||
for _, f := range flagIndices {
|
||||
if !participatedFlags[f] {
|
||||
continue
|
||||
}
|
||||
if participation&(1<<f) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, idx := range indices {
|
||||
if !hasNewFlag(idx) {
|
||||
continue
|
||||
}
|
||||
validator, err := beaconState.ValidatorAtIndex(primitives.ValidatorIndex(idx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payment.Weight += primitives.Gwei(validator.EffectiveBalance)
|
||||
}
|
||||
|
||||
if err := beaconState.SetBuilderPendingPayment(uint64(paymentSlot), payment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return beaconState, nil
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
)
|
||||
|
||||
func buildStateWithBlockRoots(t *testing.T, stateSlot primitives.Slot, roots map[primitives.Slot][]byte) *state_native.BeaconState {
|
||||
t.Helper()
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for slot, root := range roots {
|
||||
blockRoots[slot%cfg.SlotsPerHistoricalRoot] = root
|
||||
}
|
||||
|
||||
stateRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range stateRoots {
|
||||
stateRoots[i] = bytes.Repeat([]byte{0x11}, 32)
|
||||
}
|
||||
|
||||
randaoMixes := make([][]byte, cfg.EpochsPerHistoricalVector)
|
||||
for i := range randaoMixes {
|
||||
randaoMixes[i] = bytes.Repeat([]byte{0x22}, 32)
|
||||
}
|
||||
|
||||
execPayloadAvailability := make([]byte, cfg.SlotsPerHistoricalRoot/8)
|
||||
|
||||
stProto := ðpb.BeaconStateGloas{
|
||||
Slot: stateSlot,
|
||||
GenesisValidatorsRoot: bytes.Repeat([]byte{0x33}, 32),
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x44}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x44}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
BlockRoots: blockRoots,
|
||||
StateRoots: stateRoots,
|
||||
RandaoMixes: randaoMixes,
|
||||
ExecutionPayloadAvailability: execPayloadAvailability,
|
||||
Validators: []*ethpb.Validator{},
|
||||
Balances: []uint64{},
|
||||
BuilderPendingPayments: make([]*ethpb.BuilderPendingPayment, cfg.SlotsPerEpoch*2),
|
||||
BuilderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{},
|
||||
}
|
||||
|
||||
state, err := state_native.InitializeFromProtoGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
return state.(*state_native.BeaconState)
|
||||
}
|
||||
|
||||
func TestSameSlotAttestation(t *testing.T) {
|
||||
rootA := bytes.Repeat([]byte{0xAA}, 32)
|
||||
rootB := bytes.Repeat([]byte{0xBB}, 32)
|
||||
rootC := bytes.Repeat([]byte{0xCC}, 32)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
stateSlot primitives.Slot
|
||||
slot primitives.Slot
|
||||
blockRoot []byte
|
||||
roots map[primitives.Slot][]byte
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "slot zero always true",
|
||||
stateSlot: 1,
|
||||
slot: 0,
|
||||
blockRoot: rootA,
|
||||
roots: map[primitives.Slot][]byte{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "matching current different previous",
|
||||
stateSlot: 6,
|
||||
slot: 4,
|
||||
blockRoot: rootA,
|
||||
roots: map[primitives.Slot][]byte{
|
||||
4: rootA,
|
||||
3: rootB,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "matching current same previous",
|
||||
stateSlot: 6,
|
||||
slot: 4,
|
||||
blockRoot: rootA,
|
||||
roots: map[primitives.Slot][]byte{
|
||||
4: rootA,
|
||||
3: rootA,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "non matching current",
|
||||
stateSlot: 6,
|
||||
slot: 4,
|
||||
blockRoot: rootC,
|
||||
roots: map[primitives.Slot][]byte{
|
||||
4: rootA,
|
||||
3: rootB,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := buildStateWithBlockRoots(t, tt.stateSlot, tt.roots)
|
||||
var rootArr [32]byte
|
||||
copy(rootArr[:], tt.blockRoot)
|
||||
|
||||
got, err := SameSlotAttestation(state, rootArr, tt.slot)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func buildStateForPaymentTest(t *testing.T, stateSlot primitives.Slot, paymentIdx int, amount primitives.Gwei, weight primitives.Gwei, roots map[primitives.Slot][]byte) *state_native.BeaconState {
|
||||
t.Helper()
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for slot, root := range roots {
|
||||
blockRoots[slot%cfg.SlotsPerHistoricalRoot] = root
|
||||
}
|
||||
|
||||
stateRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range stateRoots {
|
||||
stateRoots[i] = bytes.Repeat([]byte{0x44}, 32)
|
||||
}
|
||||
randaoMixes := make([][]byte, cfg.EpochsPerHistoricalVector)
|
||||
for i := range randaoMixes {
|
||||
randaoMixes[i] = bytes.Repeat([]byte{0x55}, 32)
|
||||
}
|
||||
|
||||
validator := ðpb.Validator{
|
||||
PublicKey: bytes.Repeat([]byte{0x01}, 48),
|
||||
WithdrawalCredentials: append([]byte{cfg.ETH1AddressWithdrawalPrefixByte}, bytes.Repeat([]byte{0x02}, 31)...),
|
||||
EffectiveBalance: cfg.MinActivationBalance,
|
||||
}
|
||||
|
||||
payments := make([]*ethpb.BuilderPendingPayment, cfg.SlotsPerEpoch*2)
|
||||
for i := range payments {
|
||||
payments[i] = ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
},
|
||||
}
|
||||
}
|
||||
payments[paymentIdx] = ðpb.BuilderPendingPayment{
|
||||
Weight: weight,
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
Amount: amount,
|
||||
},
|
||||
}
|
||||
|
||||
execPayloadAvailability := make([]byte, cfg.SlotsPerHistoricalRoot/8)
|
||||
|
||||
stProto := ðpb.BeaconStateGloas{
|
||||
Slot: stateSlot,
|
||||
GenesisValidatorsRoot: bytes.Repeat([]byte{0x33}, 32),
|
||||
BlockRoots: blockRoots,
|
||||
StateRoots: stateRoots,
|
||||
RandaoMixes: randaoMixes,
|
||||
ExecutionPayloadAvailability: execPayloadAvailability,
|
||||
Validators: []*ethpb.Validator{validator},
|
||||
Balances: []uint64{cfg.MinActivationBalance},
|
||||
CurrentEpochParticipation: []byte{0},
|
||||
PreviousEpochParticipation: []byte{0},
|
||||
BuilderPendingPayments: payments,
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x66}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x66}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
}
|
||||
|
||||
state, err := state_native.InitializeFromProtoGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
return state.(*state_native.BeaconState)
|
||||
}
|
||||
|
||||
func TestUpdatePendingPaymentWeight(t *testing.T) {
|
||||
cfg := params.BeaconConfig()
|
||||
slotsPerEpoch := cfg.SlotsPerEpoch
|
||||
slot := primitives.Slot(4)
|
||||
stateSlot := slot + 1
|
||||
currentEpoch := slots.ToEpoch(stateSlot)
|
||||
|
||||
rootA := bytes.Repeat([]byte{0xAA}, 32)
|
||||
rootB := bytes.Repeat([]byte{0xBB}, 32)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
targetEpoch primitives.Epoch
|
||||
blockRoot []byte
|
||||
initialAmount primitives.Gwei
|
||||
initialWeight primitives.Gwei
|
||||
wantWeight primitives.Gwei
|
||||
}{
|
||||
{
|
||||
name: "same slot current epoch adds weight",
|
||||
targetEpoch: currentEpoch,
|
||||
blockRoot: rootA,
|
||||
initialAmount: 10,
|
||||
initialWeight: 0,
|
||||
wantWeight: primitives.Gwei(cfg.MinActivationBalance),
|
||||
},
|
||||
{
|
||||
name: "same slot zero amount no weight change",
|
||||
targetEpoch: currentEpoch,
|
||||
blockRoot: rootA,
|
||||
initialAmount: 0,
|
||||
initialWeight: 5,
|
||||
wantWeight: 5,
|
||||
},
|
||||
{
|
||||
name: "non matching block root no change",
|
||||
targetEpoch: currentEpoch,
|
||||
blockRoot: rootB,
|
||||
initialAmount: 10,
|
||||
initialWeight: 7,
|
||||
wantWeight: 7,
|
||||
},
|
||||
{
|
||||
name: "previous epoch target uses earlier slot",
|
||||
targetEpoch: currentEpoch - 1,
|
||||
blockRoot: rootA,
|
||||
initialAmount: 20,
|
||||
initialWeight: 0,
|
||||
wantWeight: primitives.Gwei(cfg.MinActivationBalance),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var paymentIdx int
|
||||
if tt.targetEpoch == currentEpoch {
|
||||
paymentIdx = int(slotsPerEpoch + (slot % slotsPerEpoch))
|
||||
} else {
|
||||
paymentIdx = int(slot % slotsPerEpoch)
|
||||
}
|
||||
state := buildStateForPaymentTest(t, stateSlot, paymentIdx, tt.initialAmount, tt.initialWeight, map[primitives.Slot][]byte{
|
||||
slot: tt.blockRoot,
|
||||
slot - 1: rootB,
|
||||
})
|
||||
|
||||
att := ðpb.Attestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: slot,
|
||||
CommitteeIndex: 0,
|
||||
BeaconBlockRoot: tt.blockRoot,
|
||||
Source: ðpb.Checkpoint{},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: tt.targetEpoch,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
participatedFlags := map[uint8]bool{
|
||||
cfg.TimelySourceFlagIndex: true,
|
||||
cfg.TimelyTargetFlagIndex: true,
|
||||
cfg.TimelyHeadFlagIndex: true,
|
||||
}
|
||||
indices := []uint64{0}
|
||||
|
||||
gotState, err := UpdatePendingPaymentWeight(state, att, indices, participatedFlags)
|
||||
require.NoError(t, err)
|
||||
|
||||
payment, err := gotState.BuilderPendingPayment(uint64(paymentIdx))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.wantWeight, payment.Weight)
|
||||
})
|
||||
}
|
||||
}
|
||||
193
beacon-chain/core/gloas/bid.go
Normal file
193
beacon-chain/core/gloas/bid.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls/common"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ProcessExecutionPayloadBid processes a signed execution payload bid in the Gloas fork.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// process_execution_payload_bid(state: BeaconState, block: BeaconBlock):
|
||||
//
|
||||
// signed_bid = block.body.signed_execution_payload_bid
|
||||
// bid = signed_bid.message
|
||||
// builder_index = bid.builder_index
|
||||
// amount = bid.value
|
||||
// if builder_index == BUILDER_INDEX_SELF_BUILD:
|
||||
// assert amount == 0
|
||||
// assert signed_bid.signature == G2_POINT_AT_INFINITY
|
||||
// else:
|
||||
// assert is_active_builder(state, builder_index)
|
||||
// assert can_builder_cover_bid(state, builder_index, amount)
|
||||
// assert verify_execution_payload_bid_signature(state, signed_bid)
|
||||
// assert bid.slot == block.slot
|
||||
// assert bid.parent_block_hash == state.latest_block_hash
|
||||
// assert bid.parent_block_root == block.parent_root
|
||||
// assert bid.prev_randao == get_randao_mix(state, get_current_epoch(state))
|
||||
// if amount > 0:
|
||||
// state.builder_pending_payments[...] = BuilderPendingPayment(weight=0, withdrawal=BuilderPendingWithdrawal(fee_recipient=bid.fee_recipient, amount=amount, builder_index=builder_index))
|
||||
// state.latest_execution_payload_bid = bid
|
||||
func ProcessExecutionPayloadBid(st state.BeaconState, block interfaces.ReadOnlyBeaconBlock) error {
|
||||
signedBid, err := block.Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get signed execution payload bid")
|
||||
}
|
||||
|
||||
wrappedBid, err := blocks.WrappedROSignedExecutionPayloadBid(signedBid)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to wrap signed bid")
|
||||
}
|
||||
|
||||
bid, err := wrappedBid.Bid()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get bid from wrapped bid")
|
||||
}
|
||||
|
||||
builderIndex := bid.BuilderIndex()
|
||||
amount := bid.Value()
|
||||
|
||||
if builderIndex == params.BeaconConfig().BuilderIndexSelfBuild {
|
||||
if amount != 0 {
|
||||
return fmt.Errorf("self-build amount must be zero, got %d", amount)
|
||||
}
|
||||
if wrappedBid.Signature() != common.InfiniteSignature {
|
||||
return errors.New("self-build signature must be point at infinity")
|
||||
}
|
||||
} else {
|
||||
ok, err := st.IsActiveBuilder(builderIndex)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "builder active check failed")
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("builder %d is not active", builderIndex)
|
||||
}
|
||||
|
||||
ok, err = st.CanBuilderCoverBid(builderIndex, amount)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "builder balance check failed")
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("builder %d cannot cover bid amount %d", builderIndex, amount)
|
||||
}
|
||||
|
||||
if err := validatePayloadBidSignature(st, wrappedBid); err != nil {
|
||||
return errors.Wrap(err, "bid signature validation failed")
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateBidConsistency(st, bid, block); err != nil {
|
||||
return errors.Wrap(err, "bid consistency validation failed")
|
||||
}
|
||||
|
||||
if amount > 0 {
|
||||
feeRecipient := bid.FeeRecipient()
|
||||
pendingPayment := ðpb.BuilderPendingPayment{
|
||||
Weight: 0,
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: feeRecipient[:],
|
||||
Amount: amount,
|
||||
BuilderIndex: builderIndex,
|
||||
},
|
||||
}
|
||||
slotIndex := params.BeaconConfig().SlotsPerEpoch + (bid.Slot() % params.BeaconConfig().SlotsPerEpoch)
|
||||
if err := st.SetBuilderPendingPayment(slotIndex, pendingPayment); err != nil {
|
||||
return errors.Wrap(err, "failed to set pending payment")
|
||||
}
|
||||
}
|
||||
|
||||
if err := st.SetExecutionPayloadBid(bid); err != nil {
|
||||
return errors.Wrap(err, "failed to cache execution payload bid")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateBidConsistency checks that the bid is consistent with the current beacon state.
|
||||
func validateBidConsistency(st state.BeaconState, bid interfaces.ROExecutionPayloadBid, block interfaces.ReadOnlyBeaconBlock) error {
|
||||
if bid.Slot() != block.Slot() {
|
||||
return fmt.Errorf("bid slot %d does not match block slot %d", bid.Slot(), block.Slot())
|
||||
}
|
||||
|
||||
latestBlockHash, err := st.LatestBlockHash()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get latest block hash")
|
||||
}
|
||||
if bid.ParentBlockHash() != latestBlockHash {
|
||||
return fmt.Errorf("bid parent block hash mismatch: got %x, expected %x",
|
||||
bid.ParentBlockHash(), latestBlockHash)
|
||||
}
|
||||
|
||||
if bid.ParentBlockRoot() != block.ParentRoot() {
|
||||
return fmt.Errorf("bid parent block root mismatch: got %x, expected %x",
|
||||
bid.ParentBlockRoot(), block.ParentRoot())
|
||||
}
|
||||
|
||||
randaoMix, err := helpers.RandaoMix(st, slots.ToEpoch(st.Slot()))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get randao mix")
|
||||
}
|
||||
if bid.PrevRandao() != [32]byte(randaoMix) {
|
||||
return fmt.Errorf("bid prev randao mismatch: got %x, expected %x", bid.PrevRandao(), randaoMix)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePayloadBidSignature verifies the BLS signature on a signed execution payload bid.
|
||||
// It validates that the signature was created by the builder specified in the bid
|
||||
// using the appropriate domain for the beacon builder.
|
||||
func validatePayloadBidSignature(st state.ReadOnlyBeaconState, signedBid interfaces.ROSignedExecutionPayloadBid) error {
|
||||
bid, err := signedBid.Bid()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get bid")
|
||||
}
|
||||
|
||||
pubkey, err := st.BuilderPubkey(bid.BuilderIndex())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get builder pubkey")
|
||||
}
|
||||
|
||||
publicKey, err := bls.PublicKeyFromBytes(pubkey[:])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid builder public key")
|
||||
}
|
||||
|
||||
signatureBytes := signedBid.Signature()
|
||||
signature, err := bls.SignatureFromBytes(signatureBytes[:])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid signature format")
|
||||
}
|
||||
|
||||
currentEpoch := slots.ToEpoch(bid.Slot())
|
||||
domain, err := signing.Domain(
|
||||
st.Fork(),
|
||||
currentEpoch,
|
||||
params.BeaconConfig().DomainBeaconBuilder,
|
||||
st.GenesisValidatorsRoot(),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to compute signing domain")
|
||||
}
|
||||
|
||||
signingRoot, err := signedBid.SigningRoot(domain)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to compute signing root")
|
||||
}
|
||||
|
||||
if !signature.Verify(publicKey, signingRoot[:]) {
|
||||
return signing.ErrSigFailedToVerify
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
633
beacon-chain/core/gloas/bid_test.go
Normal file
633
beacon-chain/core/gloas/bid_test.go
Normal file
@@ -0,0 +1,633 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls/common"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
validatorpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
fastssz "github.com/prysmaticlabs/fastssz"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type stubBlockBody struct {
|
||||
signedBid *ethpb.SignedExecutionPayloadBid
|
||||
}
|
||||
|
||||
func (s stubBlockBody) Version() int { return version.Gloas }
|
||||
func (s stubBlockBody) RandaoReveal() [96]byte { return [96]byte{} }
|
||||
func (s stubBlockBody) Eth1Data() *ethpb.Eth1Data { return nil }
|
||||
func (s stubBlockBody) Graffiti() [32]byte { return [32]byte{} }
|
||||
func (s stubBlockBody) ProposerSlashings() []*ethpb.ProposerSlashing { return nil }
|
||||
func (s stubBlockBody) AttesterSlashings() []ethpb.AttSlashing { return nil }
|
||||
func (s stubBlockBody) Attestations() []ethpb.Att { return nil }
|
||||
func (s stubBlockBody) Deposits() []*ethpb.Deposit { return nil }
|
||||
func (s stubBlockBody) VoluntaryExits() []*ethpb.SignedVoluntaryExit { return nil }
|
||||
func (s stubBlockBody) SyncAggregate() (*ethpb.SyncAggregate, error) { return nil, nil }
|
||||
func (s stubBlockBody) IsNil() bool { return s.signedBid == nil }
|
||||
func (s stubBlockBody) HashTreeRoot() ([32]byte, error) { return [32]byte{}, nil }
|
||||
func (s stubBlockBody) Proto() (proto.Message, error) { return nil, nil }
|
||||
func (s stubBlockBody) Execution() (interfaces.ExecutionData, error) { return nil, nil }
|
||||
func (s stubBlockBody) BLSToExecutionChanges() ([]*ethpb.SignedBLSToExecutionChange, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (s stubBlockBody) BlobKzgCommitments() ([][]byte, error) { return nil, nil }
|
||||
func (s stubBlockBody) ExecutionRequests() (*enginev1.ExecutionRequests, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (s stubBlockBody) PayloadAttestations() ([]*ethpb.PayloadAttestation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (s stubBlockBody) SignedExecutionPayloadBid() (*ethpb.SignedExecutionPayloadBid, error) {
|
||||
return s.signedBid, nil
|
||||
}
|
||||
func (s stubBlockBody) MarshalSSZ() ([]byte, error) { return nil, nil }
|
||||
func (s stubBlockBody) MarshalSSZTo([]byte) ([]byte, error) { return nil, nil }
|
||||
func (s stubBlockBody) UnmarshalSSZ([]byte) error { return nil }
|
||||
func (s stubBlockBody) SizeSSZ() int { return 0 }
|
||||
|
||||
type stubBlock struct {
|
||||
slot primitives.Slot
|
||||
proposer primitives.ValidatorIndex
|
||||
parentRoot [32]byte
|
||||
body stubBlockBody
|
||||
v int
|
||||
}
|
||||
|
||||
var (
|
||||
_ interfaces.ReadOnlyBeaconBlockBody = (*stubBlockBody)(nil)
|
||||
_ interfaces.ReadOnlyBeaconBlock = (*stubBlock)(nil)
|
||||
)
|
||||
|
||||
func (s stubBlock) Slot() primitives.Slot { return s.slot }
|
||||
func (s stubBlock) ProposerIndex() primitives.ValidatorIndex { return s.proposer }
|
||||
func (s stubBlock) ParentRoot() [32]byte { return s.parentRoot }
|
||||
func (s stubBlock) StateRoot() [32]byte { return [32]byte{} }
|
||||
func (s stubBlock) Body() interfaces.ReadOnlyBeaconBlockBody { return s.body }
|
||||
func (s stubBlock) IsNil() bool { return false }
|
||||
func (s stubBlock) IsBlinded() bool { return false }
|
||||
func (s stubBlock) HashTreeRoot() ([32]byte, error) { return [32]byte{}, nil }
|
||||
func (s stubBlock) Proto() (proto.Message, error) { return nil, nil }
|
||||
func (s stubBlock) MarshalSSZ() ([]byte, error) { return nil, nil }
|
||||
func (s stubBlock) MarshalSSZTo([]byte) ([]byte, error) { return nil, nil }
|
||||
func (s stubBlock) UnmarshalSSZ([]byte) error { return nil }
|
||||
func (s stubBlock) SizeSSZ() int { return 0 }
|
||||
func (s stubBlock) Version() int { return s.v }
|
||||
func (s stubBlock) AsSignRequestObject() (validatorpb.SignRequestObject, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (s stubBlock) HashTreeRootWith(*fastssz.Hasher) error { return nil }
|
||||
|
||||
func buildGloasState(t *testing.T, slot primitives.Slot, proposerIdx primitives.ValidatorIndex, builderIdx primitives.BuilderIndex, balance uint64, randao [32]byte, latestHash [32]byte, builderPubkey [48]byte) *state_native.BeaconState {
|
||||
t.Helper()
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
stateRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range blockRoots {
|
||||
blockRoots[i] = bytes.Repeat([]byte{0xAA}, 32)
|
||||
stateRoots[i] = bytes.Repeat([]byte{0xBB}, 32)
|
||||
}
|
||||
randaoMixes := make([][]byte, cfg.EpochsPerHistoricalVector)
|
||||
for i := range randaoMixes {
|
||||
randaoMixes[i] = randao[:]
|
||||
}
|
||||
|
||||
withdrawalCreds := make([]byte, 32)
|
||||
withdrawalCreds[0] = cfg.BuilderWithdrawalPrefixByte
|
||||
|
||||
validatorCount := int(proposerIdx) + 1
|
||||
validators := make([]*ethpb.Validator, validatorCount)
|
||||
balances := make([]uint64, validatorCount)
|
||||
for i := range validatorCount {
|
||||
validators[i] = ðpb.Validator{
|
||||
PublicKey: builderPubkey[:],
|
||||
WithdrawalCredentials: withdrawalCreds,
|
||||
EffectiveBalance: balance,
|
||||
Slashed: false,
|
||||
ActivationEligibilityEpoch: 0,
|
||||
ActivationEpoch: 0,
|
||||
ExitEpoch: cfg.FarFutureEpoch,
|
||||
WithdrawableEpoch: cfg.FarFutureEpoch,
|
||||
}
|
||||
balances[i] = balance
|
||||
}
|
||||
|
||||
payments := make([]*ethpb.BuilderPendingPayment, cfg.SlotsPerEpoch*2)
|
||||
for i := range payments {
|
||||
payments[i] = ðpb.BuilderPendingPayment{Withdrawal: ðpb.BuilderPendingWithdrawal{}}
|
||||
}
|
||||
|
||||
var builders []*ethpb.Builder
|
||||
if builderIdx != params.BeaconConfig().BuilderIndexSelfBuild {
|
||||
builderCount := int(builderIdx) + 1
|
||||
builders = make([]*ethpb.Builder, builderCount)
|
||||
builders[builderCount-1] = ðpb.Builder{
|
||||
Pubkey: builderPubkey[:],
|
||||
Version: []byte{0},
|
||||
ExecutionAddress: bytes.Repeat([]byte{0x01}, 20),
|
||||
Balance: primitives.Gwei(balance),
|
||||
DepositEpoch: 0,
|
||||
WithdrawableEpoch: cfg.FarFutureEpoch,
|
||||
}
|
||||
}
|
||||
|
||||
stProto := ðpb.BeaconStateGloas{
|
||||
Slot: slot,
|
||||
GenesisValidatorsRoot: bytes.Repeat([]byte{0x11}, 32),
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x22}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x22}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
BlockRoots: blockRoots,
|
||||
StateRoots: stateRoots,
|
||||
RandaoMixes: randaoMixes,
|
||||
Validators: validators,
|
||||
Balances: balances,
|
||||
LatestBlockHash: latestHash[:],
|
||||
BuilderPendingPayments: payments,
|
||||
BuilderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{},
|
||||
Builders: builders,
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{
|
||||
Epoch: 1,
|
||||
},
|
||||
}
|
||||
|
||||
st, err := state_native.InitializeFromProtoGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
return st.(*state_native.BeaconState)
|
||||
}
|
||||
|
||||
func signBid(t *testing.T, sk common.SecretKey, bid *ethpb.ExecutionPayloadBid, fork *ethpb.Fork, genesisRoot [32]byte) [96]byte {
|
||||
t.Helper()
|
||||
epoch := slots.ToEpoch(primitives.Slot(bid.Slot))
|
||||
domain, err := signing.Domain(fork, epoch, params.BeaconConfig().DomainBeaconBuilder, genesisRoot[:])
|
||||
require.NoError(t, err)
|
||||
root, err := signing.ComputeSigningRoot(bid, domain)
|
||||
require.NoError(t, err)
|
||||
sig := sk.Sign(root[:]).Marshal()
|
||||
var out [96]byte
|
||||
copy(out[:], sig)
|
||||
return out
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_SelfBuildSuccess(t *testing.T) {
|
||||
slot := primitives.Slot(12)
|
||||
proposerIdx := primitives.ValidatorIndex(0)
|
||||
builderIdx := params.BeaconConfig().BuilderIndexSelfBuild
|
||||
randao := [32]byte(bytes.Repeat([]byte{0xAA}, 32))
|
||||
latestHash := [32]byte(bytes.Repeat([]byte{0xBB}, 32))
|
||||
pubKey := [48]byte{}
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 0,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
signed := ðpb.SignedExecutionPayloadBid{
|
||||
Message: bid,
|
||||
Signature: common.InfiniteSignature[:],
|
||||
}
|
||||
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx,
|
||||
parentRoot: bytesutil.ToBytes32(bid.ParentBlockRoot),
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
require.NoError(t, ProcessExecutionPayloadBid(state, block))
|
||||
|
||||
stateProto, ok := state.ToProto().(*ethpb.BeaconStateGloas)
|
||||
require.Equal(t, true, ok)
|
||||
slotIndex := params.BeaconConfig().SlotsPerEpoch + (slot % params.BeaconConfig().SlotsPerEpoch)
|
||||
require.Equal(t, primitives.Gwei(0), stateProto.BuilderPendingPayments[slotIndex].Withdrawal.Amount)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_SelfBuildNonZeroAmountFails(t *testing.T) {
|
||||
slot := primitives.Slot(2)
|
||||
proposerIdx := primitives.ValidatorIndex(0)
|
||||
builderIdx := params.BeaconConfig().BuilderIndexSelfBuild
|
||||
randao := [32]byte{}
|
||||
latestHash := [32]byte{1}
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, [48]byte{})
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
|
||||
PrevRandao: randao[:],
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
|
||||
}
|
||||
signed := ðpb.SignedExecutionPayloadBid{
|
||||
Message: bid,
|
||||
Signature: common.InfiniteSignature[:],
|
||||
}
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx,
|
||||
parentRoot: bytesutil.ToBytes32(bid.ParentBlockRoot),
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
err := ProcessExecutionPayloadBid(state, block)
|
||||
require.ErrorContains(t, "self-build amount must be zero", err)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_PendingPaymentAndCacheBid(t *testing.T) {
|
||||
slot := primitives.Slot(8)
|
||||
builderIdx := primitives.BuilderIndex(1)
|
||||
proposerIdx := primitives.ValidatorIndex(2)
|
||||
randao := [32]byte(bytes.Repeat([]byte{0xAA}, 32))
|
||||
latestHash := [32]byte(bytes.Repeat([]byte{0xBB}, 32))
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
pub := sk.PublicKey().Marshal()
|
||||
var pubKey [48]byte
|
||||
copy(pubKey[:], pub)
|
||||
|
||||
balance := params.BeaconConfig().MinActivationBalance + 1_000_000
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, balance, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 500_000,
|
||||
ExecutionPayment: 1,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
signed := ðpb.SignedExecutionPayloadBid{
|
||||
Message: bid,
|
||||
Signature: sig[:],
|
||||
}
|
||||
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx, // not self-build
|
||||
parentRoot: bytesutil.ToBytes32(bid.ParentBlockRoot),
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
require.NoError(t, ProcessExecutionPayloadBid(state, block))
|
||||
|
||||
stateProto, ok := state.ToProto().(*ethpb.BeaconStateGloas)
|
||||
require.Equal(t, true, ok)
|
||||
slotIndex := params.BeaconConfig().SlotsPerEpoch + (slot % params.BeaconConfig().SlotsPerEpoch)
|
||||
require.Equal(t, primitives.Gwei(500_000), stateProto.BuilderPendingPayments[slotIndex].Withdrawal.Amount)
|
||||
|
||||
require.NotNil(t, stateProto.LatestExecutionPayloadBid)
|
||||
require.Equal(t, primitives.BuilderIndex(1), stateProto.LatestExecutionPayloadBid.BuilderIndex)
|
||||
require.Equal(t, primitives.Gwei(500_000), stateProto.LatestExecutionPayloadBid.Value)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_BuilderNotActive(t *testing.T) {
|
||||
slot := primitives.Slot(4)
|
||||
builderIdx := primitives.BuilderIndex(1)
|
||||
proposerIdx := primitives.ValidatorIndex(2)
|
||||
randao := [32]byte(bytes.Repeat([]byte{0x01}, 32))
|
||||
latestHash := [32]byte(bytes.Repeat([]byte{0x02}, 32))
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
var pubKey [48]byte
|
||||
copy(pubKey[:], sk.PublicKey().Marshal())
|
||||
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
// Make builder inactive by setting withdrawable_epoch.
|
||||
stateProto := state.ToProto().(*ethpb.BeaconStateGloas)
|
||||
stateProto.Builders[int(builderIdx)].WithdrawableEpoch = 0
|
||||
stateIface, err := state_native.InitializeFromProtoGloas(stateProto)
|
||||
require.NoError(t, err)
|
||||
state = stateIface.(*state_native.BeaconState)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x03}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x04}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x05}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x06}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
signed := ðpb.SignedExecutionPayloadBid{Message: bid, Signature: sig[:]}
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx,
|
||||
parentRoot: bytesutil.ToBytes32(bid.ParentBlockRoot),
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
err = ProcessExecutionPayloadBid(state, block)
|
||||
require.ErrorContains(t, "is not active", err)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_CannotCoverBid(t *testing.T) {
|
||||
slot := primitives.Slot(5)
|
||||
builderIdx := primitives.BuilderIndex(1)
|
||||
proposerIdx := primitives.ValidatorIndex(2)
|
||||
randao := [32]byte(bytes.Repeat([]byte{0x0A}, 32))
|
||||
latestHash := [32]byte(bytes.Repeat([]byte{0x0B}, 32))
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
var pubKey [48]byte
|
||||
copy(pubKey[:], sk.PublicKey().Marshal())
|
||||
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+10, randao, latestHash, pubKey)
|
||||
stateProto := state.ToProto().(*ethpb.BeaconStateGloas)
|
||||
// Add pending balances to push below required balance.
|
||||
stateProto.BuilderPendingWithdrawals = []*ethpb.BuilderPendingWithdrawal{
|
||||
{Amount: 15, BuilderIndex: builderIdx},
|
||||
}
|
||||
stateProto.BuilderPendingPayments = []*ethpb.BuilderPendingPayment{
|
||||
{Withdrawal: ðpb.BuilderPendingWithdrawal{Amount: 20, BuilderIndex: builderIdx}},
|
||||
}
|
||||
stateIface, err := state_native.InitializeFromProtoGloas(stateProto)
|
||||
require.NoError(t, err)
|
||||
state = stateIface.(*state_native.BeaconState)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 25,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
signed := ðpb.SignedExecutionPayloadBid{Message: bid, Signature: sig[:]}
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx,
|
||||
parentRoot: bytesutil.ToBytes32(bid.ParentBlockRoot),
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
err = ProcessExecutionPayloadBid(state, block)
|
||||
require.ErrorContains(t, "cannot cover bid amount", err)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_InvalidSignature(t *testing.T) {
|
||||
slot := primitives.Slot(6)
|
||||
builderIdx := primitives.BuilderIndex(1)
|
||||
proposerIdx := primitives.ValidatorIndex(2)
|
||||
randao := [32]byte(bytes.Repeat([]byte{0xAA}, 32))
|
||||
latestHash := [32]byte(bytes.Repeat([]byte{0xBB}, 32))
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
var pubKey [48]byte
|
||||
copy(pubKey[:], sk.PublicKey().Marshal())
|
||||
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
// Use an invalid signature.
|
||||
invalidSig := [96]byte{1}
|
||||
signed := ðpb.SignedExecutionPayloadBid{Message: bid, Signature: invalidSig[:]}
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx,
|
||||
parentRoot: bytesutil.ToBytes32(bid.ParentBlockRoot),
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
err = ProcessExecutionPayloadBid(state, block)
|
||||
require.ErrorContains(t, "bid signature validation failed", err)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_SlotMismatch(t *testing.T) {
|
||||
slot := primitives.Slot(10)
|
||||
builderIdx := primitives.BuilderIndex(1)
|
||||
proposerIdx := primitives.ValidatorIndex(2)
|
||||
randao := [32]byte(bytes.Repeat([]byte{0xAA}, 32))
|
||||
latestHash := [32]byte(bytes.Repeat([]byte{0xBB}, 32))
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
var pubKey [48]byte
|
||||
copy(pubKey[:], sk.PublicKey().Marshal())
|
||||
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot + 1, // mismatch
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
signed := ðpb.SignedExecutionPayloadBid{Message: bid, Signature: sig[:]}
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx,
|
||||
parentRoot: bytesutil.ToBytes32(bid.ParentBlockRoot),
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
err = ProcessExecutionPayloadBid(state, block)
|
||||
require.ErrorContains(t, "bid slot", err)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_ParentHashMismatch(t *testing.T) {
|
||||
slot := primitives.Slot(11)
|
||||
builderIdx := primitives.BuilderIndex(1)
|
||||
proposerIdx := primitives.ValidatorIndex(2)
|
||||
randao := [32]byte(bytes.Repeat([]byte{0xAA}, 32))
|
||||
latestHash := [32]byte(bytes.Repeat([]byte{0xBB}, 32))
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
var pubKey [48]byte
|
||||
copy(pubKey[:], sk.PublicKey().Marshal())
|
||||
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32), // mismatch
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
signed := ðpb.SignedExecutionPayloadBid{Message: bid, Signature: sig[:]}
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx,
|
||||
parentRoot: bytesutil.ToBytes32(bid.ParentBlockRoot),
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
err = ProcessExecutionPayloadBid(state, block)
|
||||
require.ErrorContains(t, "parent block hash mismatch", err)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_ParentRootMismatch(t *testing.T) {
|
||||
slot := primitives.Slot(12)
|
||||
builderIdx := primitives.BuilderIndex(1)
|
||||
proposerIdx := primitives.ValidatorIndex(2)
|
||||
randao := [32]byte(bytes.Repeat([]byte{0xAA}, 32))
|
||||
latestHash := [32]byte(bytes.Repeat([]byte{0xBB}, 32))
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
var pubKey [48]byte
|
||||
copy(pubKey[:], sk.PublicKey().Marshal())
|
||||
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
parentRoot := bytes.Repeat([]byte{0x22}, 32)
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: parentRoot,
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
signed := ðpb.SignedExecutionPayloadBid{Message: bid, Signature: sig[:]}
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx,
|
||||
parentRoot: bytesutil.ToBytes32(bytes.Repeat([]byte{0x99}, 32)), // mismatch
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
err = ProcessExecutionPayloadBid(state, block)
|
||||
require.ErrorContains(t, "parent block root mismatch", err)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_PrevRandaoMismatch(t *testing.T) {
|
||||
slot := primitives.Slot(13)
|
||||
builderIdx := primitives.BuilderIndex(1)
|
||||
proposerIdx := primitives.ValidatorIndex(2)
|
||||
randao := [32]byte(bytes.Repeat([]byte{0xAA}, 32))
|
||||
latestHash := [32]byte(bytes.Repeat([]byte{0xBB}, 32))
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
var pubKey [48]byte
|
||||
copy(pubKey[:], sk.PublicKey().Marshal())
|
||||
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x01}, 32), // mismatch
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
signed := ðpb.SignedExecutionPayloadBid{Message: bid, Signature: sig[:]}
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx,
|
||||
parentRoot: bytesutil.ToBytes32(bid.ParentBlockRoot),
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
err = ProcessExecutionPayloadBid(state, block)
|
||||
require.ErrorContains(t, "prev randao mismatch", err)
|
||||
}
|
||||
76
beacon-chain/core/gloas/pending_payment.go
Normal file
76
beacon-chain/core/gloas/pending_payment.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ProcessBuilderPendingPayments processes the builder pending payments from the previous epoch.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// def process_builder_pending_payments(state: BeaconState) -> None:
|
||||
//
|
||||
// quorum = get_builder_payment_quorum_threshold(state)
|
||||
// for payment in state.builder_pending_payments[:SLOTS_PER_EPOCH]:
|
||||
// if payment.weight >= quorum:
|
||||
// state.builder_pending_withdrawals.append(payment.withdrawal)
|
||||
//
|
||||
// old_payments = state.builder_pending_payments[SLOTS_PER_EPOCH:]
|
||||
// new_payments = [BuilderPendingPayment() for _ in range(SLOTS_PER_EPOCH)]
|
||||
// state.builder_pending_payments = old_payments + new_payments
|
||||
func ProcessBuilderPendingPayments(state state.BeaconState) error {
|
||||
quorum, err := builderQuorumThreshold(state)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute builder payment quorum threshold")
|
||||
}
|
||||
|
||||
payments, err := state.BuilderPendingPayments()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get builder pending payments")
|
||||
}
|
||||
|
||||
slotsPerEpoch := uint64(params.BeaconConfig().SlotsPerEpoch)
|
||||
var withdrawals []*ethpb.BuilderPendingWithdrawal
|
||||
for _, payment := range payments[:slotsPerEpoch] {
|
||||
if quorum > payment.Weight {
|
||||
continue
|
||||
}
|
||||
withdrawals = append(withdrawals, payment.Withdrawal)
|
||||
}
|
||||
|
||||
if err := state.AppendBuilderPendingWithdrawals(withdrawals); err != nil {
|
||||
return errors.Wrap(err, "could not append builder pending withdrawals")
|
||||
}
|
||||
|
||||
if err := state.RotateBuilderPendingPayments(); err != nil {
|
||||
return errors.Wrap(err, "could not rotate builder pending payments")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// builderQuorumThreshold calculates the quorum threshold for builder payments.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// def get_builder_payment_quorum_threshold(state: BeaconState) -> uint64:
|
||||
//
|
||||
// per_slot_balance = get_total_active_balance(state) // SLOTS_PER_EPOCH
|
||||
// quorum = per_slot_balance * BUILDER_PAYMENT_THRESHOLD_NUMERATOR
|
||||
// return uint64(quorum // BUILDER_PAYMENT_THRESHOLD_DENOMINATOR)
|
||||
func builderQuorumThreshold(state state.ReadOnlyBeaconState) (primitives.Gwei, error) {
|
||||
activeBalance, err := helpers.TotalActiveBalance(state)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "could not get total active balance")
|
||||
}
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
slotsPerEpoch := uint64(cfg.SlotsPerEpoch)
|
||||
numerator := cfg.BuilderPaymentThresholdNumerator
|
||||
denominator := cfg.BuilderPaymentThresholdDenominator
|
||||
|
||||
activeBalancePerSlot := activeBalance / slotsPerEpoch
|
||||
quorum := (activeBalancePerSlot * numerator) / denominator
|
||||
return primitives.Gwei(quorum), nil
|
||||
}
|
||||
119
beacon-chain/core/gloas/pending_payment_test.go
Normal file
119
beacon-chain/core/gloas/pending_payment_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func TestBuilderQuorumThreshold(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
cfg := params.BeaconConfig()
|
||||
|
||||
validators := []*ethpb.Validator{
|
||||
{EffectiveBalance: cfg.MaxEffectiveBalance, ActivationEpoch: 0, ExitEpoch: 1},
|
||||
{EffectiveBalance: cfg.MaxEffectiveBalance, ActivationEpoch: 0, ExitEpoch: 1},
|
||||
}
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(ðpb.BeaconStateGloas{Validators: validators})
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := builderQuorumThreshold(st)
|
||||
require.NoError(t, err)
|
||||
|
||||
total := uint64(len(validators)) * cfg.MaxEffectiveBalance
|
||||
perSlot := total / uint64(cfg.SlotsPerEpoch)
|
||||
want := (perSlot * cfg.BuilderPaymentThresholdNumerator) / cfg.BuilderPaymentThresholdDenominator
|
||||
require.Equal(t, primitives.Gwei(want), got)
|
||||
}
|
||||
|
||||
func TestProcessBuilderPendingPayments(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
cfg := params.BeaconConfig()
|
||||
|
||||
buildPayments := func(weights ...primitives.Gwei) []*ethpb.BuilderPendingPayment {
|
||||
p := make([]*ethpb.BuilderPendingPayment, 2*int(cfg.SlotsPerEpoch))
|
||||
for i := range p {
|
||||
p[i] = ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{FeeRecipient: make([]byte, 20)},
|
||||
}
|
||||
}
|
||||
for i, w := range weights {
|
||||
p[i].Weight = w
|
||||
p[i].Withdrawal.Amount = 1
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
validators := []*ethpb.Validator{
|
||||
{EffectiveBalance: cfg.MaxEffectiveBalance, ActivationEpoch: 0, ExitEpoch: 1},
|
||||
{EffectiveBalance: cfg.MaxEffectiveBalance, ActivationEpoch: 0, ExitEpoch: 1},
|
||||
}
|
||||
pbSt, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{Validators: validators})
|
||||
require.NoError(t, err)
|
||||
|
||||
total := uint64(len(validators)) * cfg.MaxEffectiveBalance
|
||||
perSlot := total / uint64(cfg.SlotsPerEpoch)
|
||||
quorum := (perSlot * cfg.BuilderPaymentThresholdNumerator) / cfg.BuilderPaymentThresholdDenominator
|
||||
slotsPerEpoch := int(cfg.SlotsPerEpoch)
|
||||
|
||||
t.Run("append qualifying withdrawals", func(t *testing.T) {
|
||||
payments := buildPayments(primitives.Gwei(quorum+1), primitives.Gwei(quorum+2))
|
||||
st := &testProcessState{BeaconState: pbSt, payments: payments}
|
||||
|
||||
require.NoError(t, ProcessBuilderPendingPayments(st))
|
||||
require.Equal(t, 2, len(st.withdrawals))
|
||||
require.Equal(t, payments[0].Withdrawal, st.withdrawals[0])
|
||||
require.Equal(t, payments[1].Withdrawal, st.withdrawals[1])
|
||||
|
||||
require.Equal(t, 2*slotsPerEpoch, len(st.payments))
|
||||
for i := slotsPerEpoch; i < 2*slotsPerEpoch; i++ {
|
||||
require.Equal(t, primitives.Gwei(0), st.payments[i].Weight)
|
||||
require.Equal(t, primitives.Gwei(0), st.payments[i].Withdrawal.Amount)
|
||||
require.Equal(t, 20, len(st.payments[i].Withdrawal.FeeRecipient))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no withdrawals when below quorum", func(t *testing.T) {
|
||||
payments := buildPayments(primitives.Gwei(quorum - 1))
|
||||
st := &testProcessState{BeaconState: pbSt, payments: payments}
|
||||
|
||||
require.NoError(t, ProcessBuilderPendingPayments(st))
|
||||
require.Equal(t, 0, len(st.withdrawals))
|
||||
})
|
||||
}
|
||||
|
||||
type testProcessState struct {
|
||||
state.BeaconState
|
||||
payments []*ethpb.BuilderPendingPayment
|
||||
withdrawals []*ethpb.BuilderPendingWithdrawal
|
||||
}
|
||||
|
||||
func (t *testProcessState) BuilderPendingPayments() ([]*ethpb.BuilderPendingPayment, error) {
|
||||
return t.payments, nil
|
||||
}
|
||||
|
||||
func (t *testProcessState) AppendBuilderPendingWithdrawals(withdrawals []*ethpb.BuilderPendingWithdrawal) error {
|
||||
t.withdrawals = append(t.withdrawals, withdrawals...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *testProcessState) RotateBuilderPendingPayments() error {
|
||||
slotsPerEpoch := int(params.BeaconConfig().SlotsPerEpoch)
|
||||
rotated := slices.Clone(t.payments[slotsPerEpoch:])
|
||||
for range slotsPerEpoch {
|
||||
rotated = append(rotated, ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
},
|
||||
})
|
||||
}
|
||||
t.payments = rotated
|
||||
return nil
|
||||
}
|
||||
@@ -1053,6 +1053,10 @@ func (s *Store) getStateUsingStateDiff(ctx context.Context, blockRoot [32]byte)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if uint64(slot) < s.getOffset() {
|
||||
return nil, ErrSlotBeforeOffset
|
||||
}
|
||||
|
||||
st, err := s.stateByDiff(ctx, slot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1070,6 +1074,10 @@ func (s *Store) hasStateUsingStateDiff(ctx context.Context, blockRoot [32]byte)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if uint64(slot) < s.getOffset() {
|
||||
return false, ErrSlotBeforeOffset
|
||||
}
|
||||
|
||||
stateLvl := computeLevel(s.getOffset(), slot)
|
||||
return stateLvl != -1, nil
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const (
|
||||
*/
|
||||
|
||||
// SlotInDiffTree returns whether the given slot is a saving point in the diff tree.
|
||||
// It it is, it also returns the offset and level in the tree.
|
||||
// If it is, it also returns the offset and level in the tree.
|
||||
func (s *Store) SlotInDiffTree(slot primitives.Slot) (uint64, int, error) {
|
||||
offset := s.getOffset()
|
||||
if uint64(slot) < offset {
|
||||
|
||||
@@ -25,7 +25,7 @@ func newStateDiffCache(s *Store) (*stateDiffCache, error) {
|
||||
return bbolt.ErrBucketNotFound
|
||||
}
|
||||
|
||||
offsetBytes := bucket.Get([]byte("offset"))
|
||||
offsetBytes := bucket.Get(offsetKey)
|
||||
if offsetBytes == nil {
|
||||
return errors.New("state diff cache: offset not found")
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
var (
|
||||
offsetKey = []byte("offset")
|
||||
ErrSlotBeforeOffset = errors.New("slot is before root offset")
|
||||
ErrSlotBeforeOffset = errors.New("slot is before state-diff root offset")
|
||||
)
|
||||
|
||||
func makeKeyForStateDiffTree(level int, slot uint64) []byte {
|
||||
@@ -73,6 +73,9 @@ func (s *Store) getAnchorState(offset uint64, lvl int, slot primitives.Slot) (an
|
||||
|
||||
// computeLevel computes the level in the diff tree. Returns -1 in case slot should not be in tree.
|
||||
func computeLevel(offset uint64, slot primitives.Slot) int {
|
||||
if uint64(slot) < offset {
|
||||
return -1
|
||||
}
|
||||
rel := uint64(slot) - offset
|
||||
for i, exp := range flags.Get().StateDiffExponents {
|
||||
if exp < 2 || exp >= 64 {
|
||||
|
||||
@@ -43,8 +43,12 @@ func TestStateDiff_ComputeLevel(t *testing.T) {
|
||||
|
||||
offset := db.getOffset()
|
||||
|
||||
// should be -1. slot < offset
|
||||
lvl := computeLevel(10, primitives.Slot(9))
|
||||
require.Equal(t, -1, lvl)
|
||||
|
||||
// 2 ** 21
|
||||
lvl := computeLevel(offset, primitives.Slot(math.PowerOf2(21)))
|
||||
lvl = computeLevel(offset, primitives.Slot(math.PowerOf2(21)))
|
||||
require.Equal(t, 0, lvl)
|
||||
|
||||
// 2 ** 21 * 3
|
||||
|
||||
@@ -1395,6 +1395,23 @@ func TestStore_CanSaveRetrieveStateUsingStateDiff(t *testing.T) {
|
||||
require.IsNil(t, readSt)
|
||||
})
|
||||
|
||||
t.Run("slot before offset", func(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
setDefaultStateDiffExponents()
|
||||
|
||||
err := setOffsetInDB(db, 10)
|
||||
require.NoError(t, err)
|
||||
|
||||
r := bytesutil.ToBytes32([]byte{'A'})
|
||||
ss := ðpb.StateSummary{Slot: 9, Root: r[:]}
|
||||
err = db.SaveStateSummary(t.Context(), ss)
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := db.getStateUsingStateDiff(t.Context(), r)
|
||||
require.ErrorIs(t, err, ErrSlotBeforeOffset)
|
||||
require.IsNil(t, st)
|
||||
})
|
||||
|
||||
t.Run("Full state snapshot", func(t *testing.T) {
|
||||
t.Run("using state summary", func(t *testing.T) {
|
||||
for v := range version.All() {
|
||||
@@ -1627,4 +1644,21 @@ func TestStore_HasStateUsingStateDiff(t *testing.T) {
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("slot before offset", func(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
setDefaultStateDiffExponents()
|
||||
|
||||
err := setOffsetInDB(db, 10)
|
||||
require.NoError(t, err)
|
||||
|
||||
r := bytesutil.ToBytes32([]byte{'B'})
|
||||
ss := ðpb.StateSummary{Slot: 0, Root: r[:]}
|
||||
err = db.SaveStateSummary(t.Context(), ss)
|
||||
require.NoError(t, err)
|
||||
|
||||
hasState, err := db.hasStateUsingStateDiff(t.Context(), r)
|
||||
require.ErrorIs(t, err, ErrSlotBeforeOffset)
|
||||
require.Equal(t, false, hasState)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ func TestGetSpec(t *testing.T) {
|
||||
config.MinSyncCommitteeParticipants = 71
|
||||
config.ProposerReorgCutoffBPS = primitives.BP(121)
|
||||
config.AttestationDueBPS = primitives.BP(122)
|
||||
config.AggregrateDueBPS = primitives.BP(123)
|
||||
config.AggregateDueBPS = primitives.BP(123)
|
||||
config.ContributionDueBPS = primitives.BP(124)
|
||||
config.TerminalBlockHash = common.HexToHash("TerminalBlockHash")
|
||||
config.TerminalBlockHashActivationEpoch = 72
|
||||
@@ -168,6 +168,10 @@ func TestGetSpec(t *testing.T) {
|
||||
config.BlobsidecarSubnetCount = 101
|
||||
config.BlobsidecarSubnetCountElectra = 102
|
||||
config.SyncMessageDueBPS = 103
|
||||
config.BuilderWithdrawalPrefixByte = byte('b')
|
||||
config.BuilderIndexSelfBuild = primitives.BuilderIndex(125)
|
||||
config.BuilderPaymentThresholdNumerator = 104
|
||||
config.BuilderPaymentThresholdDenominator = 105
|
||||
|
||||
var dbp [4]byte
|
||||
copy(dbp[:], []byte{'0', '0', '0', '1'})
|
||||
@@ -190,6 +194,9 @@ func TestGetSpec(t *testing.T) {
|
||||
var daap [4]byte
|
||||
copy(daap[:], []byte{'0', '0', '0', '7'})
|
||||
config.DomainAggregateAndProof = daap
|
||||
var dbb [4]byte
|
||||
copy(dbb[:], []byte{'0', '0', '0', '8'})
|
||||
config.DomainBeaconBuilder = dbb
|
||||
var dam [4]byte
|
||||
copy(dam[:], []byte{'1', '0', '0', '0'})
|
||||
config.DomainApplicationMask = dam
|
||||
@@ -205,7 +212,7 @@ func TestGetSpec(t *testing.T) {
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
|
||||
data, ok := resp.Data.(map[string]any)
|
||||
require.Equal(t, true, ok)
|
||||
assert.Equal(t, 175, len(data))
|
||||
assert.Equal(t, 180, len(data))
|
||||
for k, v := range data {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
switch k {
|
||||
@@ -419,8 +426,14 @@ func TestGetSpec(t *testing.T) {
|
||||
assert.Equal(t, "0x0a000000", v)
|
||||
case "DOMAIN_APPLICATION_BUILDER":
|
||||
assert.Equal(t, "0x00000001", v)
|
||||
case "DOMAIN_BEACON_BUILDER":
|
||||
assert.Equal(t, "0x30303038", v)
|
||||
case "DOMAIN_BLOB_SIDECAR":
|
||||
assert.Equal(t, "0x00000000", v)
|
||||
case "BUILDER_WITHDRAWAL_PREFIX":
|
||||
assert.Equal(t, "0x62", v)
|
||||
case "BUILDER_INDEX_SELF_BUILD":
|
||||
assert.Equal(t, "125", v)
|
||||
case "TRANSITION_TOTAL_DIFFICULTY":
|
||||
assert.Equal(t, "0", v)
|
||||
case "TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH":
|
||||
@@ -457,7 +470,7 @@ func TestGetSpec(t *testing.T) {
|
||||
assert.Equal(t, "121", v)
|
||||
case "ATTESTATION_DUE_BPS":
|
||||
assert.Equal(t, "122", v)
|
||||
case "AGGREGRATE_DUE_BPS":
|
||||
case "AGGREGATE_DUE_BPS":
|
||||
assert.Equal(t, "123", v)
|
||||
case "CONTRIBUTION_DUE_BPS":
|
||||
assert.Equal(t, "124", v)
|
||||
@@ -577,6 +590,10 @@ func TestGetSpec(t *testing.T) {
|
||||
assert.Equal(t, "102", v)
|
||||
case "SYNC_MESSAGE_DUE_BPS":
|
||||
assert.Equal(t, "103", v)
|
||||
case "BUILDER_PAYMENT_THRESHOLD_NUMERATOR":
|
||||
assert.Equal(t, "104", v)
|
||||
case "BUILDER_PAYMENT_THRESHOLD_DENOMINATOR":
|
||||
assert.Equal(t, "105", v)
|
||||
case "BLOB_SCHEDULE":
|
||||
blobSchedule, ok := v.([]any)
|
||||
assert.Equal(t, true, ok)
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
type writeOnlyGloasFields interface {
|
||||
SetBuilderPendingPayment(index uint64, payment *ethpb.BuilderPendingPayment) error
|
||||
SetExecutionPayloadBid(h interfaces.ROExecutionPayloadBid) error
|
||||
SetBuilderPendingPayment(index primitives.Slot, payment *ethpb.BuilderPendingPayment) error
|
||||
RotateBuilderPendingPayments() error
|
||||
AppendBuilderPendingWithdrawals([]*ethpb.BuilderPendingWithdrawal) error
|
||||
}
|
||||
|
||||
type readOnlyGloasFields interface {
|
||||
BuilderPendingPayment(index uint64) (*ethpb.BuilderPendingPayment, error)
|
||||
ExecutionPayloadAvailability(slot primitives.Slot) (uint64, error)
|
||||
BuilderPubkey(primitives.BuilderIndex) ([48]byte, error)
|
||||
IsActiveBuilder(primitives.BuilderIndex) (bool, error)
|
||||
CanBuilderCoverBid(primitives.BuilderIndex, primitives.Gwei) (bool, error)
|
||||
LatestBlockHash() ([32]byte, error)
|
||||
BuilderPendingPayments() ([]*ethpb.BuilderPendingPayment, error)
|
||||
}
|
||||
|
||||
@@ -1,42 +1,149 @@
|
||||
package state_native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
)
|
||||
|
||||
// BuilderPendingPayment returns the builder pending payment for the given index.
|
||||
func (b *BeaconState) BuilderPendingPayment(index uint64) (*ethpb.BuilderPendingPayment, error) {
|
||||
// LatestBlockHash returns the hash of the latest execution block.
|
||||
func (b *BeaconState) LatestBlockHash() ([32]byte, error) {
|
||||
if b.version < version.Gloas {
|
||||
return nil, errNotSupported("BuilderPendingPayment", b.version)
|
||||
return [32]byte{}, errNotSupported("LatestBlockHash", b.version)
|
||||
}
|
||||
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
return ethpb.CopyBuilderPendingPayment(b.builderPendingPayments[index]), nil
|
||||
if b.latestBlockHash == nil {
|
||||
return [32]byte{}, nil
|
||||
}
|
||||
|
||||
return [32]byte(b.latestBlockHash), nil
|
||||
}
|
||||
|
||||
// ExecutionPayloadAvailability returns the execution payload availability bit for the given slot.
|
||||
func (b *BeaconState) ExecutionPayloadAvailability(slot primitives.Slot) (uint64, error) {
|
||||
// BuilderPubkey returns the builder pubkey at the provided index.
|
||||
func (b *BeaconState) BuilderPubkey(builderIndex primitives.BuilderIndex) ([fieldparams.BLSPubkeyLength]byte, error) {
|
||||
if b.version < version.Gloas {
|
||||
return 0, errNotSupported("ExecutionPayloadAvailability", b.version)
|
||||
return [fieldparams.BLSPubkeyLength]byte{}, errNotSupported("BuilderPubkey", b.version)
|
||||
}
|
||||
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if b.executionPayloadAvailability == nil {
|
||||
return 0, nil
|
||||
builder, err := b.builderAtIndex(builderIndex)
|
||||
if err != nil {
|
||||
return [fieldparams.BLSPubkeyLength]byte{}, err
|
||||
}
|
||||
|
||||
slotIndex := slot % params.BeaconConfig().SlotsPerHistoricalRoot
|
||||
byteIndex := slotIndex / 8
|
||||
bitIndex := slotIndex % 8
|
||||
|
||||
bit := (b.executionPayloadAvailability[byteIndex] >> bitIndex) & 1
|
||||
|
||||
return uint64(bit), nil
|
||||
var pk [fieldparams.BLSPubkeyLength]byte
|
||||
copy(pk[:], builder.Pubkey)
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
// IsActiveBuilder returns true if the builder placement is finalized and it has not initiated exit.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// def is_active_builder(state: BeaconState, builder_index: BuilderIndex) -> bool:
|
||||
//
|
||||
// builder = state.builders[builder_index]
|
||||
// return (
|
||||
// builder.deposit_epoch < state.finalized_checkpoint.epoch
|
||||
// and builder.withdrawable_epoch == FAR_FUTURE_EPOCH
|
||||
// )
|
||||
func (b *BeaconState) IsActiveBuilder(builderIndex primitives.BuilderIndex) (bool, error) {
|
||||
if b.version < version.Gloas {
|
||||
return false, errNotSupported("IsActiveBuilder", b.version)
|
||||
}
|
||||
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
builder, err := b.builderAtIndex(builderIndex)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
finalizedEpoch := b.finalizedCheckpoint.Epoch
|
||||
return builder.DepositEpoch < finalizedEpoch && builder.WithdrawableEpoch == params.BeaconConfig().FarFutureEpoch, nil
|
||||
}
|
||||
|
||||
// CanBuilderCoverBid returns true if the builder has enough balance to cover the given bid amount.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// def can_builder_cover_bid(state: BeaconState, builder_index: BuilderIndex, bid_amount: Gwei) -> bool:
|
||||
//
|
||||
// builder_balance = state.builders[builder_index].balance
|
||||
// pending_withdrawals_amount = get_pending_balance_to_withdraw_for_builder(state, builder_index)
|
||||
// min_balance = MIN_DEPOSIT_AMOUNT + pending_withdrawals_amount
|
||||
// if builder_balance < min_balance:
|
||||
// return False
|
||||
// return builder_balance - min_balance >= bid_amount
|
||||
func (b *BeaconState) CanBuilderCoverBid(builderIndex primitives.BuilderIndex, bidAmount primitives.Gwei) (bool, error) {
|
||||
if b.version < version.Gloas {
|
||||
return false, errNotSupported("CanBuilderCoverBid", b.version)
|
||||
}
|
||||
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
builder, err := b.builderAtIndex(builderIndex)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
pendingBalanceToWithdraw := b.builderPendingBalanceToWithdraw(builderIndex)
|
||||
minBalance := params.BeaconConfig().MinDepositAmount + pendingBalanceToWithdraw
|
||||
|
||||
balance := uint64(builder.Balance)
|
||||
if balance < minBalance {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return balance-minBalance >= uint64(bidAmount), nil
|
||||
}
|
||||
|
||||
// builderAtIndex intentionally returns the underlying pointer without copying.
|
||||
func (b *BeaconState) builderAtIndex(builderIndex primitives.BuilderIndex) (*ethpb.Builder, error) {
|
||||
idx := uint64(builderIndex)
|
||||
if idx >= uint64(len(b.builders)) {
|
||||
return nil, fmt.Errorf("builder index %d out of range (len=%d)", builderIndex, len(b.builders))
|
||||
}
|
||||
|
||||
builder := b.builders[idx]
|
||||
if builder == nil {
|
||||
return nil, fmt.Errorf("builder at index %d is nil", builderIndex)
|
||||
}
|
||||
return builder, nil
|
||||
}
|
||||
|
||||
// builderPendingBalanceToWithdraw mirrors get_pending_balance_to_withdraw_for_builder in the spec,
|
||||
// summing both pending withdrawals and pending payments for a builder.
|
||||
func (b *BeaconState) builderPendingBalanceToWithdraw(builderIndex primitives.BuilderIndex) uint64 {
|
||||
var total uint64
|
||||
for _, withdrawal := range b.builderPendingWithdrawals {
|
||||
if withdrawal.BuilderIndex == builderIndex {
|
||||
total += uint64(withdrawal.Amount)
|
||||
}
|
||||
}
|
||||
for _, payment := range b.builderPendingPayments {
|
||||
if payment.Withdrawal.BuilderIndex == builderIndex {
|
||||
total += uint64(payment.Withdrawal.Amount)
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// BuilderPendingPayments returns a copy of the builder pending payments.
|
||||
func (b *BeaconState) BuilderPendingPayments() ([]*ethpb.BuilderPendingPayment, error) {
|
||||
if b.version < version.Gloas {
|
||||
return nil, errNotSupported("BuilderPendingPayments", b.version)
|
||||
}
|
||||
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
return b.builderPendingPaymentsVal(), nil
|
||||
}
|
||||
|
||||
@@ -1,38 +1,168 @@
|
||||
package state_native
|
||||
package state_native_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
)
|
||||
|
||||
func TestBuilderPendingPayment_ReturnsCopy(t *testing.T) {
|
||||
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||
payments := make([]*ethpb.BuilderPendingPayment, 2*slotsPerEpoch)
|
||||
target := uint64(slotsPerEpoch + 1)
|
||||
payments[target] = ðpb.BuilderPendingPayment{Weight: 10}
|
||||
|
||||
st, err := InitializeFromProtoUnsafeGloas(ðpb.BeaconStateGloas{
|
||||
BuilderPendingPayments: payments,
|
||||
func TestLatestBlockHash(t *testing.T) {
|
||||
t.Run("returns error before gloas", func(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisState(t, 1)
|
||||
_, err := st.LatestBlockHash()
|
||||
require.ErrorContains(t, "is not supported", err)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
payment, err := st.BuilderPendingPayment(target)
|
||||
require.NoError(t, err)
|
||||
t.Run("returns zero hash when unset", func(t *testing.T) {
|
||||
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// mutate returned copy
|
||||
payment.Weight = 99
|
||||
got, err := st.LatestBlockHash()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [32]byte{}, got)
|
||||
})
|
||||
|
||||
original, err := st.BuilderPendingPayment(target)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(10), uint64(original.Weight))
|
||||
t.Run("returns configured hash", func(t *testing.T) {
|
||||
hashBytes := bytes.Repeat([]byte{0xAB}, 32)
|
||||
var want [32]byte
|
||||
copy(want[:], hashBytes)
|
||||
|
||||
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
LatestBlockHash: hashBytes,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := st.LatestBlockHash()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderPendingPayment_UnsupportedVersion(t *testing.T) {
|
||||
st := &BeaconState{version: version.Electra}
|
||||
_, err := st.BuilderPendingPayment(0)
|
||||
require.ErrorContains(t, "BuilderPendingPayment", err)
|
||||
func TestBuilderPubkey(t *testing.T) {
|
||||
t.Run("returns error before gloas", func(t *testing.T) {
|
||||
stIface, _ := util.DeterministicGenesisState(t, 1)
|
||||
native, ok := stIface.(*state_native.BeaconState)
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
_, err := native.BuilderPubkey(0)
|
||||
require.ErrorContains(t, "is not supported", err)
|
||||
})
|
||||
|
||||
t.Run("returns pubkey copy", func(t *testing.T) {
|
||||
pubkey := bytes.Repeat([]byte{0xAA}, 48)
|
||||
stIface, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
Builders: []*ethpb.Builder{
|
||||
{
|
||||
Pubkey: pubkey,
|
||||
Balance: 42,
|
||||
DepositEpoch: 3,
|
||||
WithdrawableEpoch: 4,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
gotPk, err := stIface.BuilderPubkey(0)
|
||||
require.NoError(t, err)
|
||||
var wantPk [48]byte
|
||||
copy(wantPk[:], pubkey)
|
||||
require.Equal(t, wantPk, gotPk)
|
||||
|
||||
// Mutate original to ensure copy.
|
||||
pubkey[0] = 0
|
||||
require.Equal(t, byte(0xAA), gotPk[0])
|
||||
})
|
||||
|
||||
t.Run("out of range returns error", func(t *testing.T) {
|
||||
stIface, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
Builders: []*ethpb.Builder{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
st := stIface.(*state_native.BeaconState)
|
||||
_, err = st.BuilderPubkey(1)
|
||||
require.ErrorContains(t, "out of range", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderHelpers(t *testing.T) {
|
||||
t.Run("is active builder", func(t *testing.T) {
|
||||
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
Builders: []*ethpb.Builder{
|
||||
{
|
||||
Balance: 10,
|
||||
DepositEpoch: 0,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
},
|
||||
},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Epoch: 1},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
active, err := st.IsActiveBuilder(0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, active)
|
||||
|
||||
// Not active when withdrawable epoch is set.
|
||||
stProto := ðpb.BeaconStateGloas{
|
||||
Builders: []*ethpb.Builder{
|
||||
{
|
||||
Balance: 10,
|
||||
DepositEpoch: 0,
|
||||
WithdrawableEpoch: 1,
|
||||
},
|
||||
},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Epoch: 2},
|
||||
}
|
||||
stInactive, err := state_native.InitializeFromProtoGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
active, err = stInactive.IsActiveBuilder(0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, active)
|
||||
})
|
||||
|
||||
t.Run("can builder cover bid", func(t *testing.T) {
|
||||
stIface, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
Builders: []*ethpb.Builder{
|
||||
{
|
||||
Balance: primitives.Gwei(params.BeaconConfig().MinDepositAmount + 50),
|
||||
DepositEpoch: 0,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
},
|
||||
},
|
||||
BuilderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{
|
||||
{Amount: 10, BuilderIndex: 0},
|
||||
},
|
||||
BuilderPendingPayments: []*ethpb.BuilderPendingPayment{
|
||||
{Withdrawal: ðpb.BuilderPendingWithdrawal{Amount: 15, BuilderIndex: 0}},
|
||||
},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Epoch: 1},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
st := stIface.(*state_native.BeaconState)
|
||||
ok, err := st.CanBuilderCoverBid(0, 20)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
ok, err = st.CanBuilderCoverBid(0, 30)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderPendingPayments_UnsupportedVersion(t *testing.T) {
|
||||
stIface, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{})
|
||||
require.NoError(t, err)
|
||||
st := stIface.(*state_native.BeaconState)
|
||||
|
||||
_, err = st.BuilderPendingPayments()
|
||||
require.ErrorContains(t, "BuilderPendingPayments", err)
|
||||
}
|
||||
|
||||
@@ -725,3 +725,13 @@ func ProtobufBeaconStateFulu(s any) (*ethpb.BeaconStateFulu, error) {
|
||||
}
|
||||
return pbState, nil
|
||||
}
|
||||
|
||||
// ProtobufBeaconStateGloas transforms an input into beacon state Gloas in the form of protobuf.
|
||||
// Error is returned if the input is not type protobuf beacon state.
|
||||
func ProtobufBeaconStateGloas(s any) (*ethpb.BeaconStateGloas, error) {
|
||||
pbState, ok := s.(*ethpb.BeaconStateGloas)
|
||||
if !ok {
|
||||
return nil, errors.New("input is not type pb.BeaconStateGloas")
|
||||
}
|
||||
return pbState, nil
|
||||
}
|
||||
|
||||
@@ -1,13 +1,113 @@
|
||||
package state_native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native/types"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/stateutil"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
)
|
||||
|
||||
// SetBuilderPendingPayment sets a builder pending payment at the specified slot.
|
||||
func (b *BeaconState) SetBuilderPendingPayment(index uint64, payment *ethpb.BuilderPendingPayment) error {
|
||||
// RotateBuilderPendingPayments rotates the queue by dropping slots per epoch payments from the
|
||||
// front and appending slots per epoch empty payments to the end.
|
||||
// This implements: state.builder_pending_payments = state.builder_pending_payments[SLOTS_PER_EPOCH:] + [BuilderPendingPayment() for _ in range(SLOTS_PER_EPOCH)]
|
||||
func (b *BeaconState) RotateBuilderPendingPayments() error {
|
||||
if b.version < version.Gloas {
|
||||
return errNotSupported("RotateBuilderPendingPayments", b.version)
|
||||
}
|
||||
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||
copy(b.builderPendingPayments[:slotsPerEpoch], b.builderPendingPayments[slotsPerEpoch:2*slotsPerEpoch])
|
||||
|
||||
for i := slotsPerEpoch; i < primitives.Slot(len(b.builderPendingPayments)); i++ {
|
||||
b.builderPendingPayments[i] = emptyPayment()
|
||||
}
|
||||
|
||||
b.markFieldAsDirty(types.BuilderPendingPayments)
|
||||
b.rebuildTrie[types.BuilderPendingPayments] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// AppendBuilderPendingWithdrawals appends builder pending withdrawals to the beacon state.
|
||||
// If the withdrawals slice is shared, it copies the slice first to preserve references.
|
||||
func (b *BeaconState) AppendBuilderPendingWithdrawals(withdrawals []*ethpb.BuilderPendingWithdrawal) error {
|
||||
if b.version < version.Gloas {
|
||||
return errNotSupported("AppendBuilderPendingWithdrawals", b.version)
|
||||
}
|
||||
|
||||
if len(withdrawals) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
pendingWithdrawals := b.builderPendingWithdrawals
|
||||
if b.sharedFieldReferences[types.BuilderPendingWithdrawals].Refs() > 1 {
|
||||
pendingWithdrawals = make([]*ethpb.BuilderPendingWithdrawal, 0, len(b.builderPendingWithdrawals)+len(withdrawals))
|
||||
pendingWithdrawals = append(pendingWithdrawals, b.builderPendingWithdrawals...)
|
||||
b.sharedFieldReferences[types.BuilderPendingWithdrawals].MinusRef()
|
||||
b.sharedFieldReferences[types.BuilderPendingWithdrawals] = stateutil.NewRef(1)
|
||||
}
|
||||
|
||||
b.builderPendingWithdrawals = append(pendingWithdrawals, withdrawals...)
|
||||
b.markFieldAsDirty(types.BuilderPendingWithdrawals)
|
||||
return nil
|
||||
}
|
||||
|
||||
func emptyPayment() *ethpb.BuilderPendingPayment {
|
||||
return ðpb.BuilderPendingPayment{
|
||||
Weight: 0,
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
Amount: 0,
|
||||
BuilderIndex: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetExecutionPayloadBid sets the latest execution payload bid in the state.
|
||||
func (b *BeaconState) SetExecutionPayloadBid(h interfaces.ROExecutionPayloadBid) error {
|
||||
if b.version < version.Gloas {
|
||||
return errNotSupported("SetExecutionPayloadBid", b.version)
|
||||
}
|
||||
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
parentBlockHash := h.ParentBlockHash()
|
||||
parentBlockRoot := h.ParentBlockRoot()
|
||||
blockHash := h.BlockHash()
|
||||
randao := h.PrevRandao()
|
||||
blobKzgCommitmentsRoot := h.BlobKzgCommitmentsRoot()
|
||||
feeRecipient := h.FeeRecipient()
|
||||
b.latestExecutionPayloadBid = ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
ParentBlockRoot: parentBlockRoot[:],
|
||||
BlockHash: blockHash[:],
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: h.GasLimit(),
|
||||
BuilderIndex: h.BuilderIndex(),
|
||||
Slot: h.Slot(),
|
||||
Value: h.Value(),
|
||||
ExecutionPayment: h.ExecutionPayment(),
|
||||
BlobKzgCommitmentsRoot: blobKzgCommitmentsRoot[:],
|
||||
FeeRecipient: feeRecipient[:],
|
||||
}
|
||||
b.markFieldAsDirty(types.LatestExecutionPayloadBid)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBuilderPendingPayment sets a builder pending payment at the specified index.
|
||||
func (b *BeaconState) SetBuilderPendingPayment(index primitives.Slot, payment *ethpb.BuilderPendingPayment) error {
|
||||
if b.version < version.Gloas {
|
||||
return errNotSupported("SetBuilderPendingPayment", b.version)
|
||||
}
|
||||
@@ -15,6 +115,10 @@ func (b *BeaconState) SetBuilderPendingPayment(index uint64, payment *ethpb.Buil
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
if uint64(index) >= uint64(len(b.builderPendingPayments)) {
|
||||
return fmt.Errorf("builder pending payments index %d out of range (len=%d)", index, len(b.builderPendingPayments))
|
||||
}
|
||||
|
||||
b.builderPendingPayments[index] = ethpb.CopyBuilderPendingPayment(payment)
|
||||
|
||||
b.markFieldAsDirty(types.BuilderPendingPayments)
|
||||
|
||||
@@ -1,35 +1,249 @@
|
||||
package state_native
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native/types"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/stateutil"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func TestSetBuilderPendingPayment_CopiesValue(t *testing.T) {
|
||||
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||
payments := make([]*ethpb.BuilderPendingPayment, 2*slotsPerEpoch)
|
||||
st, err := InitializeFromProtoUnsafeGloas(ðpb.BeaconStateGloas{
|
||||
type testExecutionPayloadBid struct {
|
||||
parentBlockHash [32]byte
|
||||
parentBlockRoot [32]byte
|
||||
blockHash [32]byte
|
||||
prevRandao [32]byte
|
||||
blobKzgCommitmentsRoot [32]byte
|
||||
feeRecipient [20]byte
|
||||
gasLimit uint64
|
||||
builderIndex primitives.BuilderIndex
|
||||
slot primitives.Slot
|
||||
value primitives.Gwei
|
||||
executionPayment primitives.Gwei
|
||||
}
|
||||
|
||||
func (t testExecutionPayloadBid) ParentBlockHash() [32]byte { return t.parentBlockHash }
|
||||
func (t testExecutionPayloadBid) ParentBlockRoot() [32]byte { return t.parentBlockRoot }
|
||||
func (t testExecutionPayloadBid) PrevRandao() [32]byte { return t.prevRandao }
|
||||
func (t testExecutionPayloadBid) BlockHash() [32]byte { return t.blockHash }
|
||||
func (t testExecutionPayloadBid) GasLimit() uint64 { return t.gasLimit }
|
||||
func (t testExecutionPayloadBid) BuilderIndex() primitives.BuilderIndex {
|
||||
return t.builderIndex
|
||||
}
|
||||
func (t testExecutionPayloadBid) Slot() primitives.Slot { return t.slot }
|
||||
func (t testExecutionPayloadBid) Value() primitives.Gwei { return t.value }
|
||||
func (t testExecutionPayloadBid) ExecutionPayment() primitives.Gwei {
|
||||
return t.executionPayment
|
||||
}
|
||||
func (t testExecutionPayloadBid) BlobKzgCommitmentsRoot() [32]byte { return t.blobKzgCommitmentsRoot }
|
||||
func (t testExecutionPayloadBid) FeeRecipient() [20]byte { return t.feeRecipient }
|
||||
func (t testExecutionPayloadBid) IsNil() bool { return false }
|
||||
|
||||
func TestSetExecutionPayloadBid(t *testing.T) {
|
||||
t.Run("previous fork returns expected error", func(t *testing.T) {
|
||||
st := &BeaconState{version: version.Fulu}
|
||||
err := st.SetExecutionPayloadBid(testExecutionPayloadBid{})
|
||||
require.ErrorContains(t, "is not supported", err)
|
||||
})
|
||||
|
||||
t.Run("sets bid and marks dirty", func(t *testing.T) {
|
||||
var (
|
||||
parentBlockHash = [32]byte(bytes.Repeat([]byte{0xAB}, 32))
|
||||
parentBlockRoot = [32]byte(bytes.Repeat([]byte{0xCD}, 32))
|
||||
blockHash = [32]byte(bytes.Repeat([]byte{0xEF}, 32))
|
||||
prevRandao = [32]byte(bytes.Repeat([]byte{0x11}, 32))
|
||||
blobRoot = [32]byte(bytes.Repeat([]byte{0x22}, 32))
|
||||
feeRecipient [20]byte
|
||||
)
|
||||
copy(feeRecipient[:], bytes.Repeat([]byte{0x33}, len(feeRecipient)))
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
}
|
||||
bid := testExecutionPayloadBid{
|
||||
parentBlockHash: parentBlockHash,
|
||||
parentBlockRoot: parentBlockRoot,
|
||||
blockHash: blockHash,
|
||||
prevRandao: prevRandao,
|
||||
blobKzgCommitmentsRoot: blobRoot,
|
||||
feeRecipient: feeRecipient,
|
||||
gasLimit: 123,
|
||||
builderIndex: 7,
|
||||
slot: 9,
|
||||
value: 11,
|
||||
executionPayment: 22,
|
||||
}
|
||||
|
||||
require.NoError(t, st.SetExecutionPayloadBid(bid))
|
||||
|
||||
require.NotNil(t, st.latestExecutionPayloadBid)
|
||||
require.DeepEqual(t, parentBlockHash[:], st.latestExecutionPayloadBid.ParentBlockHash)
|
||||
require.DeepEqual(t, parentBlockRoot[:], st.latestExecutionPayloadBid.ParentBlockRoot)
|
||||
require.DeepEqual(t, blockHash[:], st.latestExecutionPayloadBid.BlockHash)
|
||||
require.DeepEqual(t, prevRandao[:], st.latestExecutionPayloadBid.PrevRandao)
|
||||
require.DeepEqual(t, blobRoot[:], st.latestExecutionPayloadBid.BlobKzgCommitmentsRoot)
|
||||
require.DeepEqual(t, feeRecipient[:], st.latestExecutionPayloadBid.FeeRecipient)
|
||||
require.Equal(t, uint64(123), st.latestExecutionPayloadBid.GasLimit)
|
||||
require.Equal(t, primitives.BuilderIndex(7), st.latestExecutionPayloadBid.BuilderIndex)
|
||||
require.Equal(t, primitives.Slot(9), st.latestExecutionPayloadBid.Slot)
|
||||
require.Equal(t, primitives.Gwei(11), st.latestExecutionPayloadBid.Value)
|
||||
require.Equal(t, primitives.Gwei(22), st.latestExecutionPayloadBid.ExecutionPayment)
|
||||
require.Equal(t, true, st.dirtyFields[types.LatestExecutionPayloadBid])
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetBuilderPendingPayment(t *testing.T) {
|
||||
t.Run("previous fork returns expected error", func(t *testing.T) {
|
||||
st := &BeaconState{version: version.Fulu}
|
||||
err := st.SetBuilderPendingPayment(0, ðpb.BuilderPendingPayment{})
|
||||
require.ErrorContains(t, "is not supported", err)
|
||||
})
|
||||
|
||||
t.Run("sets copy and marks dirty", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
builderPendingPayments: make([]*ethpb.BuilderPendingPayment, 2),
|
||||
}
|
||||
payment := ðpb.BuilderPendingPayment{
|
||||
Weight: 2,
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
Amount: 99,
|
||||
BuilderIndex: 1,
|
||||
},
|
||||
}
|
||||
|
||||
require.NoError(t, st.SetBuilderPendingPayment(1, payment))
|
||||
require.DeepEqual(t, payment, st.builderPendingPayments[1])
|
||||
require.Equal(t, true, st.dirtyFields[types.BuilderPendingPayments])
|
||||
|
||||
// Mutating the original should not affect the state copy.
|
||||
payment.Withdrawal.Amount = 12345
|
||||
require.Equal(t, primitives.Gwei(99), st.builderPendingPayments[1].Withdrawal.Amount)
|
||||
})
|
||||
|
||||
t.Run("returns error on out of range index", func(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
builderPendingPayments: make([]*ethpb.BuilderPendingPayment, 1),
|
||||
}
|
||||
|
||||
err := st.SetBuilderPendingPayment(2, ðpb.BuilderPendingPayment{})
|
||||
|
||||
require.ErrorContains(t, "out of range", err)
|
||||
require.Equal(t, false, st.dirtyFields[types.BuilderPendingPayments])
|
||||
})
|
||||
}
|
||||
|
||||
func TestRotateBuilderPendingPayments(t *testing.T) {
|
||||
totalPayments := 2 * params.BeaconConfig().SlotsPerEpoch
|
||||
payments := make([]*ethpb.BuilderPendingPayment, totalPayments)
|
||||
for i := range payments {
|
||||
idx := uint64(i)
|
||||
payments[i] = ðpb.BuilderPendingPayment{
|
||||
Weight: primitives.Gwei(idx * 100e9),
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
Amount: primitives.Gwei(idx * 1e9),
|
||||
BuilderIndex: primitives.BuilderIndex(idx + 100),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
statePb, err := InitializeFromProtoUnsafeGloas(ðpb.BeaconStateGloas{
|
||||
BuilderPendingPayments: payments,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
st, ok := statePb.(*BeaconState)
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
payment := ðpb.BuilderPendingPayment{Weight: 123}
|
||||
target := uint64(1)
|
||||
require.NoError(t, st.SetBuilderPendingPayment(target, payment))
|
||||
|
||||
payment.Weight = 999
|
||||
|
||||
got, err := st.BuilderPendingPayment(target)
|
||||
oldPayments, err := st.BuilderPendingPayments()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(123), uint64(got.Weight))
|
||||
require.NoError(t, st.RotateBuilderPendingPayments())
|
||||
|
||||
newPayments, err := st.BuilderPendingPayments()
|
||||
require.NoError(t, err)
|
||||
slotsPerEpoch := int(params.BeaconConfig().SlotsPerEpoch)
|
||||
for i := range slotsPerEpoch {
|
||||
require.DeepEqual(t, oldPayments[slotsPerEpoch+i], newPayments[i])
|
||||
}
|
||||
|
||||
for i := slotsPerEpoch; i < 2*slotsPerEpoch; i++ {
|
||||
payment := newPayments[i]
|
||||
require.Equal(t, primitives.Gwei(0), payment.Weight)
|
||||
require.Equal(t, 20, len(payment.Withdrawal.FeeRecipient))
|
||||
require.Equal(t, primitives.Gwei(0), payment.Withdrawal.Amount)
|
||||
require.Equal(t, primitives.BuilderIndex(0), payment.Withdrawal.BuilderIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetBuilderPendingPayment_UnsupportedVersion(t *testing.T) {
|
||||
func TestRotateBuilderPendingPayments_UnsupportedVersion(t *testing.T) {
|
||||
st := &BeaconState{version: version.Electra}
|
||||
err := st.SetBuilderPendingPayment(0, ðpb.BuilderPendingPayment{})
|
||||
require.ErrorContains(t, "SetBuilderPendingPayment", err)
|
||||
err := st.RotateBuilderPendingPayments()
|
||||
require.ErrorContains(t, "RotateBuilderPendingPayments", err)
|
||||
}
|
||||
|
||||
func TestAppendBuilderPendingWithdrawal_CopyOnWrite(t *testing.T) {
|
||||
wd := ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
Amount: 1,
|
||||
BuilderIndex: 2,
|
||||
}
|
||||
statePb, err := InitializeFromProtoUnsafeGloas(ðpb.BeaconStateGloas{
|
||||
BuilderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{wd},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
st, ok := statePb.(*BeaconState)
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
copied := st.Copy().(*BeaconState)
|
||||
require.Equal(t, uint(2), st.sharedFieldReferences[types.BuilderPendingWithdrawals].Refs())
|
||||
|
||||
appended := ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
Amount: 4,
|
||||
BuilderIndex: 5,
|
||||
}
|
||||
require.NoError(t, copied.AppendBuilderPendingWithdrawals([]*ethpb.BuilderPendingWithdrawal{appended}))
|
||||
|
||||
require.Equal(t, 1, len(st.builderPendingWithdrawals))
|
||||
require.Equal(t, 2, len(copied.builderPendingWithdrawals))
|
||||
require.DeepEqual(t, wd, copied.builderPendingWithdrawals[0])
|
||||
require.DeepEqual(t, appended, copied.builderPendingWithdrawals[1])
|
||||
require.DeepEqual(t, wd, st.builderPendingWithdrawals[0])
|
||||
require.Equal(t, uint(1), st.sharedFieldReferences[types.BuilderPendingWithdrawals].Refs())
|
||||
require.Equal(t, uint(1), copied.sharedFieldReferences[types.BuilderPendingWithdrawals].Refs())
|
||||
}
|
||||
|
||||
func TestAppendBuilderPendingWithdrawals(t *testing.T) {
|
||||
st := &BeaconState{
|
||||
version: version.Gloas,
|
||||
dirtyFields: make(map[types.FieldIndex]bool),
|
||||
sharedFieldReferences: map[types.FieldIndex]*stateutil.Reference{
|
||||
types.BuilderPendingWithdrawals: stateutil.NewRef(1),
|
||||
},
|
||||
builderPendingWithdrawals: make([]*ethpb.BuilderPendingWithdrawal, 0),
|
||||
}
|
||||
|
||||
first := ðpb.BuilderPendingWithdrawal{Amount: 1}
|
||||
second := ðpb.BuilderPendingWithdrawal{Amount: 2}
|
||||
require.NoError(t, st.AppendBuilderPendingWithdrawals([]*ethpb.BuilderPendingWithdrawal{first, second}))
|
||||
|
||||
require.Equal(t, 2, len(st.builderPendingWithdrawals))
|
||||
require.DeepEqual(t, first, st.builderPendingWithdrawals[0])
|
||||
require.DeepEqual(t, second, st.builderPendingWithdrawals[1])
|
||||
require.Equal(t, true, st.dirtyFields[types.BuilderPendingWithdrawals])
|
||||
}
|
||||
|
||||
func TestAppendBuilderPendingWithdrawals_UnsupportedVersion(t *testing.T) {
|
||||
st := &BeaconState{version: version.Electra}
|
||||
err := st.AppendBuilderPendingWithdrawals([]*ethpb.BuilderPendingWithdrawal{{}})
|
||||
require.ErrorContains(t, "AppendBuilderPendingWithdrawals", err)
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ func (b batch) ensureParent(expected [32]byte) error {
|
||||
func (b batch) blockRequest() *eth.BeaconBlocksByRangeRequest {
|
||||
return ð.BeaconBlocksByRangeRequest{
|
||||
StartSlot: b.begin,
|
||||
Count: uint64(b.end - b.begin),
|
||||
Count: uint64(b.end.FlooredSubSlot(b.begin)),
|
||||
Step: 1,
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func (b batch) blockRequest() *eth.BeaconBlocksByRangeRequest {
|
||||
func (b batch) blobRequest() *eth.BlobSidecarsByRangeRequest {
|
||||
return ð.BlobSidecarsByRangeRequest{
|
||||
StartSlot: b.begin,
|
||||
Count: uint64(b.end - b.begin),
|
||||
Count: uint64(b.end.FlooredSubSlot(b.begin)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,93 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestBlockRequest(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
begin primitives.Slot
|
||||
end primitives.Slot
|
||||
expectedCount uint64
|
||||
}{
|
||||
{
|
||||
name: "normal case",
|
||||
begin: 100,
|
||||
end: 200,
|
||||
expectedCount: 100,
|
||||
},
|
||||
{
|
||||
name: "end equals begin",
|
||||
begin: 100,
|
||||
end: 100,
|
||||
expectedCount: 0,
|
||||
},
|
||||
{
|
||||
name: "end less than begin (would underflow without check)",
|
||||
begin: 200,
|
||||
end: 100,
|
||||
expectedCount: 0,
|
||||
},
|
||||
{
|
||||
name: "zero values",
|
||||
begin: 0,
|
||||
end: 0,
|
||||
expectedCount: 0,
|
||||
},
|
||||
{
|
||||
name: "single slot",
|
||||
begin: 0,
|
||||
end: 1,
|
||||
expectedCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
b := batch{begin: tc.begin, end: tc.end}
|
||||
req := b.blockRequest()
|
||||
require.Equal(t, tc.expectedCount, req.Count)
|
||||
require.Equal(t, tc.begin, req.StartSlot)
|
||||
require.Equal(t, uint64(1), req.Step)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlobRequest(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
begin primitives.Slot
|
||||
end primitives.Slot
|
||||
expectedCount uint64
|
||||
}{
|
||||
{
|
||||
name: "normal case",
|
||||
begin: 100,
|
||||
end: 200,
|
||||
expectedCount: 100,
|
||||
},
|
||||
{
|
||||
name: "end equals begin",
|
||||
begin: 100,
|
||||
end: 100,
|
||||
expectedCount: 0,
|
||||
},
|
||||
{
|
||||
name: "end less than begin (would underflow without check)",
|
||||
begin: 200,
|
||||
end: 100,
|
||||
expectedCount: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
b := batch{begin: tc.begin, end: tc.end}
|
||||
req := b.blobRequest()
|
||||
require.Equal(t, tc.expectedCount, req.Count)
|
||||
require.Equal(t, tc.begin, req.StartSlot)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortBatchDesc(t *testing.T) {
|
||||
orderIn := []primitives.Slot{100, 10000, 1}
|
||||
orderOut := []primitives.Slot{10000, 100, 1}
|
||||
|
||||
@@ -4,9 +4,6 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/verification"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||
"github.com/OffchainLabs/prysm/v7/monitoring/tracing"
|
||||
@@ -56,32 +53,6 @@ func (s *Service) verifierRoutine() {
|
||||
}
|
||||
}
|
||||
|
||||
// A routine that runs in the background to perform batch
|
||||
// KZG verifications by draining the channel and processing all pending requests.
|
||||
func (s *Service) kzgVerifierRoutine() {
|
||||
for {
|
||||
kzgBatch := make([]*kzgVerifier, 0, 1)
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
case kzg := <-s.kzgChan:
|
||||
kzgBatch = append(kzgBatch, kzg)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
case kzg := <-s.kzgChan:
|
||||
kzgBatch = append(kzgBatch, kzg)
|
||||
continue
|
||||
default:
|
||||
verifyKzgBatch(kzgBatch)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) validateWithBatchVerifier(ctx context.Context, message string, set *bls.SignatureBatch) (pubsub.ValidationResult, error) {
|
||||
_, span := trace.StartSpan(ctx, "sync.validateWithBatchVerifier")
|
||||
defer span.End()
|
||||
@@ -154,71 +125,3 @@ func performBatchAggregation(aggSet *bls.SignatureBatch) (*bls.SignatureBatch, e
|
||||
}
|
||||
return aggSet, nil
|
||||
}
|
||||
|
||||
func (s *Service) validateWithKzgBatchVerifier(ctx context.Context, dataColumns []blocks.RODataColumn) (pubsub.ValidationResult, error) {
|
||||
_, span := trace.StartSpan(ctx, "sync.validateWithKzgBatchVerifier")
|
||||
defer span.End()
|
||||
|
||||
timeout := time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second
|
||||
|
||||
resChan := make(chan error, 1)
|
||||
verificationSet := &kzgVerifier{dataColumns: dataColumns, resChan: resChan}
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
select {
|
||||
case s.kzgChan <- verificationSet:
|
||||
case <-ctx.Done():
|
||||
return pubsub.ValidationIgnore, ctx.Err()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return pubsub.ValidationIgnore, ctx.Err() // parent context canceled, give up
|
||||
case err := <-resChan:
|
||||
if err != nil {
|
||||
log.WithError(err).Trace("Could not perform batch verification")
|
||||
tracing.AnnotateError(span, err)
|
||||
return s.validateUnbatchedColumnsKzg(ctx, dataColumns)
|
||||
}
|
||||
}
|
||||
return pubsub.ValidationAccept, nil
|
||||
}
|
||||
|
||||
func (s *Service) validateUnbatchedColumnsKzg(ctx context.Context, columns []blocks.RODataColumn) (pubsub.ValidationResult, error) {
|
||||
_, span := trace.StartSpan(ctx, "sync.validateUnbatchedColumnsKzg")
|
||||
defer span.End()
|
||||
start := time.Now()
|
||||
if err := peerdas.VerifyDataColumnsSidecarKZGProofs(columns); err != nil {
|
||||
err = errors.Wrap(err, "could not verify")
|
||||
tracing.AnnotateError(span, err)
|
||||
return pubsub.ValidationReject, err
|
||||
}
|
||||
verification.DataColumnBatchKZGVerificationHistogram.WithLabelValues("fallback").Observe(float64(time.Since(start).Milliseconds()))
|
||||
return pubsub.ValidationAccept, nil
|
||||
}
|
||||
|
||||
func verifyKzgBatch(kzgBatch []*kzgVerifier) {
|
||||
if len(kzgBatch) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
allDataColumns := make([]blocks.RODataColumn, 0, len(kzgBatch))
|
||||
for _, kzgVerifier := range kzgBatch {
|
||||
allDataColumns = append(allDataColumns, kzgVerifier.dataColumns...)
|
||||
}
|
||||
|
||||
var verificationErr error
|
||||
start := time.Now()
|
||||
err := peerdas.VerifyDataColumnsSidecarKZGProofs(allDataColumns)
|
||||
if err != nil {
|
||||
verificationErr = errors.Wrap(err, "batch KZG verification failed")
|
||||
} else {
|
||||
verification.DataColumnBatchKZGVerificationHistogram.WithLabelValues("batch").Observe(float64(time.Since(start).Milliseconds()))
|
||||
}
|
||||
|
||||
// Send the same result to all verifiers in the batch
|
||||
for _, verifier := range kzgBatch {
|
||||
verifier.resChan <- verificationErr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +668,7 @@ func populateBlock(bw *blocks.BlockWithROSidecars, blobs []blocks.ROBlob, req *p
|
||||
|
||||
func missingCommitError(root [32]byte, slot primitives.Slot, missing [][]byte) error {
|
||||
missStr := make([]string, 0, len(missing))
|
||||
for k := range missing {
|
||||
for _, k := range missing {
|
||||
missStr = append(missStr, fmt.Sprintf("%#x", k))
|
||||
}
|
||||
return errors.Wrapf(errMissingBlobsForBlockCommitments,
|
||||
|
||||
@@ -226,8 +226,6 @@ func (s *Service) Start() {
|
||||
|
||||
// fetchOriginSidecars fetches origin sidecars
|
||||
func (s *Service) fetchOriginSidecars(peers []peer.ID) error {
|
||||
const delay = 10 * time.Second // The delay between each attempt to fetch origin data column sidecars
|
||||
|
||||
blockRoot, err := s.cfg.DB.OriginCheckpointBlockRoot(s.ctx)
|
||||
if errors.Is(err, db.ErrNotFoundOriginBlockRoot) {
|
||||
return nil
|
||||
@@ -260,7 +258,7 @@ func (s *Service) fetchOriginSidecars(peers []peer.ID) error {
|
||||
blockVersion := roBlock.Version()
|
||||
|
||||
if blockVersion >= version.Fulu {
|
||||
if err := s.fetchOriginDataColumnSidecars(roBlock, delay); err != nil {
|
||||
if err := s.fetchOriginDataColumnSidecars(roBlock); err != nil {
|
||||
return errors.Wrap(err, "fetch origin columns")
|
||||
}
|
||||
return nil
|
||||
@@ -414,7 +412,7 @@ func (s *Service) fetchOriginBlobSidecars(pids []peer.ID, rob blocks.ROBlock) er
|
||||
return fmt.Errorf("no connected peer able to provide blobs for checkpoint sync block %#x", r)
|
||||
}
|
||||
|
||||
func (s *Service) fetchOriginDataColumnSidecars(roBlock blocks.ROBlock, delay time.Duration) error {
|
||||
func (s *Service) fetchOriginDataColumnSidecars(roBlock blocks.ROBlock) error {
|
||||
const (
|
||||
errorMessage = "Failed to fetch origin data column sidecars"
|
||||
warningIteration = 10
|
||||
@@ -501,7 +499,6 @@ func (s *Service) fetchOriginDataColumnSidecars(roBlock blocks.ROBlock, delay ti
|
||||
log := log.WithFields(logrus.Fields{
|
||||
"attempt": attempt,
|
||||
"missingIndices": helpers.SortedPrettySliceFromMap(missingIndicesByRoot[root]),
|
||||
"delay": delay,
|
||||
})
|
||||
|
||||
logFunc := log.Debug
|
||||
|
||||
@@ -687,10 +687,7 @@ func TestFetchOriginColumns(t *testing.T) {
|
||||
cfg.BlobSchedule = []params.BlobScheduleEntry{{Epoch: 0, MaxBlobsPerBlock: 10}}
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
const (
|
||||
delay = 0
|
||||
blobCount = 1
|
||||
)
|
||||
const blobCount = 1
|
||||
|
||||
t.Run("block has no commitments", func(t *testing.T) {
|
||||
service := new(Service)
|
||||
@@ -702,7 +699,7 @@ func TestFetchOriginColumns(t *testing.T) {
|
||||
roBlock, err := blocks.NewROBlock(signedBlock)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = service.fetchOriginDataColumnSidecars(roBlock, delay)
|
||||
err = service.fetchOriginDataColumnSidecars(roBlock)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
@@ -724,7 +721,7 @@ func TestFetchOriginColumns(t *testing.T) {
|
||||
err := storage.Save(verifiedSidecars)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = service.fetchOriginDataColumnSidecars(roBlock, delay)
|
||||
err = service.fetchOriginDataColumnSidecars(roBlock)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
@@ -829,7 +826,7 @@ func TestFetchOriginColumns(t *testing.T) {
|
||||
attempt++
|
||||
})
|
||||
|
||||
err = service.fetchOriginDataColumnSidecars(roBlock, delay)
|
||||
err = service.fetchOriginDataColumnSidecars(roBlock)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check all corresponding sidecars are saved in the store.
|
||||
|
||||
@@ -1,339 +1,14 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
)
|
||||
|
||||
func TestValidateWithKzgBatchVerifier(t *testing.T) {
|
||||
err := kzg.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dataColumns []blocks.RODataColumn
|
||||
expectedResult pubsub.ValidationResult
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "single valid data column",
|
||||
dataColumns: createValidTestDataColumns(t, 1),
|
||||
expectedResult: pubsub.ValidationAccept,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "multiple valid data columns",
|
||||
dataColumns: createValidTestDataColumns(t, 3),
|
||||
expectedResult: pubsub.ValidationAccept,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "single invalid data column",
|
||||
dataColumns: createInvalidTestDataColumns(t, 1),
|
||||
expectedResult: pubsub.ValidationReject,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "empty data column slice",
|
||||
dataColumns: []blocks.RODataColumn{},
|
||||
expectedResult: pubsub.ValidationAccept,
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
service := &Service{
|
||||
ctx: ctx,
|
||||
kzgChan: make(chan *kzgVerifier, 100),
|
||||
}
|
||||
go service.kzgVerifierRoutine()
|
||||
|
||||
result, err := service.validateWithKzgBatchVerifier(ctx, tt.dataColumns)
|
||||
|
||||
require.Equal(t, tt.expectedResult, result)
|
||||
if tt.expectError {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifierRoutine(t *testing.T) {
|
||||
err := kzg.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("processes single request", func(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
service := &Service{
|
||||
ctx: ctx,
|
||||
kzgChan: make(chan *kzgVerifier, 100),
|
||||
}
|
||||
go service.kzgVerifierRoutine()
|
||||
|
||||
dataColumns := createValidTestDataColumns(t, 1)
|
||||
resChan := make(chan error, 1)
|
||||
service.kzgChan <- &kzgVerifier{dataColumns: dataColumns, resChan: resChan}
|
||||
|
||||
select {
|
||||
case err := <-resChan:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout waiting for verification result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("batches multiple requests", func(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
service := &Service{
|
||||
ctx: ctx,
|
||||
kzgChan: make(chan *kzgVerifier, 100),
|
||||
}
|
||||
go service.kzgVerifierRoutine()
|
||||
|
||||
const numRequests = 5
|
||||
resChans := make([]chan error, numRequests)
|
||||
|
||||
for i := range numRequests {
|
||||
dataColumns := createValidTestDataColumns(t, 1)
|
||||
resChan := make(chan error, 1)
|
||||
resChans[i] = resChan
|
||||
service.kzgChan <- &kzgVerifier{dataColumns: dataColumns, resChan: resChan}
|
||||
}
|
||||
|
||||
for i := range numRequests {
|
||||
select {
|
||||
case err := <-resChans[i]:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("timeout waiting for verification result %d", i)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("context cancellation stops routine", func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
service := &Service{
|
||||
ctx: ctx,
|
||||
kzgChan: make(chan *kzgVerifier, 100),
|
||||
}
|
||||
|
||||
routineDone := make(chan struct{})
|
||||
go func() {
|
||||
service.kzgVerifierRoutine()
|
||||
close(routineDone)
|
||||
}()
|
||||
|
||||
cancel()
|
||||
|
||||
select {
|
||||
case <-routineDone:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout waiting for routine to exit")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerifyKzgBatch(t *testing.T) {
|
||||
err := kzg.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("all valid data columns succeed", func(t *testing.T) {
|
||||
dataColumns := createValidTestDataColumns(t, 3)
|
||||
resChan := make(chan error, 1)
|
||||
kzgVerifiers := []*kzgVerifier{{dataColumns: dataColumns, resChan: resChan}}
|
||||
|
||||
verifyKzgBatch(kzgVerifiers)
|
||||
|
||||
select {
|
||||
case err := <-resChan:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout waiting for batch verification")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid proofs fail entire batch", func(t *testing.T) {
|
||||
validColumns := createValidTestDataColumns(t, 1)
|
||||
invalidColumns := createInvalidTestDataColumns(t, 1)
|
||||
allColumns := append(validColumns, invalidColumns...)
|
||||
|
||||
resChan := make(chan error, 1)
|
||||
kzgVerifiers := []*kzgVerifier{{dataColumns: allColumns, resChan: resChan}}
|
||||
|
||||
verifyKzgBatch(kzgVerifiers)
|
||||
|
||||
select {
|
||||
case err := <-resChan:
|
||||
assert.NotNil(t, err)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout waiting for batch verification")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty batch handling", func(t *testing.T) {
|
||||
verifyKzgBatch([]*kzgVerifier{})
|
||||
})
|
||||
}
|
||||
|
||||
func TestKzgBatchVerifierConcurrency(t *testing.T) {
|
||||
err := kzg.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := t.Context()
|
||||
|
||||
service := &Service{
|
||||
ctx: ctx,
|
||||
kzgChan: make(chan *kzgVerifier, 100),
|
||||
}
|
||||
go service.kzgVerifierRoutine()
|
||||
|
||||
const numGoroutines = 10
|
||||
const numRequestsPerGoroutine = 5
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numGoroutines)
|
||||
|
||||
// Multiple goroutines sending verification requests simultaneously
|
||||
for i := range numGoroutines {
|
||||
go func(goroutineID int) {
|
||||
defer wg.Done()
|
||||
|
||||
for range numRequestsPerGoroutine {
|
||||
dataColumns := createValidTestDataColumns(t, 1)
|
||||
result, err := service.validateWithKzgBatchVerifier(ctx, dataColumns)
|
||||
require.Equal(t, pubsub.ValidationAccept, result)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestKzgBatchVerifierFallback(t *testing.T) {
|
||||
err := kzg.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("fallback handles mixed valid/invalid batch correctly", func(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
service := &Service{
|
||||
ctx: ctx,
|
||||
kzgChan: make(chan *kzgVerifier, 100),
|
||||
}
|
||||
go service.kzgVerifierRoutine()
|
||||
|
||||
validColumns := createValidTestDataColumns(t, 1)
|
||||
invalidColumns := createInvalidTestDataColumns(t, 1)
|
||||
|
||||
result, err := service.validateWithKzgBatchVerifier(ctx, validColumns)
|
||||
require.Equal(t, pubsub.ValidationAccept, result)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err = service.validateWithKzgBatchVerifier(ctx, invalidColumns)
|
||||
require.Equal(t, pubsub.ValidationReject, result)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
|
||||
t.Run("empty data columns fallback", func(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
service := &Service{
|
||||
ctx: ctx,
|
||||
kzgChan: make(chan *kzgVerifier, 100),
|
||||
}
|
||||
go service.kzgVerifierRoutine()
|
||||
|
||||
result, err := service.validateWithKzgBatchVerifier(ctx, []blocks.RODataColumn{})
|
||||
require.Equal(t, pubsub.ValidationAccept, result)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateWithKzgBatchVerifier_DeadlockOnTimeout(t *testing.T) {
|
||||
err := kzg.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.SecondsPerSlot = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
service := &Service{
|
||||
ctx: ctx,
|
||||
kzgChan: make(chan *kzgVerifier),
|
||||
}
|
||||
go service.kzgVerifierRoutine()
|
||||
|
||||
result, err := service.validateWithKzgBatchVerifier(context.Background(), nil)
|
||||
require.Equal(t, pubsub.ValidationIgnore, result)
|
||||
require.ErrorIs(t, err, context.DeadlineExceeded)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
_, _ = service.validateWithKzgBatchVerifier(context.Background(), nil)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
t.Fatal("validateWithKzgBatchVerifier blocked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWithKzgBatchVerifier_ContextCanceledBeforeSend(t *testing.T) {
|
||||
cancelledCtx, cancel := context.WithCancel(t.Context())
|
||||
cancel()
|
||||
|
||||
service := &Service{
|
||||
ctx: context.Background(),
|
||||
kzgChan: make(chan *kzgVerifier),
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
result, err := service.validateWithKzgBatchVerifier(cancelledCtx, nil)
|
||||
require.Equal(t, pubsub.ValidationIgnore, result)
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
t.Fatal("validateWithKzgBatchVerifier did not return after context cancellation")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-service.kzgChan:
|
||||
t.Fatal("verificationSet was sent to kzgChan despite canceled context")
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func createValidTestDataColumns(t *testing.T, count int) []blocks.RODataColumn {
|
||||
_, roSidecars, _ := util.GenerateTestFuluBlockWithSidecars(t, count)
|
||||
if len(roSidecars) >= count {
|
||||
|
||||
@@ -77,8 +77,13 @@ func SendBeaconBlocksByRangeRequest(
|
||||
}
|
||||
defer closeStream(stream, log)
|
||||
|
||||
// Cap the slice capacity to MaxRequestBlock to prevent panic from invalid Count values.
|
||||
// This guards against upstream bugs that may produce astronomically large Count values
|
||||
// (e.g., due to unsigned integer underflow).
|
||||
sliceCap := min(req.Count, params.MaxRequestBlock(slots.ToEpoch(tor.CurrentSlot())))
|
||||
|
||||
// Augment block processing function, if non-nil block processor is provided.
|
||||
blocks := make([]interfaces.ReadOnlySignedBeaconBlock, 0, req.Count)
|
||||
blocks := make([]interfaces.ReadOnlySignedBeaconBlock, 0, sliceCap)
|
||||
process := func(blk interfaces.ReadOnlySignedBeaconBlock) error {
|
||||
blocks = append(blocks, blk)
|
||||
if blockProcessor != nil {
|
||||
|
||||
@@ -168,7 +168,6 @@ type Service struct {
|
||||
syncContributionBitsOverlapLock sync.RWMutex
|
||||
syncContributionBitsOverlapCache *lru.Cache
|
||||
signatureChan chan *signatureVerifier
|
||||
kzgChan chan *kzgVerifier
|
||||
clockWaiter startup.ClockWaiter
|
||||
initialSyncComplete chan struct{}
|
||||
verifierWaiter *verification.InitializerWaiter
|
||||
@@ -209,10 +208,7 @@ func NewService(ctx context.Context, opts ...Option) *Service {
|
||||
}
|
||||
// Initialize signature channel with configured limit
|
||||
r.signatureChan = make(chan *signatureVerifier, r.cfg.batchVerifierLimit)
|
||||
// Initialize KZG channel with fixed buffer size of 100.
|
||||
// This buffer size is designed to handle burst traffic of data column gossip messages:
|
||||
// - Data columns arrive less frequently than attestations (default batchVerifierLimit=1000)
|
||||
r.kzgChan = make(chan *kzgVerifier, 100)
|
||||
|
||||
// Correctly remove it from our seen pending block map.
|
||||
// The eviction method always assumes that the mutex is held.
|
||||
r.slotToPendingBlocks.OnEvicted(func(s string, i any) {
|
||||
@@ -265,7 +261,6 @@ func (s *Service) Start() {
|
||||
s.newColumnsVerifier = newDataColumnsVerifierFromInitializer(v)
|
||||
|
||||
go s.verifierRoutine()
|
||||
go s.kzgVerifierRoutine()
|
||||
go s.startDiscoveryAndSubscriptions()
|
||||
go s.processDataColumnLogs()
|
||||
|
||||
|
||||
@@ -144,12 +144,9 @@ func (s *Service) validateDataColumn(ctx context.Context, pid peer.ID, msg *pubs
|
||||
}
|
||||
|
||||
// [REJECT] The sidecar's column data is valid as verified by `verify_data_column_sidecar_kzg_proofs(sidecar)`.
|
||||
validationResult, err := s.validateWithKzgBatchVerifier(ctx, roDataColumns)
|
||||
if validationResult != pubsub.ValidationAccept {
|
||||
return validationResult, err
|
||||
if err := verifier.SidecarKzgProofVerified(); err != nil {
|
||||
return pubsub.ValidationReject, err
|
||||
}
|
||||
// Mark KZG verification as satisfied since we did it via batch verifier
|
||||
verifier.SatisfyRequirement(verification.RequireSidecarKzgProofVerified)
|
||||
|
||||
// [IGNORE] The sidecar is the first sidecar for the tuple `(block_header.slot, block_header.proposer_index, sidecar.index)`
|
||||
// with valid header signature, sidecar inclusion proof, and kzg proof.
|
||||
|
||||
@@ -71,10 +71,7 @@ func TestValidateDataColumn(t *testing.T) {
|
||||
ctx: ctx,
|
||||
newColumnsVerifier: newDataColumnsVerifier,
|
||||
seenDataColumnCache: newSlotAwareCache(seenDataColumnSize),
|
||||
kzgChan: make(chan *kzgVerifier, 100),
|
||||
}
|
||||
// Start the KZG verifier routine for batch verification
|
||||
go service.kzgVerifierRoutine()
|
||||
|
||||
// Encode a `beaconBlock` message instead of expected.
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
3
changelog/bastin_statediff-touch-up.md
Normal file
3
changelog/bastin_statediff-touch-up.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Ignored
|
||||
|
||||
- small touch ups on state diff code.
|
||||
2
changelog/eth2353_fix-aggregate-typo.md
Normal file
2
changelog/eth2353_fix-aggregate-typo.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Fixed
|
||||
- Fixed a typo: AggregrateDueBPS -> AggregateDueBPS.
|
||||
3
changelog/farazdagi_fix-prysmctl-timestamp.md
Normal file
3
changelog/farazdagi_fix-prysmctl-timestamp.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- Fix `prysmctl testnet generate-genesis` to use the timestamp from `--geth-genesis-json-in` when `--genesis-time` is not explicitly provided.
|
||||
3
changelog/farazdagi_remove-gometalinter-refs.md
Normal file
3
changelog/farazdagi_remove-gometalinter-refs.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Ignored
|
||||
|
||||
- Removed deprecated gometalinter references from Bazel configuration.
|
||||
3
changelog/fix-validator-web-auth-bypass.md
Normal file
3
changelog/fix-validator-web-auth-bypass.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- Prevent authentication bypass on direct `/v2/validator/*` endpoints by enforcing auth checks for non-public routes.
|
||||
3
changelog/james-prysm_longer-e2e-sync-evaluator.md
Normal file
3
changelog/james-prysm_longer-e2e-sync-evaluator.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Ignored
|
||||
|
||||
- adding a optimistic check for e2e evlauator on synced head, it may be slower post fulu to sync.
|
||||
3
changelog/manu-kzg-batch.md
Normal file
3
changelog/manu-kzg-batch.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Removed
|
||||
|
||||
- Batching of KZG verification for incoming via gossip data column sidecars
|
||||
3
changelog/manu_remove_unused_delay.md
Normal file
3
changelog/manu_remove_unused_delay.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Removed
|
||||
|
||||
- Remove unused `delay` parameter from `fetchOriginDataColumnSidecars` function.
|
||||
3
changelog/pvl-fix-16223.md
Normal file
3
changelog/pvl-fix-16223.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Changed
|
||||
|
||||
- Added some defensive checks to prevent overflows in block batch requests.
|
||||
3
changelog/pvl-gazelle-repos-check.md
Normal file
3
changelog/pvl-gazelle-repos-check.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Ignored
|
||||
|
||||
- Gazelle update-repos is now enforced via ./hack/check_gazelle.sh
|
||||
3
changelog/satushh-log.md
Normal file
3
changelog/satushh-log.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Changed
|
||||
|
||||
- Log commitments instead of indices in missingCommitError
|
||||
@@ -1,3 +0,0 @@
|
||||
### Added
|
||||
|
||||
- Added basic Gloas builder support (`Builder` message and `BeaconStateGloas` `builders`/`next_withdrawal_builder_index` fields)
|
||||
2
changelog/t_process-builder-pending-payments.md
Normal file
2
changelog/t_process-builder-pending-payments.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Added
|
||||
- add pending payments processing and quorum threshold, plus spectests and state hooks (rotate/append)
|
||||
3
changelog/ttsao_add-gloas-bid-processing.md
Normal file
3
changelog/ttsao_add-gloas-bid-processing.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Add Gloas latest execution bid processing
|
||||
3
changelog/willianpaixao_shell-autocompletion.md
Normal file
3
changelog/willianpaixao_shell-autocompletion.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Added shell completion support for `beacon-chain` and `validator` CLI tools.
|
||||
@@ -3,6 +3,8 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"completion.go",
|
||||
"completion_scripts.go",
|
||||
"config.go",
|
||||
"defaults.go",
|
||||
"flags.go",
|
||||
@@ -28,6 +30,7 @@ go_test(
|
||||
name = "go_default_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"completion_test.go",
|
||||
"config_test.go",
|
||||
"flags_test.go",
|
||||
"helpers_test.go",
|
||||
|
||||
@@ -271,9 +271,11 @@ func main() {
|
||||
Commands: []*cli.Command{
|
||||
dbcommands.Commands,
|
||||
jwtcommands.Commands,
|
||||
cmd.CompletionCommand("beacon-chain"),
|
||||
},
|
||||
Flags: appFlags,
|
||||
Before: before,
|
||||
Flags: appFlags,
|
||||
Before: before,
|
||||
EnableBashCompletion: true,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
|
||||
63
cmd/completion.go
Normal file
63
cmd/completion.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CompletionCommand returns the completion command for the given binary name.
|
||||
// The binaryName parameter should be "beacon-chain" or "validator".
|
||||
func CompletionCommand(binaryName string) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "completion",
|
||||
Category: "completion",
|
||||
Usage: "Generate shell completion scripts",
|
||||
Description: fmt.Sprintf(`Generate shell completion scripts for bash, zsh, or fish.
|
||||
|
||||
To load completions:
|
||||
|
||||
Bash:
|
||||
$ source <(%[1]s completion bash)
|
||||
# To load completions for each session, execute once:
|
||||
$ %[1]s completion bash > /etc/bash_completion.d/%[1]s
|
||||
|
||||
Zsh:
|
||||
# To load completions for each session, execute once:
|
||||
$ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
|
||||
|
||||
# You may need to start a new shell for completions to take effect.
|
||||
|
||||
Fish:
|
||||
$ %[1]s completion fish | source
|
||||
# To load completions for each session, execute once:
|
||||
$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
|
||||
`, binaryName),
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "bash",
|
||||
Usage: "Generate bash completion script",
|
||||
Action: func(_ *cli.Context) error {
|
||||
fmt.Println(bashCompletionScript(binaryName))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "zsh",
|
||||
Usage: "Generate zsh completion script",
|
||||
Action: func(_ *cli.Context) error {
|
||||
fmt.Println(zshCompletionScript(binaryName))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "fish",
|
||||
Usage: "Generate fish completion script",
|
||||
Action: func(_ *cli.Context) error {
|
||||
fmt.Println(fishCompletionScript(binaryName))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
99
cmd/completion_scripts.go
Normal file
99
cmd/completion_scripts.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// bashCompletionScript returns the bash completion script for the given binary.
|
||||
func bashCompletionScript(binaryName string) string {
|
||||
// Convert hyphens to underscores for bash function names
|
||||
funcName := strings.ReplaceAll(binaryName, "-", "_")
|
||||
return fmt.Sprintf(`#!/bin/bash
|
||||
|
||||
_%[1]s_completions() {
|
||||
local cur prev words cword opts
|
||||
COMPREPLY=()
|
||||
|
||||
# Use bash-completion if available, otherwise set variables directly
|
||||
if declare -F _init_completion >/dev/null 2>&1; then
|
||||
_init_completion -n "=:" || return
|
||||
else
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
words=("${COMP_WORDS[@]}")
|
||||
cword=$COMP_CWORD
|
||||
fi
|
||||
|
||||
# Build command array for completion - flag must be at the END
|
||||
local -a requestComp
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
requestComp=("${COMP_WORDS[@]:0:COMP_CWORD}" "$cur" --generate-bash-completion)
|
||||
else
|
||||
requestComp=("${COMP_WORDS[@]:0:COMP_CWORD}" --generate-bash-completion)
|
||||
fi
|
||||
|
||||
opts=$("${requestComp[@]}" 2>/dev/null)
|
||||
COMPREPLY=($(compgen -W "${opts}" -- "${cur}"))
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -o bashdefault -o default -o nospace -F _%[1]s_completions %[2]s
|
||||
`, funcName, binaryName)
|
||||
}
|
||||
|
||||
// zshCompletionScript returns the zsh completion script for the given binary.
|
||||
func zshCompletionScript(binaryName string) string {
|
||||
// Convert hyphens to underscores for zsh function names
|
||||
funcName := strings.ReplaceAll(binaryName, "-", "_")
|
||||
return fmt.Sprintf(`#compdef %[2]s
|
||||
|
||||
_%[1]s() {
|
||||
local curcontext="$curcontext" ret=1
|
||||
local -a completions
|
||||
|
||||
# Build command array with --generate-bash-completion at the END
|
||||
local -a requestComp
|
||||
if [[ "${words[CURRENT]}" == -* ]]; then
|
||||
requestComp=(${words[1,CURRENT]} --generate-bash-completion)
|
||||
else
|
||||
requestComp=(${words[1,CURRENT-1]} --generate-bash-completion)
|
||||
fi
|
||||
|
||||
completions=($("${requestComp[@]}" 2>/dev/null))
|
||||
|
||||
if [[ ${#completions[@]} -gt 0 ]]; then
|
||||
_describe -t commands '%[2]s' completions && ret=0
|
||||
fi
|
||||
|
||||
# Fallback to file completion
|
||||
_files && ret=0
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
compdef _%[1]s %[2]s
|
||||
`, funcName, binaryName)
|
||||
}
|
||||
|
||||
// fishCompletionScript returns the fish completion script for the given binary.
|
||||
func fishCompletionScript(binaryName string) string {
|
||||
// Convert hyphens to underscores for fish function names
|
||||
funcName := strings.ReplaceAll(binaryName, "-", "_")
|
||||
return fmt.Sprintf(`# Fish completion for %[2]s
|
||||
|
||||
function __fish_%[1]s_complete
|
||||
set -l args (commandline -opc)
|
||||
set -l cur (commandline -ct)
|
||||
|
||||
# Build command with --generate-bash-completion at the END
|
||||
if string match -q -- '-*' "$cur"
|
||||
%[2]s $args $cur --generate-bash-completion 2>/dev/null
|
||||
else
|
||||
%[2]s $args --generate-bash-completion 2>/dev/null
|
||||
end
|
||||
end
|
||||
|
||||
complete -c %[2]s -f -a "(__fish_%[1]s_complete)"
|
||||
`, funcName, binaryName)
|
||||
}
|
||||
105
cmd/completion_test.go
Normal file
105
cmd/completion_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func TestCompletionCommand(t *testing.T) {
|
||||
t.Run("creates command with correct name", func(t *testing.T) {
|
||||
cmd := CompletionCommand("beacon-chain")
|
||||
require.Equal(t, "completion", cmd.Name)
|
||||
})
|
||||
|
||||
t.Run("has three subcommands", func(t *testing.T) {
|
||||
cmd := CompletionCommand("beacon-chain")
|
||||
require.Equal(t, 3, len(cmd.Subcommands))
|
||||
|
||||
names := make([]string, len(cmd.Subcommands))
|
||||
for i, sub := range cmd.Subcommands {
|
||||
names[i] = sub.Name
|
||||
}
|
||||
assert.DeepEqual(t, []string{"bash", "zsh", "fish"}, names)
|
||||
})
|
||||
|
||||
t.Run("description contains binary name", func(t *testing.T) {
|
||||
cmd := CompletionCommand("validator")
|
||||
assert.Equal(t, true, strings.Contains(cmd.Description, "validator"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestBashCompletionScript(t *testing.T) {
|
||||
script := bashCompletionScript("beacon-chain")
|
||||
|
||||
assert.Equal(t, true, strings.Contains(script, "beacon-chain"), "script should contain binary name")
|
||||
assert.Equal(t, true, strings.Contains(script, "_beacon_chain_completions"), "script should contain function name with underscores")
|
||||
assert.Equal(t, true, strings.Contains(script, "complete -o bashdefault"), "script should contain complete command")
|
||||
assert.Equal(t, true, strings.Contains(script, "--generate-bash-completion"), "script should use generate-bash-completion flag")
|
||||
}
|
||||
|
||||
func TestZshCompletionScript(t *testing.T) {
|
||||
script := zshCompletionScript("validator")
|
||||
|
||||
assert.Equal(t, true, strings.Contains(script, "#compdef validator"), "script should contain compdef directive")
|
||||
assert.Equal(t, true, strings.Contains(script, "_validator"), "script should contain function name")
|
||||
assert.Equal(t, true, strings.Contains(script, "--generate-bash-completion"), "script should use generate-bash-completion flag")
|
||||
}
|
||||
|
||||
func TestFishCompletionScript(t *testing.T) {
|
||||
script := fishCompletionScript("beacon-chain")
|
||||
|
||||
assert.Equal(t, true, strings.Contains(script, "complete -c beacon-chain"), "script should contain complete command")
|
||||
assert.Equal(t, true, strings.Contains(script, "__fish_beacon_chain_complete"), "script should contain function name with underscores")
|
||||
assert.Equal(t, true, strings.Contains(script, "--generate-bash-completion"), "script should use generate-bash-completion flag")
|
||||
}
|
||||
|
||||
func TestScriptFunctionNames(t *testing.T) {
|
||||
// Test that hyphens are converted to underscores in function names
|
||||
bashScript := bashCompletionScript("beacon-chain")
|
||||
assert.Equal(t, true, strings.Contains(bashScript, "_beacon_chain_completions"))
|
||||
assert.Equal(t, false, strings.Contains(bashScript, "_beacon-chain_completions"))
|
||||
|
||||
zshScript := zshCompletionScript("beacon-chain")
|
||||
assert.Equal(t, true, strings.Contains(zshScript, "_beacon_chain"))
|
||||
|
||||
fishScript := fishCompletionScript("beacon-chain")
|
||||
assert.Equal(t, true, strings.Contains(fishScript, "__fish_beacon_chain_complete"))
|
||||
}
|
||||
|
||||
func TestCompletionSubcommandActions(t *testing.T) {
|
||||
// Test that Action functions execute without errors
|
||||
cmd := CompletionCommand("beacon-chain")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
subcommand string
|
||||
}{
|
||||
{"bash action executes", "bash"},
|
||||
{"zsh action executes", "zsh"},
|
||||
{"fish action executes", "fish"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var subCmd *cli.Command
|
||||
for _, sub := range cmd.Subcommands {
|
||||
if sub.Name == tt.subcommand {
|
||||
subCmd = sub
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, subCmd, "subcommand should exist")
|
||||
require.NotNil(t, subCmd.Action, "subcommand should have an action")
|
||||
|
||||
// Action should not return an error; use a real cli.Context
|
||||
app := &cli.App{}
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
err := subCmd.Action(ctx)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -214,13 +214,6 @@ func setGlobalParams() error {
|
||||
|
||||
func generateGenesis(ctx context.Context) (state.BeaconState, error) {
|
||||
f := &generateGenesisStateFlags
|
||||
if f.GenesisTime == 0 {
|
||||
f.GenesisTime = uint64(time.Now().Unix())
|
||||
log.Info("No genesis time specified, defaulting to now()")
|
||||
}
|
||||
log.Infof("Delaying genesis %v by %v seconds", f.GenesisTime, f.GenesisTimeDelay)
|
||||
f.GenesisTime += f.GenesisTimeDelay
|
||||
log.Infof("Genesis is now %v", f.GenesisTime)
|
||||
|
||||
v, err := version.FromString(f.ForkName)
|
||||
if err != nil {
|
||||
@@ -258,7 +251,28 @@ func generateGenesis(ctx context.Context) (state.BeaconState, error) {
|
||||
if err := json.Unmarshal(gbytes, gen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// set timestamps for genesis and shanghai fork
|
||||
// Use input file's timestamp (if `--genesis-time` is NOT explicitly provided)
|
||||
if f.GenesisTime == 0 {
|
||||
f.GenesisTime = gen.Timestamp
|
||||
log.Infof("Using genesis time from input file: %d", f.GenesisTime)
|
||||
}
|
||||
}
|
||||
|
||||
// If still unset (no `--genesis-time` and no input file), default to `now()`
|
||||
if f.GenesisTime == 0 {
|
||||
f.GenesisTime = uint64(time.Now().Unix())
|
||||
log.Info("No genesis time specified, defaulting to now()")
|
||||
}
|
||||
|
||||
// Apply delay (if any) and expose used genesis time
|
||||
if f.GenesisTimeDelay > 0 {
|
||||
log.Infof("Delaying genesis %d by %d seconds", f.GenesisTime, f.GenesisTimeDelay)
|
||||
f.GenesisTime += f.GenesisTimeDelay
|
||||
}
|
||||
log.Infof("Genesis time is %d", f.GenesisTime)
|
||||
|
||||
// Set the timestamps for genesis and forks
|
||||
if f.GethGenesisJsonIn != "" {
|
||||
gen.Timestamp = f.GenesisTime
|
||||
genesis := time.Unix(int64(f.GenesisTime), 0)
|
||||
gen.Config.ShanghaiTime = interop.GethShanghaiTime(genesis, params.BeaconConfig())
|
||||
@@ -301,7 +315,7 @@ func generateGenesis(ctx context.Context) (state.BeaconState, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.WriteFile(f.GethGenesisJsonOut, gbytes, 0600); err != nil {
|
||||
if err := os.WriteFile(f.GethGenesisJsonOut, gbytes, 0o600); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to write %s", f.GethGenesisJsonOut)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,75 @@ func Test_generateGenesis_BaseFeeValidation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func writeFile(path string, data []byte) error {
|
||||
return os.WriteFile(path, data, 0644)
|
||||
func Test_generateGenesis_TimestampHandling(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputTimestamp uint64 // timestamp in input genesis.json (0 = no input file)
|
||||
genesisTime uint64 // --genesis-time (0 = not set)
|
||||
genesisTimeDelay uint64 // --genesis-time-delay
|
||||
wantTimestamp uint64
|
||||
}{
|
||||
{
|
||||
name: "uses input file timestamp when no --genesis-time",
|
||||
inputTimestamp: 1700000000,
|
||||
wantTimestamp: 1700000000,
|
||||
},
|
||||
{
|
||||
name: "explicit --genesis-time overrides input file",
|
||||
inputTimestamp: 1700000000,
|
||||
genesisTime: 1600000000,
|
||||
wantTimestamp: 1600000000,
|
||||
},
|
||||
{
|
||||
name: "delay applied to input file timestamp",
|
||||
inputTimestamp: 1700000000,
|
||||
genesisTimeDelay: 100,
|
||||
wantTimestamp: 1700000100,
|
||||
},
|
||||
{
|
||||
name: "delay applied to explicit --genesis-time",
|
||||
inputTimestamp: 1700000000,
|
||||
genesisTime: 1600000000,
|
||||
genesisTimeDelay: 50,
|
||||
wantTimestamp: 1600000050,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
originalFlags := generateGenesisStateFlags
|
||||
defer func() {
|
||||
generateGenesisStateFlags = originalFlags
|
||||
}()
|
||||
|
||||
generateGenesisStateFlags.NumValidators = 2
|
||||
generateGenesisStateFlags.GenesisTime = tt.genesisTime
|
||||
generateGenesisStateFlags.GenesisTimeDelay = tt.genesisTimeDelay
|
||||
generateGenesisStateFlags.ForkName = version.String(version.Deneb)
|
||||
|
||||
if tt.inputTimestamp > 0 {
|
||||
genesis := &core.Genesis{
|
||||
Timestamp: tt.inputTimestamp,
|
||||
BaseFee: big.NewInt(1000000000),
|
||||
Difficulty: big.NewInt(0),
|
||||
GasLimit: 15000000,
|
||||
Alloc: types.GenesisAlloc{},
|
||||
Config: ¶ms.ChainConfig{ChainID: big.NewInt(32382)},
|
||||
}
|
||||
genesisJSON, err := json.Marshal(genesis)
|
||||
require.NoError(t, err)
|
||||
tmpFile := t.TempDir() + "/genesis.json"
|
||||
require.NoError(t, writeFile(tmpFile, genesisJSON))
|
||||
generateGenesisStateFlags.GethGenesisJsonIn = tmpFile
|
||||
}
|
||||
|
||||
st, err := generateGenesis(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(tt.wantTimestamp), st.GenesisTime().Unix())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func writeFile(path string, data []byte) error {
|
||||
return os.WriteFile(path, data, 0o644)
|
||||
}
|
||||
|
||||
@@ -140,8 +140,10 @@ func main() {
|
||||
slashingprotectioncommands.Commands,
|
||||
dbcommands.Commands,
|
||||
web.Commands,
|
||||
cmd.CompletionCommand("validator"),
|
||||
},
|
||||
Flags: appFlags,
|
||||
Flags: appFlags,
|
||||
EnableBashCompletion: true,
|
||||
Before: func(ctx *cli.Context) error {
|
||||
// Load flags from config file, if specified.
|
||||
if err := cmd.LoadFlagsFromConfig(ctx, appFlags); err != nil {
|
||||
|
||||
@@ -56,10 +56,12 @@ type BeaconChainConfig struct {
|
||||
EffectiveBalanceIncrement uint64 `yaml:"EFFECTIVE_BALANCE_INCREMENT" spec:"true"` // EffectiveBalanceIncrement is used for converting the high balance into the low balance for validators.
|
||||
|
||||
// Initial value constants.
|
||||
BLSWithdrawalPrefixByte byte `yaml:"BLS_WITHDRAWAL_PREFIX" spec:"true"` // BLSWithdrawalPrefixByte is used for BLS withdrawal and it's the first byte.
|
||||
ETH1AddressWithdrawalPrefixByte byte `yaml:"ETH1_ADDRESS_WITHDRAWAL_PREFIX" spec:"true"` // ETH1AddressWithdrawalPrefixByte is used for withdrawals and it's the first byte.
|
||||
CompoundingWithdrawalPrefixByte byte `yaml:"COMPOUNDING_WITHDRAWAL_PREFIX" spec:"true"` // CompoundingWithdrawalPrefixByteByte is used for compounding withdrawals and it's the first byte.
|
||||
ZeroHash [32]byte // ZeroHash is used to represent a zeroed out 32 byte array.
|
||||
BLSWithdrawalPrefixByte byte `yaml:"BLS_WITHDRAWAL_PREFIX" spec:"true"` // BLSWithdrawalPrefixByte is used for BLS withdrawal and it's the first byte.
|
||||
ETH1AddressWithdrawalPrefixByte byte `yaml:"ETH1_ADDRESS_WITHDRAWAL_PREFIX" spec:"true"` // ETH1AddressWithdrawalPrefixByte is used for withdrawals and it's the first byte.
|
||||
CompoundingWithdrawalPrefixByte byte `yaml:"COMPOUNDING_WITHDRAWAL_PREFIX" spec:"true"` // CompoundingWithdrawalPrefixByteByte is used for compounding withdrawals and it's the first byte.
|
||||
BuilderWithdrawalPrefixByte byte `yaml:"BUILDER_WITHDRAWAL_PREFIX" spec:"true"` // BuilderWithdrawalPrefixByte is used for builder withdrawals and it's the first byte.
|
||||
BuilderIndexSelfBuild primitives.BuilderIndex `yaml:"BUILDER_INDEX_SELF_BUILD" spec:"true"` // BuilderIndexSelfBuild indicates proposer self-built payloads.
|
||||
ZeroHash [32]byte // ZeroHash is used to represent a zeroed out 32 byte array.
|
||||
|
||||
// Time parameters constants.
|
||||
GenesisDelay uint64 `yaml:"GENESIS_DELAY" spec:"true"` // GenesisDelay is the minimum number of seconds to delay starting the Ethereum Beacon Chain genesis. Must be at least 1 second.
|
||||
@@ -86,7 +88,7 @@ type BeaconChainConfig struct {
|
||||
IntervalsPerSlot uint64 `yaml:"INTERVALS_PER_SLOT"` // IntervalsPerSlot defines the number of fork choice intervals in a slot defined in the fork choice spec.
|
||||
ProposerReorgCutoffBPS primitives.BP `yaml:"PROPOSER_REORG_CUTOFF_BPS" spec:"true"` // ProposerReorgCutoffBPS defines the proposer reorg deadline in basis points of the slot.
|
||||
AttestationDueBPS primitives.BP `yaml:"ATTESTATION_DUE_BPS" spec:"true"` // AttestationDueBPS defines the attestation due time in basis points of the slot.
|
||||
AggregrateDueBPS primitives.BP `yaml:"AGGREGRATE_DUE_BPS" spec:"true"` // AggregrateDueBPS defines the aggregate due time in basis points of the slot.
|
||||
AggregateDueBPS primitives.BP `yaml:"AGGREGATE_DUE_BPS" spec:"true"` // AggregateDueBPS defines the aggregate due time in basis points of the slot.
|
||||
SyncMessageDueBPS primitives.BP `yaml:"SYNC_MESSAGE_DUE_BPS" spec:"true"` // SyncMessageDueBPS defines the sync message due time in basis points of the slot.
|
||||
ContributionDueBPS primitives.BP `yaml:"CONTRIBUTION_DUE_BPS" spec:"true"` // ContributionDueBPS defines the contribution due time in basis points of the slot.
|
||||
|
||||
@@ -139,6 +141,7 @@ type BeaconChainConfig struct {
|
||||
DomainApplicationMask [4]byte `yaml:"DOMAIN_APPLICATION_MASK" spec:"true"` // DomainApplicationMask defines the BLS signature domain for application mask.
|
||||
DomainApplicationBuilder [4]byte `yaml:"DOMAIN_APPLICATION_BUILDER" spec:"true"` // DomainApplicationBuilder defines the BLS signature domain for application builder.
|
||||
DomainBLSToExecutionChange [4]byte `yaml:"DOMAIN_BLS_TO_EXECUTION_CHANGE" spec:"true"` // DomainBLSToExecutionChange defines the BLS signature domain to change withdrawal addresses to ETH1 prefix
|
||||
DomainBeaconBuilder [4]byte `yaml:"DOMAIN_BEACON_BUILDER" spec:"true"` // DomainBeaconBuilder defines the BLS signature domain for beacon block builder.
|
||||
|
||||
// Prysm constants.
|
||||
GenesisValidatorsRoot [32]byte // GenesisValidatorsRoot is the root hash of the genesis validators.
|
||||
@@ -290,6 +293,10 @@ type BeaconChainConfig struct {
|
||||
ValidatorCustodyRequirement uint64 `yaml:"VALIDATOR_CUSTODY_REQUIREMENT" spec:"true"` // ValidatorCustodyRequirement is the minimum number of custody groups an honest node with validators attached custodies and serves samples from
|
||||
BalancePerAdditionalCustodyGroup uint64 `yaml:"BALANCE_PER_ADDITIONAL_CUSTODY_GROUP" spec:"true"` // BalancePerAdditionalCustodyGroup is the balance increment corresponding to one additional group to custody.
|
||||
|
||||
// Values introduced in Gloas upgrade
|
||||
BuilderPaymentThresholdNumerator uint64 `yaml:"BUILDER_PAYMENT_THRESHOLD_NUMERATOR" spec:"true"` // BuilderPaymentThresholdNumerator is the numerator for builder payment quorum threshold calculation.
|
||||
BuilderPaymentThresholdDenominator uint64 `yaml:"BUILDER_PAYMENT_THRESHOLD_DENOMINATOR" spec:"true"` // BuilderPaymentThresholdDenominator is the denominator for builder payment quorum threshold calculation.
|
||||
|
||||
// Networking Specific Parameters
|
||||
MaxPayloadSize uint64 `yaml:"MAX_PAYLOAD_SIZE" spec:"true"` // MAX_PAYLOAD_SIZE is the maximum allowed size of uncompressed payload in gossip messages and rpc chunks.
|
||||
AttestationSubnetCount uint64 `yaml:"ATTESTATION_SUBNET_COUNT" spec:"true"` // AttestationSubnetCount is the number of attestation subnets used in the gossipsub protocol.
|
||||
|
||||
@@ -243,7 +243,7 @@ func ConfigToYaml(cfg *BeaconChainConfig) []byte {
|
||||
fmt.Sprintf("MAX_BLOBS_PER_BLOCK: %d", cfg.DeprecatedMaxBlobsPerBlock),
|
||||
fmt.Sprintf("PROPOSER_REORG_CUTOFF_BPS: %d", cfg.ProposerReorgCutoffBPS),
|
||||
fmt.Sprintf("ATTESTATION_DUE_BPS: %d", cfg.AttestationDueBPS),
|
||||
fmt.Sprintf("AGGREGRATE_DUE_BPS: %d", cfg.AggregrateDueBPS),
|
||||
fmt.Sprintf("AGGREGATE_DUE_BPS: %d", cfg.AggregateDueBPS),
|
||||
fmt.Sprintf("SYNC_MESSAGE_DUE_BPS: %d", cfg.SyncMessageDueBPS),
|
||||
fmt.Sprintf("CONTRIBUTION_DUE_BPS: %d", cfg.ContributionDueBPS),
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
// These are variables that we don't use in Prysm. (i.e. future hardfork, light client... etc)
|
||||
// IMPORTANT: Use one field per line and sort these alphabetically to reduce conflicts.
|
||||
var placeholderFields = []string{
|
||||
"AGGREGATE_DUE_BPS",
|
||||
"AGGREGATE_DUE_BPS_GLOAS",
|
||||
"ATTESTATION_DEADLINE",
|
||||
"ATTESTATION_DUE_BPS_GLOAS",
|
||||
@@ -99,7 +98,7 @@ func assertEqualConfigs(t *testing.T, name string, fields []string, expected, ac
|
||||
assert.Equal(t, expected.HysteresisDownwardMultiplier, actual.HysteresisDownwardMultiplier, "%s: HysteresisDownwardMultiplier", name)
|
||||
assert.Equal(t, expected.HysteresisUpwardMultiplier, actual.HysteresisUpwardMultiplier, "%s: HysteresisUpwardMultiplier", name)
|
||||
assert.Equal(t, expected.AttestationDueBPS, actual.AttestationDueBPS, "%s: AttestationDueBPS", name)
|
||||
assert.Equal(t, expected.AggregrateDueBPS, actual.AggregrateDueBPS, "%s: AggregrateDueBPS", name)
|
||||
assert.Equal(t, expected.AggregateDueBPS, actual.AggregateDueBPS, "%s: AggregateDueBPS", name)
|
||||
assert.Equal(t, expected.ContributionDueBPS, actual.ContributionDueBPS, "%s: ContributionDueBPS", name)
|
||||
assert.Equal(t, expected.ProposerReorgCutoffBPS, actual.ProposerReorgCutoffBPS, "%s: ProposerReorgCutoffBPS", name)
|
||||
assert.Equal(t, expected.SyncMessageDueBPS, actual.SyncMessageDueBPS, "%s: SyncMessageDueBPS", name)
|
||||
|
||||
@@ -94,6 +94,8 @@ var mainnetBeaconConfig = &BeaconChainConfig{
|
||||
BLSWithdrawalPrefixByte: byte(0),
|
||||
ETH1AddressWithdrawalPrefixByte: byte(1),
|
||||
CompoundingWithdrawalPrefixByte: byte(2),
|
||||
BuilderWithdrawalPrefixByte: byte(3),
|
||||
BuilderIndexSelfBuild: primitives.BuilderIndex(math.MaxUint64),
|
||||
ZeroHash: [32]byte{},
|
||||
|
||||
// Time parameter constants.
|
||||
@@ -121,7 +123,7 @@ var mainnetBeaconConfig = &BeaconChainConfig{
|
||||
// Time-based protocol parameters.
|
||||
ProposerReorgCutoffBPS: primitives.BP(1667),
|
||||
AttestationDueBPS: primitives.BP(3333),
|
||||
AggregrateDueBPS: primitives.BP(6667),
|
||||
AggregateDueBPS: primitives.BP(6667),
|
||||
SyncMessageDueBPS: primitives.BP(3333),
|
||||
ContributionDueBPS: primitives.BP(6667),
|
||||
|
||||
@@ -182,6 +184,7 @@ var mainnetBeaconConfig = &BeaconChainConfig{
|
||||
DomainApplicationMask: bytesutil.Uint32ToBytes4(0x00000001),
|
||||
DomainApplicationBuilder: bytesutil.Uint32ToBytes4(0x00000001),
|
||||
DomainBLSToExecutionChange: bytesutil.Uint32ToBytes4(0x0A000000),
|
||||
DomainBeaconBuilder: bytesutil.Uint32ToBytes4(0x0B000000),
|
||||
|
||||
// Prysm constants.
|
||||
GenesisValidatorsRoot: [32]byte{75, 54, 61, 185, 78, 40, 97, 32, 215, 110, 185, 5, 52, 15, 221, 78, 84, 191, 233, 240, 107, 243, 63, 246, 207, 90, 210, 127, 81, 27, 254, 149},
|
||||
@@ -328,6 +331,11 @@ var mainnetBeaconConfig = &BeaconChainConfig{
|
||||
MinEpochsForDataColumnSidecarsRequest: 4096,
|
||||
ValidatorCustodyRequirement: 8,
|
||||
BalancePerAdditionalCustodyGroup: 32_000_000_000,
|
||||
|
||||
// Values related to gloas
|
||||
BuilderPaymentThresholdNumerator: 6,
|
||||
BuilderPaymentThresholdDenominator: 10,
|
||||
|
||||
// Values related to networking parameters.
|
||||
MaxPayloadSize: 10 * 1 << 20, // 10 MiB
|
||||
AttestationSubnetCount: 64,
|
||||
|
||||
@@ -14,11 +14,13 @@ go_library(
|
||||
"roblock.go",
|
||||
"rodatacolumn.go",
|
||||
"setters.go",
|
||||
"signed_execution_bid.go",
|
||||
"types.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/consensus-types/blocks",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/state/stateutil:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
@@ -53,9 +55,11 @@ go_test(
|
||||
"roblock_test.go",
|
||||
"rodatacolumn_test.go",
|
||||
"setters_test.go",
|
||||
"signed_execution_bid_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types:go_default_library",
|
||||
|
||||
@@ -641,6 +641,8 @@ func BuildSignedBeaconBlockFromExecutionPayload(blk interfaces.ReadOnlySignedBea
|
||||
},
|
||||
Signature: sig[:],
|
||||
}
|
||||
case version.Gloas:
|
||||
return nil, errors.Wrap(errUnsupportedBeaconBlock, "gloas blocks are not supported in this function")
|
||||
default:
|
||||
return nil, errors.New("Block not of known type")
|
||||
}
|
||||
|
||||
142
consensus-types/blocks/signed_execution_bid.go
Normal file
142
consensus-types/blocks/signed_execution_bid.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
consensus_types "github.com/OffchainLabs/prysm/v7/consensus-types"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// signedExecutionPayloadBid wraps the protobuf signed execution payload bid
|
||||
// and implements the ROSignedExecutionPayloadBid interface.
|
||||
type signedExecutionPayloadBid struct {
|
||||
bid *ethpb.SignedExecutionPayloadBid
|
||||
}
|
||||
|
||||
// executionPayloadBidGloas wraps the protobuf execution payload bid for Gloas fork
|
||||
// and implements the ROExecutionPayloadBidGloas interface.
|
||||
type executionPayloadBidGloas struct {
|
||||
payload *ethpb.ExecutionPayloadBid
|
||||
}
|
||||
|
||||
// IsNil checks if the signed execution payload bid is nil or invalid.
|
||||
func (s signedExecutionPayloadBid) IsNil() bool {
|
||||
if s.bid == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, err := WrappedROExecutionPayloadBid(s.bid.Message); err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return len(s.bid.Signature) != 96
|
||||
}
|
||||
|
||||
// IsNil checks if the execution payload bid is nil or has invalid fields.
|
||||
func (h executionPayloadBidGloas) IsNil() bool {
|
||||
if h.payload == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(h.payload.ParentBlockHash) != 32 ||
|
||||
len(h.payload.ParentBlockRoot) != 32 ||
|
||||
len(h.payload.BlockHash) != 32 ||
|
||||
len(h.payload.PrevRandao) != 32 ||
|
||||
len(h.payload.BlobKzgCommitmentsRoot) != 32 ||
|
||||
len(h.payload.FeeRecipient) != 20 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// WrappedROSignedExecutionPayloadBid creates a new read-only signed execution payload bid
|
||||
// wrapper from the given protobuf message.
|
||||
func WrappedROSignedExecutionPayloadBid(pb *ethpb.SignedExecutionPayloadBid) (interfaces.ROSignedExecutionPayloadBid, error) {
|
||||
wrapper := signedExecutionPayloadBid{bid: pb}
|
||||
if wrapper.IsNil() {
|
||||
return nil, consensus_types.ErrNilObjectWrapped
|
||||
}
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
// WrappedROExecutionPayloadBid creates a new read-only execution payload bid
|
||||
// wrapper for the Gloas fork from the given protobuf message.
|
||||
func WrappedROExecutionPayloadBid(pb *ethpb.ExecutionPayloadBid) (interfaces.ROExecutionPayloadBid, error) {
|
||||
wrapper := executionPayloadBidGloas{payload: pb}
|
||||
if wrapper.IsNil() {
|
||||
return nil, consensus_types.ErrNilObjectWrapped
|
||||
}
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
// Bid returns the execution payload bid as a read-only interface.
|
||||
func (s signedExecutionPayloadBid) Bid() (interfaces.ROExecutionPayloadBid, error) {
|
||||
return WrappedROExecutionPayloadBid(s.bid.Message)
|
||||
}
|
||||
|
||||
// SigningRoot computes the signing root for the execution payload bid with the given domain.
|
||||
func (s signedExecutionPayloadBid) SigningRoot(domain []byte) ([32]byte, error) {
|
||||
return signing.ComputeSigningRoot(s.bid.Message, domain)
|
||||
}
|
||||
|
||||
// Signature returns the BLS signature as a 96-byte array.
|
||||
func (s signedExecutionPayloadBid) Signature() [96]byte {
|
||||
return [96]byte(s.bid.Signature)
|
||||
}
|
||||
|
||||
// ParentBlockHash returns the hash of the parent execution block.
|
||||
func (h executionPayloadBidGloas) ParentBlockHash() [32]byte {
|
||||
return [32]byte(h.payload.ParentBlockHash)
|
||||
}
|
||||
|
||||
// ParentBlockRoot returns the beacon block root of the parent block.
|
||||
func (h executionPayloadBidGloas) ParentBlockRoot() [32]byte {
|
||||
return [32]byte(h.payload.ParentBlockRoot)
|
||||
}
|
||||
|
||||
// PrevRandao returns the previous randao value for the execution block.
|
||||
func (h executionPayloadBidGloas) PrevRandao() [32]byte {
|
||||
return [32]byte(h.payload.PrevRandao)
|
||||
}
|
||||
|
||||
// BlockHash returns the hash of the execution block.
|
||||
func (h executionPayloadBidGloas) BlockHash() [32]byte {
|
||||
return [32]byte(h.payload.BlockHash)
|
||||
}
|
||||
|
||||
// GasLimit returns the gas limit for the execution block.
|
||||
func (h executionPayloadBidGloas) GasLimit() uint64 {
|
||||
return h.payload.GasLimit
|
||||
}
|
||||
|
||||
// BuilderIndex returns the validator index of the builder who created this bid.
|
||||
func (h executionPayloadBidGloas) BuilderIndex() primitives.BuilderIndex {
|
||||
return h.payload.BuilderIndex
|
||||
}
|
||||
|
||||
// Slot returns the beacon chain slot for which this bid was created.
|
||||
func (h executionPayloadBidGloas) Slot() primitives.Slot {
|
||||
return h.payload.Slot
|
||||
}
|
||||
|
||||
// Value returns the payment value offered by the builder in Gwei.
|
||||
func (h executionPayloadBidGloas) Value() primitives.Gwei {
|
||||
return primitives.Gwei(h.payload.Value)
|
||||
}
|
||||
|
||||
// ExecutionPayment returns the execution payment offered by the builder.
|
||||
func (h executionPayloadBidGloas) ExecutionPayment() primitives.Gwei {
|
||||
return primitives.Gwei(h.payload.ExecutionPayment)
|
||||
}
|
||||
|
||||
// BlobKzgCommitmentsRoot returns the root of the KZG commitments for blobs.
|
||||
func (h executionPayloadBidGloas) BlobKzgCommitmentsRoot() [32]byte {
|
||||
return [32]byte(h.payload.BlobKzgCommitmentsRoot)
|
||||
}
|
||||
|
||||
// FeeRecipient returns the execution address that will receive the builder payment.
|
||||
func (h executionPayloadBidGloas) FeeRecipient() [20]byte {
|
||||
return [20]byte(h.payload.FeeRecipient)
|
||||
}
|
||||
127
consensus-types/blocks/signed_execution_bid_test.go
Normal file
127
consensus-types/blocks/signed_execution_bid_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package blocks_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
consensus_types "github.com/OffchainLabs/prysm/v7/consensus-types"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func validExecutionPayloadBid() *ethpb.ExecutionPayloadBid {
|
||||
return ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x01}, 32),
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x02}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x03}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
|
||||
GasLimit: 123,
|
||||
BuilderIndex: 5,
|
||||
Slot: 6,
|
||||
Value: 7,
|
||||
ExecutionPayment: 8,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x05}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x06}, 20),
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrappedROExecutionPayloadBid(t *testing.T) {
|
||||
t.Run("returns error on invalid lengths", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
mutate func(*ethpb.ExecutionPayloadBid)
|
||||
}{
|
||||
{
|
||||
name: "parent block hash",
|
||||
mutate: func(b *ethpb.ExecutionPayloadBid) { b.ParentBlockHash = []byte{0x01} },
|
||||
},
|
||||
{
|
||||
name: "parent block root",
|
||||
mutate: func(b *ethpb.ExecutionPayloadBid) { b.ParentBlockRoot = []byte{0x02} },
|
||||
},
|
||||
{
|
||||
name: "block hash",
|
||||
mutate: func(b *ethpb.ExecutionPayloadBid) { b.BlockHash = []byte{0x03} },
|
||||
},
|
||||
{
|
||||
name: "prev randao",
|
||||
mutate: func(b *ethpb.ExecutionPayloadBid) { b.PrevRandao = []byte{0x04} },
|
||||
},
|
||||
{
|
||||
name: "blob kzg commitments root",
|
||||
mutate: func(b *ethpb.ExecutionPayloadBid) { b.BlobKzgCommitmentsRoot = []byte{0x05} },
|
||||
},
|
||||
{
|
||||
name: "fee recipient",
|
||||
mutate: func(b *ethpb.ExecutionPayloadBid) { b.FeeRecipient = []byte{0x06} },
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
invalid := validExecutionPayloadBid()
|
||||
tc.mutate(invalid)
|
||||
|
||||
_, err := blocks.WrappedROExecutionPayloadBid(invalid)
|
||||
require.Equal(t, consensus_types.ErrNilObjectWrapped, err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wraps and exposes fields", func(t *testing.T) {
|
||||
bid := validExecutionPayloadBid()
|
||||
wrapped, err := blocks.WrappedROExecutionPayloadBid(bid)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, primitives.BuilderIndex(5), wrapped.BuilderIndex())
|
||||
require.Equal(t, primitives.Slot(6), wrapped.Slot())
|
||||
require.Equal(t, primitives.Gwei(7), wrapped.Value())
|
||||
require.Equal(t, primitives.Gwei(8), wrapped.ExecutionPayment())
|
||||
assert.DeepEqual(t, [32]byte(bytes.Repeat([]byte{0x01}, 32)), wrapped.ParentBlockHash())
|
||||
assert.DeepEqual(t, [32]byte(bytes.Repeat([]byte{0x02}, 32)), wrapped.ParentBlockRoot())
|
||||
assert.DeepEqual(t, [32]byte(bytes.Repeat([]byte{0x03}, 32)), wrapped.BlockHash())
|
||||
assert.DeepEqual(t, [32]byte(bytes.Repeat([]byte{0x04}, 32)), wrapped.PrevRandao())
|
||||
assert.DeepEqual(t, [32]byte(bytes.Repeat([]byte{0x05}, 32)), wrapped.BlobKzgCommitmentsRoot())
|
||||
assert.DeepEqual(t, [20]byte(bytes.Repeat([]byte{0x06}, 20)), wrapped.FeeRecipient())
|
||||
})
|
||||
}
|
||||
|
||||
func TestWrappedROSignedExecutionPayloadBid(t *testing.T) {
|
||||
t.Run("returns error for invalid signature length", func(t *testing.T) {
|
||||
signed := ðpb.SignedExecutionPayloadBid{
|
||||
Message: validExecutionPayloadBid(),
|
||||
Signature: bytes.Repeat([]byte{0xAA}, 95),
|
||||
}
|
||||
_, err := blocks.WrappedROSignedExecutionPayloadBid(signed)
|
||||
require.Equal(t, consensus_types.ErrNilObjectWrapped, err)
|
||||
})
|
||||
|
||||
t.Run("wraps and provides bid/signing data", func(t *testing.T) {
|
||||
sig := bytes.Repeat([]byte{0xAB}, 96)
|
||||
signed := ðpb.SignedExecutionPayloadBid{
|
||||
Message: validExecutionPayloadBid(),
|
||||
Signature: sig,
|
||||
}
|
||||
|
||||
wrapped, err := blocks.WrappedROSignedExecutionPayloadBid(signed)
|
||||
require.NoError(t, err)
|
||||
|
||||
bid, err := wrapped.Bid()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, primitives.Gwei(8), bid.ExecutionPayment())
|
||||
|
||||
gotSig := wrapped.Signature()
|
||||
assert.DeepEqual(t, [96]byte(sig), gotSig)
|
||||
|
||||
domain := bytes.Repeat([]byte{0xCC}, 32)
|
||||
wantRoot, err := signing.ComputeSigningRoot(signed.Message, domain)
|
||||
require.NoError(t, err)
|
||||
gotRoot, err := wrapped.SigningRoot(domain)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, wantRoot, gotRoot)
|
||||
})
|
||||
}
|
||||
@@ -6,6 +6,7 @@ go_library(
|
||||
"beacon_block.go",
|
||||
"error.go",
|
||||
"light_client.go",
|
||||
"signed_execution_payload_bid.go",
|
||||
"utils.go",
|
||||
"validator.go",
|
||||
],
|
||||
|
||||
28
consensus-types/interfaces/signed_execution_payload_bid.go
Normal file
28
consensus-types/interfaces/signed_execution_payload_bid.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
field_params "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
)
|
||||
|
||||
type ROSignedExecutionPayloadBid interface {
|
||||
Bid() (ROExecutionPayloadBid, error)
|
||||
Signature() [field_params.BLSSignatureLength]byte
|
||||
SigningRoot([]byte) ([32]byte, error)
|
||||
IsNil() bool
|
||||
}
|
||||
|
||||
type ROExecutionPayloadBid interface {
|
||||
ParentBlockHash() [32]byte
|
||||
ParentBlockRoot() [32]byte
|
||||
PrevRandao() [32]byte
|
||||
BlockHash() [32]byte
|
||||
GasLimit() uint64
|
||||
BuilderIndex() primitives.BuilderIndex
|
||||
Slot() primitives.Slot
|
||||
Value() primitives.Gwei
|
||||
ExecutionPayment() primitives.Gwei
|
||||
BlobKzgCommitmentsRoot() [32]byte
|
||||
FeeRecipient() [20]byte
|
||||
IsNil() bool
|
||||
}
|
||||
@@ -1,22 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Continuous Integration script to check that BUILD.bazel files are as expected
|
||||
# when generated from gazelle.
|
||||
# Continuous Integration script to check that BUILD.bazel files and deps.bzl
|
||||
# are as expected when generated from gazelle.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
exit_code=0
|
||||
|
||||
# Duplicate redirect 5 to stdout so that it can be captured, but still printed
|
||||
# nicely.
|
||||
exec 5>&1
|
||||
|
||||
# Run gazelle while piping a copy of the output to stdout via 5.
|
||||
changes=$(bazel --batch --bazelrc=.buildkite-bazelrc run //:gazelle -- fix --mode=diff | tee >(cat - >&5))
|
||||
echo "Checking deps.bzl is in sync with go.mod..."
|
||||
bazel --batch --bazelrc=.buildkite-bazelrc run //:gazelle -- update-repos -from_file=go.mod -to_macro=deps.bzl%prysm_deps -prune=true
|
||||
|
||||
# If the captured stdout is not empty then Gazelle has diffs.
|
||||
if [ -z "$changes" ]
|
||||
then
|
||||
echo "OK: Gazelle does not need to be run"
|
||||
exit 0
|
||||
if git diff --exit-code deps.bzl; then
|
||||
echo "OK: deps.bzl is in sync with go.mod"
|
||||
else
|
||||
echo "FAIL: Gazelle needs to be run"
|
||||
echo "Please run bazel run //:gazelle -- fix"
|
||||
exit 1
|
||||
echo ""
|
||||
echo "FAIL: deps.bzl is out of sync with go.mod"
|
||||
exit_code=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Checking BUILD.bazel files..."
|
||||
build_changes=$(bazel --batch --bazelrc=.buildkite-bazelrc run //:gazelle -- fix --mode=diff | tee >(cat - >&5)) || true
|
||||
|
||||
if [ -z "$build_changes" ]; then
|
||||
echo "OK: BUILD.bazel files are in sync"
|
||||
else
|
||||
echo "FAIL: BUILD.bazel files are out of sync"
|
||||
exit_code=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ $exit_code -eq 0 ]; then
|
||||
echo "All gazelle checks passed"
|
||||
else
|
||||
echo "Gazelle checks failed. Please run:"
|
||||
echo " bazel run //:gazelle -- update-repos -from_file=go.mod -to_macro=deps.bzl%prysm_deps -prune=true"
|
||||
echo " bazel run //:gazelle -- fix"
|
||||
fi
|
||||
|
||||
exit $exit_code
|
||||
|
||||
@@ -198,12 +198,12 @@ func CopyBuilderPendingPayment(original *BuilderPendingPayment) *BuilderPendingP
|
||||
|
||||
return &BuilderPendingPayment{
|
||||
Weight: original.Weight,
|
||||
Withdrawal: copyBuilderPendingWithdrawal(original.Withdrawal),
|
||||
Withdrawal: CopyBuilderPendingWithdrawal(original.Withdrawal),
|
||||
}
|
||||
}
|
||||
|
||||
// copyBuilderPendingWithdrawal creates a deep copy of a builder pending withdrawal.
|
||||
func copyBuilderPendingWithdrawal(original *BuilderPendingWithdrawal) *BuilderPendingWithdrawal {
|
||||
// CopyBuilderPendingWithdrawal creates a deep copy of a builder pending withdrawal.
|
||||
func CopyBuilderPendingWithdrawal(original *BuilderPendingWithdrawal) *BuilderPendingWithdrawal {
|
||||
if original == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -356,64 +356,6 @@ func TestCopyBlindedBeaconBlockBodyDeneb(t *testing.T) {
|
||||
assert.NotEmpty(t, bb, "Copied blinded beacon block body Deneb has empty fields")
|
||||
}
|
||||
|
||||
func TestCopyBuilderPendingPayment(t *testing.T) {
|
||||
t.Run("nil payment", func(t *testing.T) {
|
||||
if got := v1alpha1.CopyBuilderPendingPayment(nil); got != nil {
|
||||
t.Fatalf("CopyBuilderPendingPayment(nil) = %v, want nil", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nil withdrawal", func(t *testing.T) {
|
||||
original := &v1alpha1.BuilderPendingPayment{
|
||||
Weight: primitives.Gwei(1),
|
||||
}
|
||||
|
||||
copied := v1alpha1.CopyBuilderPendingPayment(original)
|
||||
if copied == original {
|
||||
t.Fatal("expected CopyBuilderPendingPayment to return a new pointer")
|
||||
}
|
||||
if !reflect.DeepEqual(copied, original) {
|
||||
t.Fatalf("CopyBuilderPendingPayment() = %v, want %v", copied, original)
|
||||
}
|
||||
if copied.Withdrawal != nil {
|
||||
t.Fatalf("CopyBuilderPendingPayment() withdrawal = %v, want nil", copied.Withdrawal)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("deep copy", func(t *testing.T) {
|
||||
original := &v1alpha1.BuilderPendingPayment{
|
||||
Weight: primitives.Gwei(2500),
|
||||
Withdrawal: &v1alpha1.BuilderPendingWithdrawal{
|
||||
FeeRecipient: []byte("fee_recipient_20_byt"),
|
||||
Amount: primitives.Gwei(10000),
|
||||
BuilderIndex: primitives.BuilderIndex(789),
|
||||
},
|
||||
}
|
||||
|
||||
copied := v1alpha1.CopyBuilderPendingPayment(original)
|
||||
if copied == original {
|
||||
t.Fatal("expected CopyBuilderPendingPayment to return a new pointer")
|
||||
}
|
||||
if copied.Withdrawal == original.Withdrawal {
|
||||
t.Fatal("expected nested Withdrawal to be copied")
|
||||
}
|
||||
if !reflect.DeepEqual(copied, original) {
|
||||
t.Fatalf("CopyBuilderPendingPayment() = %v, want %v", copied, original)
|
||||
}
|
||||
|
||||
want := proto.Clone(copied).(*v1alpha1.BuilderPendingPayment)
|
||||
|
||||
original.Weight++
|
||||
original.Withdrawal.Amount++
|
||||
original.Withdrawal.BuilderIndex++
|
||||
original.Withdrawal.FeeRecipient[0] ^= 0xFF
|
||||
|
||||
if !reflect.DeepEqual(copied, want) {
|
||||
t.Fatalf("copied payment mutated after changing source: got %v, want %v", copied, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func bytes(length int) []byte {
|
||||
b := make([]byte, length)
|
||||
for i := range length {
|
||||
@@ -1304,3 +1246,89 @@ func genPayloadAttestations(num int) []*v1alpha1.PayloadAttestation {
|
||||
}
|
||||
return pas
|
||||
}
|
||||
|
||||
func TestCopyBuilderPendingWithdrawal(t *testing.T) {
|
||||
t.Run("nil returns nil", func(t *testing.T) {
|
||||
if got := v1alpha1.CopyBuilderPendingWithdrawal(nil); got != nil {
|
||||
t.Fatalf("CopyBuilderPendingWithdrawal(nil) = %v, want nil", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("deep copy", func(t *testing.T) {
|
||||
original := &v1alpha1.BuilderPendingWithdrawal{
|
||||
FeeRecipient: []byte{0x01, 0x02},
|
||||
Amount: primitives.Gwei(10),
|
||||
BuilderIndex: primitives.BuilderIndex(3),
|
||||
}
|
||||
|
||||
copied := v1alpha1.CopyBuilderPendingWithdrawal(original)
|
||||
if copied == original {
|
||||
t.Fatalf("expected new pointer for withdrawal copy")
|
||||
}
|
||||
if !reflect.DeepEqual(copied, original) {
|
||||
t.Fatalf("CopyBuilderPendingWithdrawal() = %v, want %v", copied, original)
|
||||
}
|
||||
|
||||
original.FeeRecipient[0] = 0xFF
|
||||
original.Amount = primitives.Gwei(20)
|
||||
original.BuilderIndex = primitives.BuilderIndex(9)
|
||||
|
||||
if copied.FeeRecipient[0] == original.FeeRecipient[0] {
|
||||
t.Fatalf("fee recipient was not deep copied")
|
||||
}
|
||||
if copied.Amount != primitives.Gwei(10) {
|
||||
t.Fatalf("amount mutated on copy: %d", copied.Amount)
|
||||
}
|
||||
if copied.BuilderIndex != primitives.BuilderIndex(3) {
|
||||
t.Fatalf("builder index mutated on copy: %d", copied.BuilderIndex)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCopyBuilderPendingPayment(t *testing.T) {
|
||||
t.Run("nil returns nil", func(t *testing.T) {
|
||||
if got := v1alpha1.CopyBuilderPendingPayment(nil); got != nil {
|
||||
t.Fatalf("CopyBuilderPendingPayment(nil) = %v, want nil", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("deep copy", func(t *testing.T) {
|
||||
original := &v1alpha1.BuilderPendingPayment{
|
||||
Weight: primitives.Gwei(5),
|
||||
Withdrawal: &v1alpha1.BuilderPendingWithdrawal{
|
||||
FeeRecipient: []byte{0x0A, 0x0B},
|
||||
Amount: primitives.Gwei(50),
|
||||
BuilderIndex: primitives.BuilderIndex(7),
|
||||
},
|
||||
}
|
||||
|
||||
copied := v1alpha1.CopyBuilderPendingPayment(original)
|
||||
if copied == original {
|
||||
t.Fatalf("expected new pointer for payment copy")
|
||||
}
|
||||
if copied.Withdrawal == original.Withdrawal {
|
||||
t.Fatalf("expected nested withdrawal to be deep copied")
|
||||
}
|
||||
if !reflect.DeepEqual(copied, original) {
|
||||
t.Fatalf("CopyBuilderPendingPayment() = %v, want %v", copied, original)
|
||||
}
|
||||
|
||||
original.Weight = primitives.Gwei(10)
|
||||
original.Withdrawal.FeeRecipient[0] = 0xFF
|
||||
original.Withdrawal.Amount = primitives.Gwei(75)
|
||||
original.Withdrawal.BuilderIndex = primitives.BuilderIndex(9)
|
||||
|
||||
if copied.Weight != primitives.Gwei(5) {
|
||||
t.Fatalf("weight mutated on copy: %d", copied.Weight)
|
||||
}
|
||||
if copied.Withdrawal.FeeRecipient[0] == original.Withdrawal.FeeRecipient[0] {
|
||||
t.Fatalf("withdrawal fee recipient was not deep copied")
|
||||
}
|
||||
if copied.Withdrawal.Amount != primitives.Gwei(50) {
|
||||
t.Fatalf("withdrawal amount mutated on copy: %d", copied.Withdrawal.Amount)
|
||||
}
|
||||
if copied.Withdrawal.BuilderIndex != primitives.BuilderIndex(7) {
|
||||
t.Fatalf("withdrawal builder index mutated on copy: %d", copied.Withdrawal.BuilderIndex)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -289,12 +289,47 @@ func (r *testRunner) waitForMatchingHead(ctx context.Context, timeout time.Durat
|
||||
return errors.Wrap(err, "unexpected error requesting head block root from 'ref' beacon node")
|
||||
}
|
||||
if bytesutil.ToBytes32(cResp.HeadBlockRoot) == bytesutil.ToBytes32(rResp.HeadBlockRoot) {
|
||||
return nil
|
||||
// Head matches, now wait for node to exit optimistic mode.
|
||||
// For Fulu, the execution client may take additional time to sync and verify payloads.
|
||||
// Give extra time (up to 2 minutes) for optimistic status to clear.
|
||||
return r.waitForNonOptimistic(ctx, 2*time.Minute, check)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// waitForNonOptimistic waits for a node to exit optimistic mode, with a bounded timeout.
|
||||
// If the timeout is reached, it logs a warning but does not fail - the evaluator will
|
||||
// handle optimistic nodes gracefully by skipping finalized/justified checks.
|
||||
func (r *testRunner) waitForNonOptimistic(ctx context.Context, timeout time.Duration, conn *grpc.ClientConn) error {
|
||||
start := time.Now()
|
||||
deadline := start.Add(timeout)
|
||||
checkClient := eth.NewBeaconChainClient(conn)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
cResp, err := checkClient.GetChainHead(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
// If we can't get chain head, just continue - head already matched
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
if !cResp.OptimisticStatus {
|
||||
log.Infof("Node exited optimistic mode after %s", time.Since(start))
|
||||
return nil
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// Timeout reached but node is still optimistic - this is OK, evaluator handles it
|
||||
log.Warnf("Node still in optimistic mode after %s, continuing anyway (evaluator will handle)", timeout)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *testRunner) testCheckpointSync(ctx context.Context, g *errgroup.Group, i int, conns []*grpc.ClientConn, bnAPI, enr, minerEnr string) error {
|
||||
matchTimeout := 3 * time.Minute
|
||||
ethNode := eth1.NewNode(i, minerEnr)
|
||||
|
||||
@@ -129,11 +129,12 @@ func finishedSyncing(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) e
|
||||
}
|
||||
|
||||
func allNodesHaveSameHead(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error {
|
||||
headEpochs := make([]primitives.Epoch, len(conns))
|
||||
headSlots := make([]primitives.Slot, len(conns))
|
||||
justifiedRoots := make([][]byte, len(conns))
|
||||
prevJustifiedRoots := make([][]byte, len(conns))
|
||||
finalizedRoots := make([][]byte, len(conns))
|
||||
chainHeads := make([]*eth.ChainHead, len(conns))
|
||||
optimisticStatus := make([]bool, len(conns))
|
||||
g, _ := errgroup.WithContext(context.Background())
|
||||
|
||||
for i, conn := range conns {
|
||||
@@ -145,11 +146,12 @@ func allNodesHaveSameHead(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientCo
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "connection number=%d", conIdx)
|
||||
}
|
||||
headEpochs[conIdx] = chainHead.HeadEpoch
|
||||
headSlots[conIdx] = chainHead.HeadSlot
|
||||
justifiedRoots[conIdx] = chainHead.JustifiedBlockRoot
|
||||
prevJustifiedRoots[conIdx] = chainHead.PreviousJustifiedBlockRoot
|
||||
finalizedRoots[conIdx] = chainHead.FinalizedBlockRoot
|
||||
chainHeads[conIdx] = chainHead
|
||||
optimisticStatus[conIdx] = chainHead.OptimisticStatus
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -158,14 +160,26 @@ func allNodesHaveSameHead(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientCo
|
||||
}
|
||||
|
||||
for i := range conns {
|
||||
if headEpochs[0] != headEpochs[i] {
|
||||
// Allow head slots to differ by at most 2 slots to account for timing
|
||||
// differences when querying nodes and chain advancement during evaluation.
|
||||
slotDiff := headSlots[0] - headSlots[i]
|
||||
if headSlots[i] > headSlots[0] {
|
||||
slotDiff = headSlots[i] - headSlots[0]
|
||||
}
|
||||
if slotDiff > 2 {
|
||||
return fmt.Errorf(
|
||||
"received conflicting head epochs on node %d, expected %d, received %d",
|
||||
"received conflicting head slots on node %d, expected %d (±2), received %d",
|
||||
i,
|
||||
headEpochs[0],
|
||||
headEpochs[i],
|
||||
headSlots[0],
|
||||
headSlots[i],
|
||||
)
|
||||
}
|
||||
// Skip finalized/justified checks for nodes in optimistic mode.
|
||||
// Optimistic nodes haven't verified execution payloads yet, so their
|
||||
// finalized/justified state may lag behind fully verified nodes.
|
||||
if optimisticStatus[i] {
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(justifiedRoots[0], justifiedRoots[i]) {
|
||||
return fmt.Errorf(
|
||||
"received conflicting justified block roots on node %d, expected %#x, received %#x: %s and %s",
|
||||
|
||||
@@ -200,7 +200,8 @@ go_test(
|
||||
"fulu__sanity__blocks_test.go",
|
||||
"fulu__sanity__slots_test.go",
|
||||
"fulu__ssz_static__ssz_static_test.go",
|
||||
"gloas__operations__attestation_test.go",
|
||||
"gloas__epoch_processing__process_builder_pending_payments_test.go",
|
||||
"gloas__operations__execution_payload_header_test.go",
|
||||
"gloas__ssz_static__ssz_static_test.go",
|
||||
"phase0__epoch_processing__effective_balance_updates_test.go",
|
||||
"phase0__epoch_processing__epoch_processing_test.go",
|
||||
@@ -279,6 +280,7 @@ go_test(
|
||||
"//testing/spectest/shared/fulu/rewards:go_default_library",
|
||||
"//testing/spectest/shared/fulu/sanity:go_default_library",
|
||||
"//testing/spectest/shared/fulu/ssz_static:go_default_library",
|
||||
"//testing/spectest/shared/gloas/epoch_processing:go_default_library",
|
||||
"//testing/spectest/shared/gloas/operations:go_default_library",
|
||||
"//testing/spectest/shared/gloas/ssz_static:go_default_library",
|
||||
"//testing/spectest/shared/phase0/epoch_processing:go_default_library",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package mainnet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/epoch_processing"
|
||||
)
|
||||
|
||||
func TestMainnet_Gloas_EpochProcessing_ProcessBuilderPendingPayments(t *testing.T) {
|
||||
epoch_processing.RunBuilderPendingPaymentsTests(t, "mainnet")
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package mainnet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations"
|
||||
)
|
||||
|
||||
func TestMainnet_Gloas_Operations_Attestation(t *testing.T) {
|
||||
operations.RunAttestationTest(t, "mainnet")
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package mainnet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations"
|
||||
)
|
||||
|
||||
func TestMainnet_Gloas_Operations_ExecutionPayloadBid(t *testing.T) {
|
||||
operations.RunExecutionPayloadBidTest(t, "mainnet")
|
||||
}
|
||||
@@ -206,7 +206,8 @@ go_test(
|
||||
"fulu__sanity__blocks_test.go",
|
||||
"fulu__sanity__slots_test.go",
|
||||
"fulu__ssz_static__ssz_static_test.go",
|
||||
"gloas__operations__attestation_test.go",
|
||||
"gloas__epoch_processing__process_builder_pending_payments_test.go",
|
||||
"gloas__operations__execution_payload_bid_test.go",
|
||||
"gloas__ssz_static__ssz_static_test.go",
|
||||
"phase0__epoch_processing__effective_balance_updates_test.go",
|
||||
"phase0__epoch_processing__epoch_processing_test.go",
|
||||
@@ -289,6 +290,7 @@ go_test(
|
||||
"//testing/spectest/shared/fulu/rewards:go_default_library",
|
||||
"//testing/spectest/shared/fulu/sanity:go_default_library",
|
||||
"//testing/spectest/shared/fulu/ssz_static:go_default_library",
|
||||
"//testing/spectest/shared/gloas/epoch_processing:go_default_library",
|
||||
"//testing/spectest/shared/gloas/operations:go_default_library",
|
||||
"//testing/spectest/shared/gloas/ssz_static:go_default_library",
|
||||
"//testing/spectest/shared/phase0/epoch_processing:go_default_library",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package minimal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/epoch_processing"
|
||||
)
|
||||
|
||||
func TestMinimal_Gloas_EpochProcessing_ProcessBuilderPendingPayments(t *testing.T) {
|
||||
epoch_processing.RunBuilderPendingPaymentsTests(t, "minimal")
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package minimal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations"
|
||||
)
|
||||
|
||||
func TestMinimal_Gloas_Operations_Attestation(t *testing.T) {
|
||||
operations.RunAttestationTest(t, "minimal")
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package minimal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations"
|
||||
)
|
||||
|
||||
func TestMinimal_Gloas_Operations_ExecutionPayloadBid(t *testing.T) {
|
||||
operations.RunExecutionPayloadBidTest(t, "minimal")
|
||||
}
|
||||
@@ -12,6 +12,7 @@ go_library(
|
||||
"deposit.go",
|
||||
"deposit_request.go",
|
||||
"execution_payload.go",
|
||||
"execution_payload_bid.go",
|
||||
"proposer_slashing.go",
|
||||
"slashing.go",
|
||||
"sync_aggregate.go",
|
||||
@@ -26,6 +27,7 @@ go_library(
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/electra:go_default_library",
|
||||
"//beacon-chain/core/gloas:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/validators:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package operations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/spectest/utils"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
"github.com/golang/snappy"
|
||||
)
|
||||
|
||||
func runExecutionPayloadBidTest(t *testing.T, config string, fork string, objName string, block blockWithSSZObject, sszToState SSZToState, operationFn BlockOperation) {
|
||||
require.NoError(t, utils.SetConfig(t, config))
|
||||
testFolders, testsFolderPath := utils.TestFolders(t, config, fork, "operations/"+objName+"/pyspec_tests")
|
||||
if len(testFolders) == 0 {
|
||||
t.Fatalf("No test folders found for %s/%s/%s", config, fork, "operations/"+objName+"/pyspec_tests")
|
||||
}
|
||||
for _, folder := range testFolders {
|
||||
t.Run(folder.Name(), func(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
folderPath := path.Join(testsFolderPath, folder.Name())
|
||||
blockFile, err := util.BazelFileBytes(folderPath, "block.ssz_snappy")
|
||||
require.NoError(t, err)
|
||||
blockSSZ, err := snappy.Decode(nil /* dst */, blockFile)
|
||||
require.NoError(t, err, "Failed to decompress")
|
||||
blk, err := block(blockSSZ)
|
||||
require.NoError(t, err)
|
||||
RunBlockOperationTest(t, folderPath, blk, sszToState, operationFn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func RunExecutionPayloadBidTest(t *testing.T, config string, fork string, block blockWithSSZObject, sszToState SSZToState) {
|
||||
runExecutionPayloadBidTest(t, config, fork, "execution_payload_bid", block, sszToState, func(ctx context.Context, s state.BeaconState, b interfaces.ReadOnlySignedBeaconBlock) (state.BeaconState, error) {
|
||||
err := gloas.ProcessExecutionPayloadBid(s, b.Block())
|
||||
return s, err
|
||||
})
|
||||
}
|
||||
26
testing/spectest/shared/gloas/epoch_processing/BUILD.bazel
Normal file
26
testing/spectest/shared/gloas/epoch_processing/BUILD.bazel
Normal file
@@ -0,0 +1,26 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"builder_pending_payments.go",
|
||||
"helpers.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/epoch_processing",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/gloas:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/spectest/utils:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_golang_snappy//:go_default_library",
|
||||
"@com_github_google_go_cmp//cmp:go_default_library",
|
||||
"@io_bazel_rules_go//go/tools/bazel:go_default_library",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
"@org_golang_google_protobuf//testing/protocmp:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
package epoch_processing
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/spectest/utils"
|
||||
)
|
||||
|
||||
func RunBuilderPendingPaymentsTests(t *testing.T, config string) {
|
||||
require.NoError(t, utils.SetConfig(t, config))
|
||||
testFolders, testsFolderPath := utils.TestFolders(t, config, "gloas", "epoch_processing/builder_pending_payments/pyspec_tests")
|
||||
for _, folder := range testFolders {
|
||||
t.Run(folder.Name(), func(t *testing.T) {
|
||||
folderPath := path.Join(testsFolderPath, folder.Name())
|
||||
RunEpochOperationTest(t, folderPath, processBuilderPendingPayments)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func processBuilderPendingPayments(t *testing.T, st state.BeaconState) (state.BeaconState, error) {
|
||||
return st, gloas.ProcessBuilderPendingPayments(st)
|
||||
}
|
||||
76
testing/spectest/shared/gloas/epoch_processing/helpers.go
Normal file
76
testing/spectest/shared/gloas/epoch_processing/helpers.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package epoch_processing
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
"github.com/bazelbuild/rules_go/go/tools/bazel"
|
||||
"github.com/golang/snappy"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
)
|
||||
|
||||
type epochOperation func(*testing.T, state.BeaconState) (state.BeaconState, error)
|
||||
|
||||
// RunEpochOperationTest takes in the prestate and processes it through the
|
||||
// passed in epoch operation function and checks the post state with the expected post state.
|
||||
func RunEpochOperationTest(
|
||||
t *testing.T,
|
||||
testFolderPath string,
|
||||
operationFn epochOperation,
|
||||
) {
|
||||
preBeaconStateFile, err := util.BazelFileBytes(path.Join(testFolderPath, "pre.ssz_snappy"))
|
||||
require.NoError(t, err)
|
||||
preBeaconStateSSZ, err := snappy.Decode(nil /* dst */, preBeaconStateFile)
|
||||
require.NoError(t, err, "Failed to decompress")
|
||||
preBeaconStateBase := ðpb.BeaconStateGloas{}
|
||||
if err := preBeaconStateBase.UnmarshalSSZ(preBeaconStateSSZ); err != nil {
|
||||
t.Fatalf("Failed to unmarshal: %v", err)
|
||||
}
|
||||
preBeaconState, err := state_native.InitializeFromProtoGloas(preBeaconStateBase)
|
||||
require.NoError(t, err)
|
||||
|
||||
// If the post.ssz is not present, it means the test should fail on our end.
|
||||
postSSZFilepath, err := bazel.Runfile(path.Join(testFolderPath, "post.ssz_snappy"))
|
||||
postSSZExists := true
|
||||
if err != nil && strings.Contains(err.Error(), "could not locate file") {
|
||||
postSSZExists = false
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
beaconState, err := operationFn(t, preBeaconState)
|
||||
if postSSZExists {
|
||||
require.NoError(t, err)
|
||||
|
||||
postBeaconStateFile, err := os.ReadFile(postSSZFilepath) // #nosec G304
|
||||
require.NoError(t, err)
|
||||
postBeaconStateSSZ, err := snappy.Decode(nil /* dst */, postBeaconStateFile)
|
||||
require.NoError(t, err, "Failed to decompress")
|
||||
postBeaconState := ðpb.BeaconStateGloas{}
|
||||
if err := postBeaconState.UnmarshalSSZ(postBeaconStateSSZ); err != nil {
|
||||
t.Fatalf("Failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
pbState, err := state_native.ProtobufBeaconStateGloas(beaconState.ToProtoUnsafe())
|
||||
require.NoError(t, err)
|
||||
if !proto.Equal(pbState, postBeaconState) {
|
||||
t.Log(cmp.Diff(postBeaconState, pbState, protocmp.Transform()))
|
||||
t.Fatal("Post state does not match expected")
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Fatal("Did not fail when expected")
|
||||
}
|
||||
t.Logf("Expected failure; failure reason = %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,12 @@ go_library(
|
||||
name = "go_default_library",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"attestation.go",
|
||||
"execution_payload_bid.go",
|
||||
"helpers.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package operations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/altair"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
common "github.com/OffchainLabs/prysm/v7/testing/spectest/shared/common/operations"
|
||||
)
|
||||
|
||||
func blockWithAttestation(attestationSSZ []byte) (interfaces.SignedBeaconBlock, error) {
|
||||
att := ðpb.AttestationElectra{}
|
||||
if err := att.UnmarshalSSZ(attestationSSZ); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := ðpb.BeaconBlockGloas{}
|
||||
b.Body = ðpb.BeaconBlockBodyGloas{Attestations: []*ethpb.AttestationElectra{att}}
|
||||
return blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockGloas{Block: b})
|
||||
}
|
||||
|
||||
func RunAttestationTest(t *testing.T, config string) {
|
||||
common.RunAttestationTest(t, config, version.String(version.Gloas), blockWithAttestation, altair.ProcessAttestationsNoVerifySignature, sszToState)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package operations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
common "github.com/OffchainLabs/prysm/v7/testing/spectest/shared/common/operations"
|
||||
)
|
||||
|
||||
func blockWithSignedExecutionPayloadBid(blockSSZ []byte) (interfaces.SignedBeaconBlock, error) {
|
||||
var block ethpb.BeaconBlockGloas
|
||||
if err := block.UnmarshalSSZ(blockSSZ); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockGloas{Block: &block})
|
||||
}
|
||||
|
||||
func RunExecutionPayloadBidTest(t *testing.T, config string) {
|
||||
common.RunExecutionPayloadBidTest(t, config, version.String(version.Gloas), blockWithSignedExecutionPayloadBid, sszToState)
|
||||
}
|
||||
@@ -3,8 +3,6 @@ package operations
|
||||
import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
@@ -15,19 +13,3 @@ func sszToState(b []byte) (state.BeaconState, error) {
|
||||
}
|
||||
return state_native.InitializeFromProtoGloas(base)
|
||||
}
|
||||
|
||||
func sszToBlock(b []byte) (interfaces.SignedBeaconBlock, error) {
|
||||
base := ðpb.BeaconBlockGloas{}
|
||||
if err := base.UnmarshalSSZ(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockGloas{Block: base})
|
||||
}
|
||||
|
||||
func sszToBlockBody(b []byte) (interfaces.ReadOnlyBeaconBlockBody, error) {
|
||||
base := ðpb.BeaconBlockBodyGloas{}
|
||||
if err := base.UnmarshalSSZ(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blocks.NewBeaconBlockBody(base)
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ func (v *validator) signSlotWithSelectionProof(ctx context.Context, pubKey [fiel
|
||||
// such that any attestations from this slot have time to reach the beacon node
|
||||
// before creating the aggregated attestation.
|
||||
func (v *validator) waitToSlotTwoThirds(ctx context.Context, slot primitives.Slot) {
|
||||
v.waitUntilSlotComponent(ctx, slot, params.BeaconConfig().AggregrateDueBPS)
|
||||
v.waitUntilSlotComponent(ctx, slot, params.BeaconConfig().AggregateDueBPS)
|
||||
}
|
||||
|
||||
// This returns the signature of validator signing over aggregate and
|
||||
|
||||
@@ -257,7 +257,7 @@ func TestWaitForSlotTwoThird_WaitCorrectly(t *testing.T) {
|
||||
numOfSlots := primitives.Slot(4)
|
||||
slotDuration := params.BeaconConfig().SlotDuration()
|
||||
validator.genesisTime = currentTime.Add(-slotDuration * time.Duration(numOfSlots))
|
||||
timeToSleep := params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().AggregrateDueBPS)
|
||||
timeToSleep := params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().AggregateDueBPS)
|
||||
|
||||
twoThirdTime := currentTime.Add(timeToSleep)
|
||||
validator.waitToSlotTwoThirds(t.Context(), numOfSlots)
|
||||
|
||||
@@ -51,7 +51,7 @@ func (v *validator) slotComponentSpanName(component primitives.BP) string {
|
||||
switch component {
|
||||
case cfg.AttestationDueBPS:
|
||||
return "validator.waitAttestationWindow"
|
||||
case cfg.AggregrateDueBPS:
|
||||
case cfg.AggregateDueBPS:
|
||||
return "validator.waitAggregateWindow"
|
||||
case cfg.SyncMessageDueBPS:
|
||||
return "validator.waitSyncMessageWindow"
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestSlotComponentSpanName(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "aggregate",
|
||||
component: cfg.AggregrateDueBPS,
|
||||
component: cfg.AggregateDueBPS,
|
||||
expected: "validator.waitAggregateWindow",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -37,8 +37,16 @@ func (s *Server) AuthTokenInterceptor() grpc.UnaryServerInterceptor {
|
||||
// AuthTokenHandler is an HTTP handler to authorize a route.
|
||||
func (s *Server) AuthTokenHandler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// if it's not initialize or has a web prefix
|
||||
if (strings.Contains(r.URL.Path, api.WebApiUrlPrefix) || strings.Contains(r.URL.Path, api.KeymanagerApiPrefix)) && !strings.Contains(r.URL.Path, api.SystemLogsPrefix) {
|
||||
path := r.URL.Path
|
||||
needsAuth := strings.Contains(path, api.WebApiUrlPrefix) || strings.Contains(path, api.KeymanagerApiPrefix)
|
||||
// Protect direct (non-/api) web endpoints too; otherwise callers can bypass auth by hitting /v2/validator/*.
|
||||
if strings.HasPrefix(path, api.WebUrlPrefix) &&
|
||||
!strings.HasPrefix(path, api.WebUrlPrefix+"initialize") &&
|
||||
!strings.HasPrefix(path, api.WebUrlPrefix+"health/") {
|
||||
needsAuth = true
|
||||
}
|
||||
|
||||
if needsAuth && !strings.Contains(path, api.SystemLogsPrefix) {
|
||||
// ignore some routes
|
||||
reqToken := r.Header.Get("Authorization")
|
||||
if reqToken == "" {
|
||||
|
||||
@@ -107,6 +107,16 @@ func TestServer_AuthTokenHandler(t *testing.T) {
|
||||
require.NoError(t, json.Unmarshal(rr.Body.Bytes(), errJson))
|
||||
require.StringContains(t, "Unauthorized", errJson.Message)
|
||||
})
|
||||
t.Run("direct /v2 endpoint also needs auth token (no /api bypass)", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, "/v2/validator/beacon/status", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
testHandler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
errJson := &httputil.DefaultJsonError{}
|
||||
require.NoError(t, json.Unmarshal(rr.Body.Bytes(), errJson))
|
||||
require.StringContains(t, "Unauthorized", errJson.Message)
|
||||
})
|
||||
t.Run("initialize does not need auth", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, api.WebUrlPrefix+"initialize", http.NoBody)
|
||||
|
||||
Reference in New Issue
Block a user