mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-02-01 08:35:24 -05:00
Compare commits
2 Commits
e2e-debugg
...
process-ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69f6d32c01 | ||
|
|
fb4847deaa |
@@ -4,6 +4,9 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"bid.go",
|
"bid.go",
|
||||||
|
"deposit_request.go",
|
||||||
|
"log.go",
|
||||||
|
"payload.go",
|
||||||
"payload_attestation.go",
|
"payload_attestation.go",
|
||||||
"pending_payment.go",
|
"pending_payment.go",
|
||||||
"proposer_slashing.go",
|
"proposer_slashing.go",
|
||||||
@@ -12,6 +15,7 @@ go_library(
|
|||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
"//beacon-chain/core/helpers:go_default_library",
|
"//beacon-chain/core/helpers:go_default_library",
|
||||||
|
"//beacon-chain/core/requests:go_default_library",
|
||||||
"//beacon-chain/core/signing:go_default_library",
|
"//beacon-chain/core/signing:go_default_library",
|
||||||
"//beacon-chain/core/time:go_default_library",
|
"//beacon-chain/core/time:go_default_library",
|
||||||
"//beacon-chain/state:go_default_library",
|
"//beacon-chain/state:go_default_library",
|
||||||
@@ -25,9 +29,13 @@ go_library(
|
|||||||
"//crypto/bls/common:go_default_library",
|
"//crypto/bls/common:go_default_library",
|
||||||
"//crypto/hash:go_default_library",
|
"//crypto/hash:go_default_library",
|
||||||
"//encoding/bytesutil:go_default_library",
|
"//encoding/bytesutil:go_default_library",
|
||||||
|
"//encoding/ssz:go_default_library",
|
||||||
|
"//proto/engine/v1:go_default_library",
|
||||||
"//proto/prysm/v1alpha1:go_default_library",
|
"//proto/prysm/v1alpha1:go_default_library",
|
||||||
|
"//runtime/version:go_default_library",
|
||||||
"//time/slots:go_default_library",
|
"//time/slots:go_default_library",
|
||||||
"@com_github_pkg_errors//:go_default_library",
|
"@com_github_pkg_errors//:go_default_library",
|
||||||
|
"@com_github_sirupsen_logrus//:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,7 +43,9 @@ go_test(
|
|||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
"bid_test.go",
|
"bid_test.go",
|
||||||
|
"deposit_request_test.go",
|
||||||
"payload_attestation_test.go",
|
"payload_attestation_test.go",
|
||||||
|
"payload_test.go",
|
||||||
"pending_payment_test.go",
|
"pending_payment_test.go",
|
||||||
"proposer_slashing_test.go",
|
"proposer_slashing_test.go",
|
||||||
],
|
],
|
||||||
@@ -45,6 +55,7 @@ go_test(
|
|||||||
"//beacon-chain/core/signing:go_default_library",
|
"//beacon-chain/core/signing:go_default_library",
|
||||||
"//beacon-chain/state:go_default_library",
|
"//beacon-chain/state:go_default_library",
|
||||||
"//beacon-chain/state/state-native:go_default_library",
|
"//beacon-chain/state/state-native:go_default_library",
|
||||||
|
"//beacon-chain/state/testing:go_default_library",
|
||||||
"//config/params:go_default_library",
|
"//config/params:go_default_library",
|
||||||
"//consensus-types/blocks:go_default_library",
|
"//consensus-types/blocks:go_default_library",
|
||||||
"//consensus-types/interfaces:go_default_library",
|
"//consensus-types/interfaces:go_default_library",
|
||||||
@@ -52,6 +63,7 @@ go_test(
|
|||||||
"//crypto/bls:go_default_library",
|
"//crypto/bls:go_default_library",
|
||||||
"//crypto/bls/common:go_default_library",
|
"//crypto/bls/common:go_default_library",
|
||||||
"//encoding/bytesutil:go_default_library",
|
"//encoding/bytesutil:go_default_library",
|
||||||
|
"//encoding/ssz:go_default_library",
|
||||||
"//proto/engine/v1:go_default_library",
|
"//proto/engine/v1:go_default_library",
|
||||||
"//proto/prysm/v1alpha1:go_default_library",
|
"//proto/prysm/v1alpha1:go_default_library",
|
||||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||||
|
|||||||
174
beacon-chain/core/gloas/deposit_request.go
Normal file
174
beacon-chain/core/gloas/deposit_request.go
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package gloas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||||
|
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||||
|
"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"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func processDepositRequests(ctx context.Context, beaconState state.BeaconState, requests []*enginev1.DepositRequest) error {
|
||||||
|
if len(requests) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, receipt := range requests {
|
||||||
|
if err := processDepositRequest(beaconState, receipt); err != nil {
|
||||||
|
return errors.Wrap(err, "could not apply deposit request")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processDepositRequest processes the specific deposit request
|
||||||
|
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||||
|
// def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None:
|
||||||
|
//
|
||||||
|
// # [New in Gloas:EIP7732]
|
||||||
|
// builder_pubkeys = [b.pubkey for b in state.builders]
|
||||||
|
// validator_pubkeys = [v.pubkey for v in state.validators]
|
||||||
|
//
|
||||||
|
// # [New in Gloas:EIP7732]
|
||||||
|
// # Regardless of the withdrawal credentials prefix, if a builder/validator
|
||||||
|
// # already exists with this pubkey, apply the deposit to their balance
|
||||||
|
// is_builder = deposit_request.pubkey in builder_pubkeys
|
||||||
|
// is_validator = deposit_request.pubkey in validator_pubkeys
|
||||||
|
// is_builder_prefix = is_builder_withdrawal_credential(deposit_request.withdrawal_credentials)
|
||||||
|
// if is_builder or (is_builder_prefix and not is_validator):
|
||||||
|
//
|
||||||
|
// # Apply builder deposits immediately
|
||||||
|
// apply_deposit_for_builder(
|
||||||
|
// state,
|
||||||
|
// deposit_request.pubkey,
|
||||||
|
// deposit_request.withdrawal_credentials,
|
||||||
|
// deposit_request.amount,
|
||||||
|
// deposit_request.signature,
|
||||||
|
// )
|
||||||
|
// return
|
||||||
|
//
|
||||||
|
// # Add validator deposits to the queue
|
||||||
|
// state.pending_deposits.append(
|
||||||
|
// PendingDeposit(
|
||||||
|
// pubkey=deposit_request.pubkey,
|
||||||
|
// withdrawal_credentials=deposit_request.withdrawal_credentials,
|
||||||
|
// amount=deposit_request.amount,
|
||||||
|
// signature=deposit_request.signature,
|
||||||
|
// slot=state.slot,
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
func processDepositRequest(beaconState state.BeaconState, request *enginev1.DepositRequest) error {
|
||||||
|
if request == nil {
|
||||||
|
return errors.New("nil deposit request")
|
||||||
|
}
|
||||||
|
|
||||||
|
applied, err := applyBuilderDepositRequest(beaconState, request)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not apply builder deposit")
|
||||||
|
}
|
||||||
|
if applied {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := beaconState.AppendPendingDeposit(ðpb.PendingDeposit{
|
||||||
|
PublicKey: request.Pubkey,
|
||||||
|
WithdrawalCredentials: request.WithdrawalCredentials,
|
||||||
|
Amount: request.Amount,
|
||||||
|
Signature: request.Signature,
|
||||||
|
Slot: beaconState.Slot(),
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "could not append deposit request")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyBuilderDepositRequest(beaconState state.BeaconState, request *enginev1.DepositRequest) (bool, error) {
|
||||||
|
if beaconState.Version() < version.Gloas {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey := bytesutil.ToBytes48(request.Pubkey)
|
||||||
|
_, isValidator := beaconState.ValidatorIndexByPubkey(pubkey)
|
||||||
|
_, isBuilder := beaconState.BuilderIndexByPubkey(pubkey)
|
||||||
|
isBuilderPrefix := IsBuilderWithdrawalCredential(request.WithdrawalCredentials)
|
||||||
|
if !isBuilder && (!isBuilderPrefix || isValidator) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := applyDepositForBuilder(
|
||||||
|
beaconState,
|
||||||
|
request.Pubkey,
|
||||||
|
request.WithdrawalCredentials,
|
||||||
|
request.Amount,
|
||||||
|
request.Signature,
|
||||||
|
); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyDepositForBuilder processes an execution-layer deposit for a builder.
|
||||||
|
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||||
|
// def apply_deposit_for_builder(
|
||||||
|
//
|
||||||
|
// state: BeaconState,
|
||||||
|
// pubkey: BLSPubkey,
|
||||||
|
// withdrawal_credentials: Bytes32,
|
||||||
|
// amount: uint64,
|
||||||
|
// signature: BLSSignature,
|
||||||
|
//
|
||||||
|
// ) -> None:
|
||||||
|
//
|
||||||
|
// builder_pubkeys = [b.pubkey for b in state.builders]
|
||||||
|
// if pubkey not in builder_pubkeys:
|
||||||
|
// # Verify the deposit signature (proof of possession) which is not checked by the deposit contract
|
||||||
|
// if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature):
|
||||||
|
// add_builder_to_registry(state, pubkey, withdrawal_credentials, amount)
|
||||||
|
// else:
|
||||||
|
// # Increase balance by deposit amount
|
||||||
|
// builder_index = builder_pubkeys.index(pubkey)
|
||||||
|
// state.builders[builder_index].balance += amount
|
||||||
|
func applyDepositForBuilder(
|
||||||
|
beaconState state.BeaconState,
|
||||||
|
pubkey []byte,
|
||||||
|
withdrawalCredentials []byte,
|
||||||
|
amount uint64,
|
||||||
|
signature []byte,
|
||||||
|
) error {
|
||||||
|
pubkeyBytes := bytesutil.ToBytes48(pubkey)
|
||||||
|
if idx, exists := beaconState.BuilderIndexByPubkey(pubkeyBytes); exists {
|
||||||
|
return beaconState.IncreaseBuilderBalance(idx, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
valid, err := helpers.IsValidDepositSignature(ðpb.Deposit_Data{
|
||||||
|
PublicKey: pubkey,
|
||||||
|
WithdrawalCredentials: withdrawalCredentials,
|
||||||
|
Amount: amount,
|
||||||
|
Signature: signature,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not verify deposit signature")
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"pubkey": fmt.Sprintf("%x", pubkey),
|
||||||
|
}).Warn("ignoring builder deposit: invalid signature")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
withdrawalCredBytes := bytesutil.ToBytes32(withdrawalCredentials)
|
||||||
|
return beaconState.AddBuilderFromDeposit(pubkeyBytes, withdrawalCredBytes, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsBuilderWithdrawalCredential(withdrawalCredentials []byte) bool {
|
||||||
|
return len(withdrawalCredentials) == fieldparams.RootLength &&
|
||||||
|
withdrawalCredentials[0] == params.BeaconConfig().BuilderWithdrawalPrefixByte
|
||||||
|
}
|
||||||
150
beacon-chain/core/gloas/deposit_request_test.go
Normal file
150
beacon-chain/core/gloas/deposit_request_test.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package gloas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||||
|
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||||
|
stateTesting "github.com/OffchainLabs/prysm/v7/beacon-chain/state/testing"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||||
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||||
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProcessDepositRequests_EmptyAndNil(t *testing.T) {
|
||||||
|
st := newGloasState(t, nil, nil)
|
||||||
|
|
||||||
|
t.Run("empty requests continues", func(t *testing.T) {
|
||||||
|
err := processDepositRequests(t.Context(), st, []*enginev1.DepositRequest{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil request errors", func(t *testing.T) {
|
||||||
|
err := processDepositRequests(t.Context(), st, []*enginev1.DepositRequest{nil})
|
||||||
|
require.ErrorContains(t, "nil deposit request", err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessDepositRequest_BuilderDepositAddsBuilder(t *testing.T) {
|
||||||
|
sk, err := bls.RandKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cred := builderWithdrawalCredentials()
|
||||||
|
pd := stateTesting.GeneratePendingDeposit(t, sk, 1234, cred, 0)
|
||||||
|
req := depositRequestFromPending(pd, 1)
|
||||||
|
|
||||||
|
st := newGloasState(t, nil, nil)
|
||||||
|
err = processDepositRequest(st, req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
idx, ok := st.BuilderIndexByPubkey(toBytes48(req.Pubkey))
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
|
||||||
|
builder, err := st.Builder(idx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, builder)
|
||||||
|
require.DeepEqual(t, req.Pubkey, builder.Pubkey)
|
||||||
|
require.DeepEqual(t, []byte{cred[0]}, builder.Version)
|
||||||
|
require.DeepEqual(t, cred[12:], builder.ExecutionAddress)
|
||||||
|
require.Equal(t, uint64(1234), uint64(builder.Balance))
|
||||||
|
require.Equal(t, params.BeaconConfig().FarFutureEpoch, builder.WithdrawableEpoch)
|
||||||
|
|
||||||
|
pending, err := st.PendingDeposits()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, len(pending))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessDepositRequest_ExistingBuilderIncreasesBalance(t *testing.T) {
|
||||||
|
sk, err := bls.RandKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pubkey := sk.PublicKey().Marshal()
|
||||||
|
builders := []*ethpb.Builder{
|
||||||
|
{
|
||||||
|
Pubkey: pubkey,
|
||||||
|
Version: []byte{0},
|
||||||
|
ExecutionAddress: bytes.Repeat([]byte{0x11}, 20),
|
||||||
|
Balance: 5,
|
||||||
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
st := newGloasState(t, nil, builders)
|
||||||
|
|
||||||
|
cred := validatorWithdrawalCredentials()
|
||||||
|
pd := stateTesting.GeneratePendingDeposit(t, sk, 200, cred, 0)
|
||||||
|
req := depositRequestFromPending(pd, 9)
|
||||||
|
|
||||||
|
err = processDepositRequest(st, req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
idx, ok := st.BuilderIndexByPubkey(toBytes48(pubkey))
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
builder, err := st.Builder(idx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, uint64(205), uint64(builder.Balance))
|
||||||
|
|
||||||
|
pending, err := st.PendingDeposits()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, len(pending))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyDepositForBuilder_InvalidSignatureIgnoresDeposit(t *testing.T) {
|
||||||
|
sk, err := bls.RandKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cred := builderWithdrawalCredentials()
|
||||||
|
st := newGloasState(t, nil, nil)
|
||||||
|
err = applyDepositForBuilder(st, sk.PublicKey().Marshal(), cred[:], 100, make([]byte, 96))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, ok := st.BuilderIndexByPubkey(toBytes48(sk.PublicKey().Marshal()))
|
||||||
|
require.Equal(t, false, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGloasState(t *testing.T, validators []*ethpb.Validator, builders []*ethpb.Builder) state.BeaconState {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
DepositRequestsStartIndex: params.BeaconConfig().UnsetDepositRequestsStartIndex,
|
||||||
|
Validators: validators,
|
||||||
|
Balances: make([]uint64, len(validators)),
|
||||||
|
PendingDeposits: []*ethpb.PendingDeposit{},
|
||||||
|
Builders: builders,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
func depositRequestFromPending(pd *ethpb.PendingDeposit, index uint64) *enginev1.DepositRequest {
|
||||||
|
return &enginev1.DepositRequest{
|
||||||
|
Pubkey: pd.PublicKey,
|
||||||
|
WithdrawalCredentials: pd.WithdrawalCredentials,
|
||||||
|
Amount: pd.Amount,
|
||||||
|
Signature: pd.Signature,
|
||||||
|
Index: index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func builderWithdrawalCredentials() [32]byte {
|
||||||
|
var cred [32]byte
|
||||||
|
cred[0] = params.BeaconConfig().BuilderWithdrawalPrefixByte
|
||||||
|
copy(cred[12:], bytes.Repeat([]byte{0x22}, 20))
|
||||||
|
return cred
|
||||||
|
}
|
||||||
|
|
||||||
|
func validatorWithdrawalCredentials() [32]byte {
|
||||||
|
var cred [32]byte
|
||||||
|
cred[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||||
|
copy(cred[12:], bytes.Repeat([]byte{0x33}, 20))
|
||||||
|
return cred
|
||||||
|
}
|
||||||
|
|
||||||
|
func toBytes48(b []byte) [48]byte {
|
||||||
|
var out [48]byte
|
||||||
|
copy(out[:], b)
|
||||||
|
return out
|
||||||
|
}
|
||||||
9
beacon-chain/core/gloas/log.go
Normal file
9
beacon-chain/core/gloas/log.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// Code generated by hack/gen-logs.sh; DO NOT EDIT.
|
||||||
|
// This file is created and regenerated automatically. Anything added here might get removed.
|
||||||
|
package gloas
|
||||||
|
|
||||||
|
import "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
// The prefix for logs from this package will be the text after the last slash in the package path.
|
||||||
|
// If you wish to change this, you should add your desired name in the runtime/logging/logrus-prefixed-formatter/prefix-replacement.go file.
|
||||||
|
var log = logrus.WithField("package", "beacon-chain/core/gloas")
|
||||||
344
beacon-chain/core/gloas/payload.go
Normal file
344
beacon-chain/core/gloas/payload.go
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
package gloas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
requests "github.com/OffchainLabs/prysm/v7/beacon-chain/core/requests"
|
||||||
|
"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/interfaces"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/encoding/ssz"
|
||||||
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessExecutionPayload processes the signed execution payload envelope for the Gloas fork.
|
||||||
|
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||||
|
// def process_execution_payload(
|
||||||
|
//
|
||||||
|
// state: BeaconState,
|
||||||
|
// signed_envelope: SignedExecutionPayloadEnvelope,
|
||||||
|
// execution_engine: ExecutionEngine,
|
||||||
|
// verify: bool = True,
|
||||||
|
//
|
||||||
|
// ) -> None:
|
||||||
|
//
|
||||||
|
// envelope = signed_envelope.message
|
||||||
|
// payload = envelope.payload
|
||||||
|
//
|
||||||
|
// if verify:
|
||||||
|
// assert verify_execution_payload_envelope_signature(state, signed_envelope)
|
||||||
|
//
|
||||||
|
// previous_state_root = hash_tree_root(state)
|
||||||
|
// if state.latest_block_header.state_root == Root():
|
||||||
|
// state.latest_block_header.state_root = previous_state_root
|
||||||
|
//
|
||||||
|
// assert envelope.beacon_block_root == hash_tree_root(state.latest_block_header)
|
||||||
|
// assert envelope.slot == state.slot
|
||||||
|
//
|
||||||
|
// committed_bid = state.latest_execution_payload_bid
|
||||||
|
// assert envelope.builder_index == committed_bid.builder_index
|
||||||
|
// assert committed_bid.blob_kzg_commitments_root == hash_tree_root(envelope.blob_kzg_commitments)
|
||||||
|
// assert committed_bid.prev_randao == payload.prev_randao
|
||||||
|
//
|
||||||
|
// assert hash_tree_root(payload.withdrawals) == hash_tree_root(state.payload_expected_withdrawals)
|
||||||
|
//
|
||||||
|
// assert committed_bid.gas_limit == payload.gas_limit
|
||||||
|
// assert committed_bid.block_hash == payload.block_hash
|
||||||
|
// assert payload.parent_hash == state.latest_block_hash
|
||||||
|
// assert payload.timestamp == compute_time_at_slot(state, state.slot)
|
||||||
|
// assert (
|
||||||
|
// len(envelope.blob_kzg_commitments)
|
||||||
|
// <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block
|
||||||
|
// )
|
||||||
|
// versioned_hashes = [
|
||||||
|
// kzg_commitment_to_versioned_hash(commitment) for commitment in envelope.blob_kzg_commitments
|
||||||
|
// ]
|
||||||
|
// requests = envelope.execution_requests
|
||||||
|
// assert execution_engine.verify_and_notify_new_payload(
|
||||||
|
// NewPayloadRequest(
|
||||||
|
// execution_payload=payload,
|
||||||
|
// versioned_hashes=versioned_hashes,
|
||||||
|
// parent_beacon_block_root=state.latest_block_header.parent_root,
|
||||||
|
// execution_requests=requests,
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// for op in requests.deposits: process_deposit_request(state, op)
|
||||||
|
// for op in requests.withdrawals: process_withdrawal_request(state, op)
|
||||||
|
// for op in requests.consolidations: process_consolidation_request(state, op)
|
||||||
|
//
|
||||||
|
// payment = state.builder_pending_payments[SLOTS_PER_EPOCH + state.slot % SLOTS_PER_EPOCH]
|
||||||
|
// amount = payment.withdrawal.amount
|
||||||
|
// if amount > 0:
|
||||||
|
// state.builder_pending_withdrawals.append(payment.withdrawal)
|
||||||
|
// state.builder_pending_payments[SLOTS_PER_EPOCH + state.slot % SLOTS_PER_EPOCH] = (
|
||||||
|
// BuilderPendingPayment()
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// state.execution_payload_availability[state.slot % SLOTS_PER_HISTORICAL_ROOT] = 0b1
|
||||||
|
// state.latest_block_hash = payload.block_hash
|
||||||
|
//
|
||||||
|
// if verify:
|
||||||
|
// assert envelope.state_root == hash_tree_root(state)
|
||||||
|
func ProcessExecutionPayload(
|
||||||
|
ctx context.Context,
|
||||||
|
st state.BeaconState,
|
||||||
|
signedEnvelope interfaces.ROSignedExecutionPayloadEnvelope,
|
||||||
|
) error {
|
||||||
|
if err := verifyExecutionPayloadEnvelopeSignature(st, signedEnvelope); err != nil {
|
||||||
|
return errors.Wrap(err, "signature verification failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
latestHeader := st.LatestBlockHeader()
|
||||||
|
if len(latestHeader.StateRoot) == 0 || bytes.Equal(latestHeader.StateRoot, make([]byte, 32)) {
|
||||||
|
previousStateRoot, err := st.HashTreeRoot(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not compute state root")
|
||||||
|
}
|
||||||
|
latestHeader.StateRoot = previousStateRoot[:]
|
||||||
|
if err := st.SetLatestBlockHeader(latestHeader); err != nil {
|
||||||
|
return errors.Wrap(err, "could not set latest block header")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blockHeaderRoot, err := latestHeader.HashTreeRoot()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not compute block header root")
|
||||||
|
}
|
||||||
|
envelope, err := signedEnvelope.Envelope()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not get envelope from signed envelope")
|
||||||
|
}
|
||||||
|
beaconBlockRoot := envelope.BeaconBlockRoot()
|
||||||
|
if !bytes.Equal(beaconBlockRoot[:], blockHeaderRoot[:]) {
|
||||||
|
return errors.Errorf("envelope beacon block root does not match state latest block header root: envelope=%#x, header=%#x", beaconBlockRoot, blockHeaderRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
if envelope.Slot() != st.Slot() {
|
||||||
|
return errors.Errorf("envelope slot does not match state slot: envelope=%d, state=%d", envelope.Slot(), st.Slot())
|
||||||
|
}
|
||||||
|
|
||||||
|
latestBid, err := st.LatestExecutionPayloadBid()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not get latest execution payload bid")
|
||||||
|
}
|
||||||
|
if latestBid == nil {
|
||||||
|
return errors.New("latest execution payload bid is nil")
|
||||||
|
}
|
||||||
|
if envelope.BuilderIndex() != latestBid.BuilderIndex() {
|
||||||
|
return errors.Errorf("envelope builder index does not match committed bid builder index: envelope=%d, bid=%d", envelope.BuilderIndex(), latestBid.BuilderIndex())
|
||||||
|
}
|
||||||
|
|
||||||
|
envelopeBlobCommitments := envelope.BlobKzgCommitments()
|
||||||
|
envelopeBlobRoot, err := ssz.KzgCommitmentsRoot(envelopeBlobCommitments)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not compute envelope blob KZG commitments root")
|
||||||
|
}
|
||||||
|
committedBlobRoot := latestBid.BlobKzgCommitmentsRoot()
|
||||||
|
if !bytes.Equal(committedBlobRoot[:], envelopeBlobRoot[:]) {
|
||||||
|
return errors.Errorf("committed bid blob KZG commitments root does not match envelope: bid=%#x, envelope=%#x", committedBlobRoot, envelopeBlobRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := envelope.Execution()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not get execution payload from envelope")
|
||||||
|
}
|
||||||
|
withdrawals, err := payload.Withdrawals()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not get withdrawals from payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := st.WithdrawalsMatchPayloadExpected(withdrawals)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not validate payload withdrawals")
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return errors.New("payload withdrawals do not match expected withdrawals")
|
||||||
|
}
|
||||||
|
|
||||||
|
if latestBid.GasLimit() != payload.GasLimit() {
|
||||||
|
return errors.Errorf("committed bid gas limit does not match payload gas limit: bid=%d, payload=%d", latestBid.GasLimit(), payload.GasLimit())
|
||||||
|
}
|
||||||
|
|
||||||
|
latestBidPrevRandao := latestBid.PrevRandao()
|
||||||
|
if !bytes.Equal(payload.PrevRandao(), latestBidPrevRandao[:]) {
|
||||||
|
return errors.Errorf("payload prev randao does not match committed bid prev randao: payload=%#x, bid=%#x", payload.PrevRandao(), latestBidPrevRandao)
|
||||||
|
}
|
||||||
|
|
||||||
|
bidBlockHash := latestBid.BlockHash()
|
||||||
|
payloadBlockHash := payload.BlockHash()
|
||||||
|
if !bytes.Equal(bidBlockHash[:], payloadBlockHash) {
|
||||||
|
return errors.Errorf("committed bid block hash does not match payload block hash: bid=%#x, payload=%#x", bidBlockHash, payloadBlockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
latestBlockHash, err := st.LatestBlockHash()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not get latest block hash")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(payload.ParentHash(), latestBlockHash[:]) {
|
||||||
|
return errors.Errorf("payload parent hash does not match state latest block hash: payload=%#x, state=%#x", payload.ParentHash(), latestBlockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := slots.StartTime(st.GenesisTime(), st.Slot())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not compute timestamp")
|
||||||
|
}
|
||||||
|
if payload.Timestamp() != uint64(t.Unix()) {
|
||||||
|
return errors.Errorf("payload timestamp does not match expected timestamp: payload=%d, expected=%d", payload.Timestamp(), uint64(t.Unix()))
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := params.BeaconConfig()
|
||||||
|
maxBlobsPerBlock := cfg.MaxBlobsPerBlock(envelope.Slot())
|
||||||
|
if len(envelopeBlobCommitments) > maxBlobsPerBlock {
|
||||||
|
return errors.Errorf("too many blob KZG commitments: got=%d, max=%d", len(envelopeBlobCommitments), maxBlobsPerBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := processExecutionRequests(ctx, st, envelope.ExecutionRequests()); err != nil {
|
||||||
|
return errors.Wrap(err, "could not process execution requests")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := st.QueueBuilderPayment(); err != nil {
|
||||||
|
return errors.Wrap(err, "could not queue builder payment")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := st.SetExecutionPayloadAvailability(st.Slot(), true); err != nil {
|
||||||
|
return errors.Wrap(err, "could not set execution payload availability")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := st.SetLatestBlockHash([32]byte(payload.BlockHash())); err != nil {
|
||||||
|
return errors.Wrap(err, "could not set latest block hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := st.HashTreeRoot(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not get hash tree root")
|
||||||
|
}
|
||||||
|
if r != envelope.StateRoot() {
|
||||||
|
return fmt.Errorf("state root mismatch: expected %#x, got %#x", envelope.StateRoot(), r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func envelopePublicKey(st state.BeaconState, builderIdx primitives.BuilderIndex) (bls.PublicKey, error) {
|
||||||
|
if builderIdx == params.BeaconConfig().BuilderIndexSelfBuild {
|
||||||
|
return proposerPublicKey(st)
|
||||||
|
}
|
||||||
|
return builderPublicKey(st, builderIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func proposerPublicKey(st state.BeaconState) (bls.PublicKey, error) {
|
||||||
|
header := st.LatestBlockHeader()
|
||||||
|
if header == nil {
|
||||||
|
return nil, fmt.Errorf("latest block header is nil")
|
||||||
|
}
|
||||||
|
proposerPubkey := st.PubkeyAtIndex(header.ProposerIndex)
|
||||||
|
publicKey, err := bls.PublicKeyFromBytes(proposerPubkey[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid proposer public key: %w", err)
|
||||||
|
}
|
||||||
|
return publicKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func builderPublicKey(st state.BeaconState, builderIdx primitives.BuilderIndex) (bls.PublicKey, error) {
|
||||||
|
builder, err := st.Builder(builderIdx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get builder: %w", err)
|
||||||
|
}
|
||||||
|
if builder == nil {
|
||||||
|
return nil, fmt.Errorf("builder at index %d not found", builderIdx)
|
||||||
|
}
|
||||||
|
publicKey, err := bls.PublicKeyFromBytes(builder.Pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid builder public key: %w", err)
|
||||||
|
}
|
||||||
|
return publicKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processExecutionRequests processes deposits, withdrawals, and consolidations from execution requests.
|
||||||
|
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||||
|
// for op in requests.deposits: process_deposit_request(state, op)
|
||||||
|
// for op in requests.withdrawals: process_withdrawal_request(state, op)
|
||||||
|
// for op in requests.consolidations: process_consolidation_request(state, op)
|
||||||
|
func processExecutionRequests(ctx context.Context, st state.BeaconState, rqs *enginev1.ExecutionRequests) error {
|
||||||
|
if err := processDepositRequests(ctx, st, rqs.Deposits); err != nil {
|
||||||
|
return errors.Wrap(err, "could not process deposit requests")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
st, err = requests.ProcessWithdrawalRequests(ctx, st, rqs.Withdrawals)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not process withdrawal requests")
|
||||||
|
}
|
||||||
|
err = requests.ProcessConsolidationRequests(ctx, st, rqs.Consolidations)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not process consolidation requests")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyExecutionPayloadEnvelopeSignature verifies the BLS signature on a signed execution payload envelope.
|
||||||
|
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||||
|
// builder_index = signed_envelope.message.builder_index
|
||||||
|
// if builder_index == BUILDER_INDEX_SELF_BUILD:
|
||||||
|
//
|
||||||
|
// validator_index = state.latest_block_header.proposer_index
|
||||||
|
// pubkey = state.validators[validator_index].pubkey
|
||||||
|
//
|
||||||
|
// else:
|
||||||
|
//
|
||||||
|
// pubkey = state.builders[builder_index].pubkey
|
||||||
|
//
|
||||||
|
// signing_root = compute_signing_root(
|
||||||
|
//
|
||||||
|
// signed_envelope.message, get_domain(state, DOMAIN_BEACON_BUILDER)
|
||||||
|
//
|
||||||
|
// )
|
||||||
|
// return bls.Verify(pubkey, signing_root, signed_envelope.signature)
|
||||||
|
func verifyExecutionPayloadEnvelopeSignature(st state.BeaconState, signedEnvelope interfaces.ROSignedExecutionPayloadEnvelope) error {
|
||||||
|
envelope, err := signedEnvelope.Envelope()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get envelope: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
builderIdx := envelope.BuilderIndex()
|
||||||
|
publicKey, err := envelopePublicKey(st, builderIdx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signatureBytes := signedEnvelope.Signature()
|
||||||
|
signature, err := bls.SignatureFromBytes(signatureBytes[:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid signature format: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentEpoch := slots.ToEpoch(envelope.Slot())
|
||||||
|
domain, err := signing.Domain(
|
||||||
|
st.Fork(),
|
||||||
|
currentEpoch,
|
||||||
|
params.BeaconConfig().DomainBeaconBuilder,
|
||||||
|
st.GenesisValidatorsRoot(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to compute signing domain: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signingRoot, err := signedEnvelope.SigningRoot(domain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to compute signing root: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !signature.Verify(publicKey, signingRoot[:]) {
|
||||||
|
return fmt.Errorf("signature verification failed: %w", signing.ErrSigFailedToVerify)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
360
beacon-chain/core/gloas/payload_test.go
Normal file
360
beacon-chain/core/gloas/payload_test.go
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
package gloas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||||
|
"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/blocks"
|
||||||
|
"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/encoding/ssz"
|
||||||
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||||
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type payloadFixture struct {
|
||||||
|
state state.BeaconState
|
||||||
|
signed interfaces.ROSignedExecutionPayloadEnvelope
|
||||||
|
signedProto *ethpb.SignedExecutionPayloadEnvelope
|
||||||
|
envelope *ethpb.ExecutionPayloadEnvelope
|
||||||
|
payload *enginev1.ExecutionPayloadDeneb
|
||||||
|
slot primitives.Slot
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPayloadFixture(t *testing.T, mutate func(payload *enginev1.ExecutionPayloadDeneb, bid *ethpb.ExecutionPayloadBid, envelope *ethpb.ExecutionPayloadEnvelope)) payloadFixture {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
cfg := params.BeaconConfig()
|
||||||
|
slot := primitives.Slot(5)
|
||||||
|
builderIdx := primitives.BuilderIndex(0)
|
||||||
|
|
||||||
|
sk, err := bls.RandKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pk := sk.PublicKey().Marshal()
|
||||||
|
|
||||||
|
randao := bytes.Repeat([]byte{0xAA}, 32)
|
||||||
|
parentHash := bytes.Repeat([]byte{0xBB}, 32)
|
||||||
|
blockHash := bytes.Repeat([]byte{0xCC}, 32)
|
||||||
|
|
||||||
|
withdrawals := []*enginev1.Withdrawal{
|
||||||
|
{Index: 0, ValidatorIndex: 1, Address: bytes.Repeat([]byte{0x01}, 20), Amount: 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
blobCommitments := [][]byte{}
|
||||||
|
blobRoot, err := ssz.KzgCommitmentsRoot(blobCommitments)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
payload := &enginev1.ExecutionPayloadDeneb{
|
||||||
|
ParentHash: parentHash,
|
||||||
|
FeeRecipient: bytes.Repeat([]byte{0x01}, 20),
|
||||||
|
StateRoot: bytes.Repeat([]byte{0x02}, 32),
|
||||||
|
ReceiptsRoot: bytes.Repeat([]byte{0x03}, 32),
|
||||||
|
LogsBloom: bytes.Repeat([]byte{0x04}, 256),
|
||||||
|
PrevRandao: randao,
|
||||||
|
BlockNumber: 1,
|
||||||
|
GasLimit: 1,
|
||||||
|
GasUsed: 0,
|
||||||
|
Timestamp: 100,
|
||||||
|
ExtraData: []byte{},
|
||||||
|
BaseFeePerGas: bytes.Repeat([]byte{0x05}, 32),
|
||||||
|
BlockHash: blockHash,
|
||||||
|
Transactions: [][]byte{},
|
||||||
|
Withdrawals: withdrawals,
|
||||||
|
BlobGasUsed: 0,
|
||||||
|
ExcessBlobGas: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
bid := ðpb.ExecutionPayloadBid{
|
||||||
|
ParentBlockHash: parentHash,
|
||||||
|
ParentBlockRoot: bytes.Repeat([]byte{0xDD}, 32),
|
||||||
|
BlockHash: blockHash,
|
||||||
|
PrevRandao: randao,
|
||||||
|
GasLimit: 1,
|
||||||
|
BuilderIndex: builderIdx,
|
||||||
|
Slot: slot,
|
||||||
|
Value: 0,
|
||||||
|
ExecutionPayment: 0,
|
||||||
|
BlobKzgCommitmentsRoot: blobRoot[:],
|
||||||
|
FeeRecipient: bytes.Repeat([]byte{0xEE}, 20),
|
||||||
|
}
|
||||||
|
|
||||||
|
header := ðpb.BeaconBlockHeader{
|
||||||
|
Slot: slot,
|
||||||
|
ParentRoot: bytes.Repeat([]byte{0x11}, 32),
|
||||||
|
StateRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||||
|
BodyRoot: bytes.Repeat([]byte{0x33}, 32),
|
||||||
|
}
|
||||||
|
headerRoot, err := header.HashTreeRoot()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
envelope := ðpb.ExecutionPayloadEnvelope{
|
||||||
|
Slot: slot,
|
||||||
|
BuilderIndex: builderIdx,
|
||||||
|
BeaconBlockRoot: headerRoot[:],
|
||||||
|
Payload: payload,
|
||||||
|
BlobKzgCommitments: blobCommitments,
|
||||||
|
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if mutate != nil {
|
||||||
|
mutate(payload, bid, envelope)
|
||||||
|
}
|
||||||
|
|
||||||
|
genesisRoot := bytes.Repeat([]byte{0xAB}, 32)
|
||||||
|
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||||
|
stateRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||||
|
for i := range blockRoots {
|
||||||
|
blockRoots[i] = bytes.Repeat([]byte{0x44}, 32)
|
||||||
|
stateRoots[i] = bytes.Repeat([]byte{0x55}, 32)
|
||||||
|
}
|
||||||
|
randaoMixes := make([][]byte, cfg.EpochsPerHistoricalVector)
|
||||||
|
for i := range randaoMixes {
|
||||||
|
randaoMixes[i] = randao
|
||||||
|
}
|
||||||
|
|
||||||
|
withdrawalCreds := make([]byte, 32)
|
||||||
|
withdrawalCreds[0] = cfg.ETH1AddressWithdrawalPrefixByte
|
||||||
|
|
||||||
|
eth1Data := ðpb.Eth1Data{
|
||||||
|
DepositRoot: bytes.Repeat([]byte{0x66}, 32),
|
||||||
|
DepositCount: 0,
|
||||||
|
BlockHash: bytes.Repeat([]byte{0x77}, 32),
|
||||||
|
}
|
||||||
|
|
||||||
|
vals := []*ethpb.Validator{
|
||||||
|
{
|
||||||
|
PublicKey: pk,
|
||||||
|
WithdrawalCredentials: withdrawalCreds,
|
||||||
|
EffectiveBalance: cfg.MinActivationBalance + 1_000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
balances := []uint64{cfg.MinActivationBalance + 1_000}
|
||||||
|
|
||||||
|
payments := make([]*ethpb.BuilderPendingPayment, cfg.SlotsPerEpoch*2)
|
||||||
|
for i := range payments {
|
||||||
|
payments[i] = ðpb.BuilderPendingPayment{
|
||||||
|
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||||
|
FeeRecipient: make([]byte, 20),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executionPayloadAvailability := make([]byte, cfg.SlotsPerHistoricalRoot/8)
|
||||||
|
|
||||||
|
builders := make([]*ethpb.Builder, builderIdx+1)
|
||||||
|
builders[builderIdx] = ðpb.Builder{
|
||||||
|
Pubkey: pk,
|
||||||
|
Version: []byte{0},
|
||||||
|
ExecutionAddress: bytes.Repeat([]byte{0x09}, 20),
|
||||||
|
Balance: 0,
|
||||||
|
DepositEpoch: 0,
|
||||||
|
WithdrawableEpoch: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
genesisTime := uint64(0)
|
||||||
|
slotSeconds := cfg.SecondsPerSlot * uint64(slot)
|
||||||
|
if payload.Timestamp > slotSeconds {
|
||||||
|
genesisTime = payload.Timestamp - slotSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
stProto := ðpb.BeaconStateGloas{
|
||||||
|
Slot: slot,
|
||||||
|
GenesisTime: genesisTime,
|
||||||
|
GenesisValidatorsRoot: genesisRoot,
|
||||||
|
Fork: ðpb.Fork{
|
||||||
|
CurrentVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||||
|
PreviousVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||||
|
Epoch: 0,
|
||||||
|
},
|
||||||
|
LatestBlockHeader: header,
|
||||||
|
BlockRoots: blockRoots,
|
||||||
|
StateRoots: stateRoots,
|
||||||
|
RandaoMixes: randaoMixes,
|
||||||
|
Eth1Data: eth1Data,
|
||||||
|
Validators: vals,
|
||||||
|
Balances: balances,
|
||||||
|
LatestBlockHash: payload.ParentHash,
|
||||||
|
LatestExecutionPayloadBid: bid,
|
||||||
|
BuilderPendingPayments: payments,
|
||||||
|
ExecutionPayloadAvailability: executionPayloadAvailability,
|
||||||
|
BuilderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{},
|
||||||
|
PayloadExpectedWithdrawals: payload.Withdrawals,
|
||||||
|
Builders: builders,
|
||||||
|
}
|
||||||
|
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(stProto)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := st.Copy()
|
||||||
|
ctx := context.Background()
|
||||||
|
require.NoError(t, processExecutionRequests(ctx, expected, envelope.ExecutionRequests))
|
||||||
|
require.NoError(t, expected.QueueBuilderPayment())
|
||||||
|
require.NoError(t, expected.SetExecutionPayloadAvailability(slot, true))
|
||||||
|
var blockHashArr [32]byte
|
||||||
|
copy(blockHashArr[:], payload.BlockHash)
|
||||||
|
require.NoError(t, expected.SetLatestBlockHash(blockHashArr))
|
||||||
|
expectedRoot, err := expected.HashTreeRoot(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
envelope.StateRoot = expectedRoot[:]
|
||||||
|
|
||||||
|
epoch := slots.ToEpoch(slot)
|
||||||
|
domain, err := signing.Domain(st.Fork(), epoch, cfg.DomainBeaconBuilder, st.GenesisValidatorsRoot())
|
||||||
|
require.NoError(t, err)
|
||||||
|
signingRoot, err := signing.ComputeSigningRoot(envelope, domain)
|
||||||
|
require.NoError(t, err)
|
||||||
|
signature := sk.Sign(signingRoot[:]).Marshal()
|
||||||
|
|
||||||
|
signedProto := ðpb.SignedExecutionPayloadEnvelope{
|
||||||
|
Message: envelope,
|
||||||
|
Signature: signature,
|
||||||
|
}
|
||||||
|
signed, err := blocks.WrappedROSignedExecutionPayloadEnvelope(signedProto)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return payloadFixture{
|
||||||
|
state: st,
|
||||||
|
signed: signed,
|
||||||
|
signedProto: signedProto,
|
||||||
|
envelope: envelope,
|
||||||
|
payload: payload,
|
||||||
|
slot: slot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessExecutionPayload_Success(t *testing.T) {
|
||||||
|
fixture := buildPayloadFixture(t, nil)
|
||||||
|
require.NoError(t, ProcessExecutionPayload(t.Context(), fixture.state, fixture.signed))
|
||||||
|
|
||||||
|
latestHash, err := fixture.state.LatestBlockHash()
|
||||||
|
require.NoError(t, err)
|
||||||
|
var expectedHash [32]byte
|
||||||
|
copy(expectedHash[:], fixture.payload.BlockHash)
|
||||||
|
require.Equal(t, expectedHash, latestHash)
|
||||||
|
|
||||||
|
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||||
|
paymentIndex := slotsPerEpoch + (fixture.slot % slotsPerEpoch)
|
||||||
|
payments, err := fixture.state.BuilderPendingPayments()
|
||||||
|
require.NoError(t, err)
|
||||||
|
payment := payments[paymentIndex]
|
||||||
|
require.NotNil(t, payment)
|
||||||
|
require.Equal(t, primitives.Gwei(0), payment.Withdrawal.Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessExecutionPayload_PrevRandaoMismatch(t *testing.T) {
|
||||||
|
fixture := buildPayloadFixture(t, func(_ *enginev1.ExecutionPayloadDeneb, bid *ethpb.ExecutionPayloadBid, _ *ethpb.ExecutionPayloadEnvelope) {
|
||||||
|
bid.PrevRandao = bytes.Repeat([]byte{0xFF}, 32)
|
||||||
|
})
|
||||||
|
|
||||||
|
err := ProcessExecutionPayload(t.Context(), fixture.state, fixture.signed)
|
||||||
|
require.ErrorContains(t, "prev randao", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueueBuilderPayment_ZeroAmountClearsSlot(t *testing.T) {
|
||||||
|
fixture := buildPayloadFixture(t, nil)
|
||||||
|
|
||||||
|
require.NoError(t, fixture.state.QueueBuilderPayment())
|
||||||
|
|
||||||
|
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||||
|
paymentIndex := slotsPerEpoch + (fixture.slot % slotsPerEpoch)
|
||||||
|
payments, err := fixture.state.BuilderPendingPayments()
|
||||||
|
require.NoError(t, err)
|
||||||
|
payment := payments[paymentIndex]
|
||||||
|
require.NotNil(t, payment)
|
||||||
|
require.Equal(t, primitives.Gwei(0), payment.Withdrawal.Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyExecutionPayloadEnvelopeSignature(t *testing.T) {
|
||||||
|
fixture := buildPayloadFixture(t, nil)
|
||||||
|
|
||||||
|
t.Run("self build", func(t *testing.T) {
|
||||||
|
proposerSk, err := bls.RandKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
proposerPk := proposerSk.PublicKey().Marshal()
|
||||||
|
|
||||||
|
stPb, ok := fixture.state.ToProtoUnsafe().(*ethpb.BeaconStateGloas)
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
stPb = proto.Clone(stPb).(*ethpb.BeaconStateGloas)
|
||||||
|
stPb.Validators[0].PublicKey = proposerPk
|
||||||
|
st, err := state_native.InitializeFromProtoUnsafeGloas(stPb)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
msg := proto.Clone(fixture.signedProto.Message).(*ethpb.ExecutionPayloadEnvelope)
|
||||||
|
msg.BuilderIndex = params.BeaconConfig().BuilderIndexSelfBuild
|
||||||
|
msg.BlobKzgCommitments = []([]byte){}
|
||||||
|
|
||||||
|
epoch := slots.ToEpoch(msg.Slot)
|
||||||
|
domain, err := signing.Domain(st.Fork(), epoch, params.BeaconConfig().DomainBeaconBuilder, st.GenesisValidatorsRoot())
|
||||||
|
require.NoError(t, err)
|
||||||
|
signingRoot, err := signing.ComputeSigningRoot(msg, domain)
|
||||||
|
require.NoError(t, err)
|
||||||
|
signature := proposerSk.Sign(signingRoot[:]).Marshal()
|
||||||
|
|
||||||
|
signedProto := ðpb.SignedExecutionPayloadEnvelope{
|
||||||
|
Message: msg,
|
||||||
|
Signature: signature,
|
||||||
|
}
|
||||||
|
signed, err := blocks.WrappedROSignedExecutionPayloadEnvelope(signedProto)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NoError(t, verifyExecutionPayloadEnvelopeSignature(st, signed))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("builder", func(t *testing.T) {
|
||||||
|
signed, err := blocks.WrappedROSignedExecutionPayloadEnvelope(fixture.signedProto)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NoError(t, verifyExecutionPayloadEnvelopeSignature(fixture.state, signed))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid signature", func(t *testing.T) {
|
||||||
|
t.Run("self build", func(t *testing.T) {
|
||||||
|
proposerSk, err := bls.RandKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
proposerPk := proposerSk.PublicKey().Marshal()
|
||||||
|
|
||||||
|
stPb, ok := fixture.state.ToProtoUnsafe().(*ethpb.BeaconStateGloas)
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
stPb = proto.Clone(stPb).(*ethpb.BeaconStateGloas)
|
||||||
|
stPb.Validators[0].PublicKey = proposerPk
|
||||||
|
st, err := state_native.InitializeFromProtoUnsafeGloas(stPb)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
msg := proto.Clone(fixture.signedProto.Message).(*ethpb.ExecutionPayloadEnvelope)
|
||||||
|
msg.BuilderIndex = params.BeaconConfig().BuilderIndexSelfBuild
|
||||||
|
if msg.BlobKzgCommitments == nil {
|
||||||
|
msg.BlobKzgCommitments = [][]byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
signedProto := ðpb.SignedExecutionPayloadEnvelope{
|
||||||
|
Message: msg,
|
||||||
|
Signature: bytes.Repeat([]byte{0xFF}, 96),
|
||||||
|
}
|
||||||
|
badSigned, err := blocks.WrappedROSignedExecutionPayloadEnvelope(signedProto)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = verifyExecutionPayloadEnvelopeSignature(st, badSigned)
|
||||||
|
require.ErrorContains(t, "invalid signature format", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("builder", func(t *testing.T) {
|
||||||
|
signedProto := ðpb.SignedExecutionPayloadEnvelope{
|
||||||
|
Message: fixture.signedProto.Message,
|
||||||
|
Signature: bytes.Repeat([]byte{0xFF}, 96),
|
||||||
|
}
|
||||||
|
badSigned, err := blocks.WrappedROSignedExecutionPayloadEnvelope(signedProto)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = verifyExecutionPayloadEnvelopeSignature(fixture.state, badSigned)
|
||||||
|
require.ErrorContains(t, "invalid signature format", err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -67,6 +67,7 @@ func getSubscriptionStatusFromDB(t *testing.T, db *Store) bool {
|
|||||||
return subscribed
|
return subscribed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestUpdateCustodyInfo(t *testing.T) {
|
func TestUpdateCustodyInfo(t *testing.T) {
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
||||||
|
|||||||
@@ -575,7 +575,7 @@ func (s *Service) beaconEndpoints(
|
|||||||
name: namespace + ".PublishBlockV2",
|
name: namespace + ".PublishBlockV2",
|
||||||
middleware: []middleware.Middleware{
|
middleware: []middleware.Middleware{
|
||||||
middleware.ContentTypeHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
|
middleware.ContentTypeHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
|
||||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
|
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||||
middleware.AcceptEncodingHeaderHandler(),
|
middleware.AcceptEncodingHeaderHandler(),
|
||||||
},
|
},
|
||||||
handler: server.PublishBlockV2,
|
handler: server.PublishBlockV2,
|
||||||
@@ -586,7 +586,7 @@ func (s *Service) beaconEndpoints(
|
|||||||
name: namespace + ".PublishBlindedBlockV2",
|
name: namespace + ".PublishBlindedBlockV2",
|
||||||
middleware: []middleware.Middleware{
|
middleware: []middleware.Middleware{
|
||||||
middleware.ContentTypeHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
|
middleware.ContentTypeHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
|
||||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
|
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||||
middleware.AcceptEncodingHeaderHandler(),
|
middleware.AcceptEncodingHeaderHandler(),
|
||||||
},
|
},
|
||||||
handler: server.PublishBlindedBlockV2,
|
handler: server.PublishBlindedBlockV2,
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import (
|
|||||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/voluntaryexits/mock"
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/voluntaryexits/mock"
|
||||||
p2pMock "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/testing"
|
p2pMock "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/testing"
|
||||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/core"
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/core"
|
||||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
|
||||||
mockSync "github.com/OffchainLabs/prysm/v7/beacon-chain/sync/initial-sync/testing"
|
mockSync "github.com/OffchainLabs/prysm/v7/beacon-chain/sync/initial-sync/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/config/params"
|
||||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ func TestGetSpec(t *testing.T) {
|
|||||||
config.GloasForkEpoch = 110
|
config.GloasForkEpoch = 110
|
||||||
config.BLSWithdrawalPrefixByte = byte('b')
|
config.BLSWithdrawalPrefixByte = byte('b')
|
||||||
config.ETH1AddressWithdrawalPrefixByte = byte('c')
|
config.ETH1AddressWithdrawalPrefixByte = byte('c')
|
||||||
|
config.BuilderWithdrawalPrefixByte = byte('e')
|
||||||
config.GenesisDelay = 24
|
config.GenesisDelay = 24
|
||||||
config.SecondsPerSlot = 25
|
config.SecondsPerSlot = 25
|
||||||
config.SlotDurationMilliseconds = 120
|
config.SlotDurationMilliseconds = 120
|
||||||
|
|||||||
@@ -1,24 +1,51 @@
|
|||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||||
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type writeOnlyGloasFields interface {
|
type writeOnlyGloasFields interface {
|
||||||
|
// Bids.
|
||||||
SetExecutionPayloadBid(h interfaces.ROExecutionPayloadBid) error
|
SetExecutionPayloadBid(h interfaces.ROExecutionPayloadBid) error
|
||||||
|
|
||||||
|
// Builder pending payments / withdrawals.
|
||||||
SetBuilderPendingPayment(index primitives.Slot, payment *ethpb.BuilderPendingPayment) error
|
SetBuilderPendingPayment(index primitives.Slot, payment *ethpb.BuilderPendingPayment) error
|
||||||
ClearBuilderPendingPayment(index primitives.Slot) error
|
ClearBuilderPendingPayment(index primitives.Slot) error
|
||||||
|
QueueBuilderPayment() error
|
||||||
RotateBuilderPendingPayments() error
|
RotateBuilderPendingPayments() error
|
||||||
AppendBuilderPendingWithdrawals([]*ethpb.BuilderPendingWithdrawal) error
|
AppendBuilderPendingWithdrawals([]*ethpb.BuilderPendingWithdrawal) error
|
||||||
|
|
||||||
|
// Execution payload availability.
|
||||||
UpdateExecutionPayloadAvailabilityAtIndex(idx uint64, val byte) error
|
UpdateExecutionPayloadAvailabilityAtIndex(idx uint64, val byte) error
|
||||||
|
|
||||||
|
// Misc.
|
||||||
|
SetLatestBlockHash(hash [32]byte) error
|
||||||
|
SetExecutionPayloadAvailability(index primitives.Slot, available bool) error
|
||||||
|
|
||||||
|
// Builders.
|
||||||
|
IncreaseBuilderBalance(index primitives.BuilderIndex, amount uint64) error
|
||||||
|
AddBuilderFromDeposit(pubkey [fieldparams.BLSPubkeyLength]byte, withdrawalCredentials [fieldparams.RootLength]byte, amount uint64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type readOnlyGloasFields interface {
|
type readOnlyGloasFields interface {
|
||||||
|
// Bids.
|
||||||
|
LatestExecutionPayloadBid() (interfaces.ROExecutionPayloadBid, error)
|
||||||
|
|
||||||
|
// Builder pending payments / withdrawals.
|
||||||
|
BuilderPendingPayments() ([]*ethpb.BuilderPendingPayment, error)
|
||||||
|
WithdrawalsMatchPayloadExpected(withdrawals []*enginev1.Withdrawal) (bool, error)
|
||||||
|
|
||||||
|
// Misc.
|
||||||
|
LatestBlockHash() ([32]byte, error)
|
||||||
|
|
||||||
|
// Builders.
|
||||||
|
Builder(index primitives.BuilderIndex) (*ethpb.Builder, error)
|
||||||
BuilderPubkey(primitives.BuilderIndex) ([48]byte, error)
|
BuilderPubkey(primitives.BuilderIndex) ([48]byte, error)
|
||||||
|
BuilderIndexByPubkey(pubkey [fieldparams.BLSPubkeyLength]byte) (primitives.BuilderIndex, bool)
|
||||||
IsActiveBuilder(primitives.BuilderIndex) (bool, error)
|
IsActiveBuilder(primitives.BuilderIndex) (bool, error)
|
||||||
CanBuilderCoverBid(primitives.BuilderIndex, primitives.Gwei) (bool, error)
|
CanBuilderCoverBid(primitives.BuilderIndex, primitives.Gwei) (bool, error)
|
||||||
LatestBlockHash() ([32]byte, error)
|
|
||||||
BuilderPendingPayments() ([]*ethpb.BuilderPendingPayment, error)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package state_native
|
package state_native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
"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/consensus-types/primitives"
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/encoding/ssz"
|
||||||
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||||
)
|
)
|
||||||
@@ -147,3 +152,78 @@ func (b *BeaconState) BuilderPendingPayments() ([]*ethpb.BuilderPendingPayment,
|
|||||||
|
|
||||||
return b.builderPendingPaymentsVal(), nil
|
return b.builderPendingPaymentsVal(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LatestExecutionPayloadBid returns the cached latest execution payload bid for Gloas.
|
||||||
|
func (b *BeaconState) LatestExecutionPayloadBid() (interfaces.ROExecutionPayloadBid, error) {
|
||||||
|
b.lock.RLock()
|
||||||
|
defer b.lock.RUnlock()
|
||||||
|
|
||||||
|
if b.latestExecutionPayloadBid == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks.WrappedROExecutionPayloadBid(b.latestExecutionPayloadBid.Copy())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithdrawalsMatchPayloadExpected returns true if the given withdrawals root matches the state's
|
||||||
|
// payload_expected_withdrawals root.
|
||||||
|
func (b *BeaconState) WithdrawalsMatchPayloadExpected(withdrawals []*enginev1.Withdrawal) (bool, error) {
|
||||||
|
if b.version < version.Gloas {
|
||||||
|
return false, errNotSupported("WithdrawalsMatchPayloadExpected", b.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.lock.RLock()
|
||||||
|
defer b.lock.RUnlock()
|
||||||
|
|
||||||
|
cfg := params.BeaconConfig()
|
||||||
|
|
||||||
|
withdrawalsRoot, err := ssz.WithdrawalSliceRoot(withdrawals, cfg.MaxWithdrawalsPerPayload)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("could not compute withdrawals root: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := b.payloadExpectedWithdrawals
|
||||||
|
if expected == nil {
|
||||||
|
expected = []*enginev1.Withdrawal{}
|
||||||
|
}
|
||||||
|
expectedRoot, err := ssz.WithdrawalSliceRoot(expected, cfg.MaxWithdrawalsPerPayload)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("could not compute expected withdrawals root: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return withdrawalsRoot == expectedRoot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder returns the builder at the given index.
|
||||||
|
func (b *BeaconState) Builder(index primitives.BuilderIndex) (*ethpb.Builder, error) {
|
||||||
|
b.lock.RLock()
|
||||||
|
defer b.lock.RUnlock()
|
||||||
|
|
||||||
|
if b.builders == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if uint64(index) >= uint64(len(b.builders)) {
|
||||||
|
return nil, fmt.Errorf("builder index %d out of bounds", index)
|
||||||
|
}
|
||||||
|
if b.builders[index] == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ethpb.CopyBuilder(b.builders[index]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuilderIndexByPubkey returns the builder index for the given pubkey, if present.
|
||||||
|
func (b *BeaconState) BuilderIndexByPubkey(pubkey [fieldparams.BLSPubkeyLength]byte) (primitives.BuilderIndex, bool) {
|
||||||
|
b.lock.RLock()
|
||||||
|
defer b.lock.RUnlock()
|
||||||
|
|
||||||
|
for i, builder := range b.builders {
|
||||||
|
if builder == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if bytes.Equal(builder.Pubkey, pubkey[:]) {
|
||||||
|
return primitives.BuilderIndex(i), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||||
|
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||||
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||||
@@ -166,3 +168,132 @@ func TestBuilderPendingPayments_UnsupportedVersion(t *testing.T) {
|
|||||||
_, err = st.BuilderPendingPayments()
|
_, err = st.BuilderPendingPayments()
|
||||||
require.ErrorContains(t, "BuilderPendingPayments", err)
|
require.ErrorContains(t, "BuilderPendingPayments", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithdrawalsMatchPayloadExpected(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.WithdrawalsMatchPayloadExpected(nil)
|
||||||
|
require.ErrorContains(t, "is not supported", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns true when roots match", func(t *testing.T) {
|
||||||
|
withdrawals := []*enginev1.Withdrawal{
|
||||||
|
{Index: 0, ValidatorIndex: 1, Address: bytes.Repeat([]byte{0x01}, 20), Amount: 10},
|
||||||
|
}
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
PayloadExpectedWithdrawals: withdrawals,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ok, err := st.WithdrawalsMatchPayloadExpected(withdrawals)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns false when roots do not match", func(t *testing.T) {
|
||||||
|
expected := []*enginev1.Withdrawal{
|
||||||
|
{Index: 0, ValidatorIndex: 1, Address: bytes.Repeat([]byte{0x01}, 20), Amount: 10},
|
||||||
|
}
|
||||||
|
actual := []*enginev1.Withdrawal{
|
||||||
|
{Index: 0, ValidatorIndex: 1, Address: bytes.Repeat([]byte{0x01}, 20), Amount: 11},
|
||||||
|
}
|
||||||
|
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
PayloadExpectedWithdrawals: expected,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ok, err := st.WithdrawalsMatchPayloadExpected(actual)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, false, ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder(t *testing.T) {
|
||||||
|
t.Run("nil builders returns nil", func(t *testing.T) {
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
Builders: nil,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got, err := st.Builder(0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, (*ethpb.Builder)(nil), got)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("out of bounds returns error", func(t *testing.T) {
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
Builders: []*ethpb.Builder{{}},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = st.Builder(1)
|
||||||
|
require.ErrorContains(t, "out of bounds", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns copy", func(t *testing.T) {
|
||||||
|
pubkey := bytes.Repeat([]byte{0xAA}, fieldparams.BLSPubkeyLength)
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
Builders: []*ethpb.Builder{
|
||||||
|
{
|
||||||
|
Pubkey: pubkey,
|
||||||
|
Balance: 42,
|
||||||
|
DepositEpoch: 3,
|
||||||
|
WithdrawableEpoch: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got1, err := st.Builder(0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEqual(t, (*ethpb.Builder)(nil), got1)
|
||||||
|
require.Equal(t, primitives.Gwei(42), got1.Balance)
|
||||||
|
require.DeepEqual(t, pubkey, got1.Pubkey)
|
||||||
|
|
||||||
|
// Mutate returned builder; state should be unchanged.
|
||||||
|
got1.Pubkey[0] = 0xFF
|
||||||
|
got2, err := st.Builder(0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, byte(0xAA), got2.Pubkey[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderIndexByPubkey(t *testing.T) {
|
||||||
|
t.Run("not found returns false", func(t *testing.T) {
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
Builders: []*ethpb.Builder{
|
||||||
|
{Pubkey: bytes.Repeat([]byte{0x11}, fieldparams.BLSPubkeyLength)},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var pk [fieldparams.BLSPubkeyLength]byte
|
||||||
|
copy(pk[:], bytes.Repeat([]byte{0x22}, fieldparams.BLSPubkeyLength))
|
||||||
|
idx, ok := st.BuilderIndexByPubkey(pk)
|
||||||
|
require.Equal(t, false, ok)
|
||||||
|
require.Equal(t, primitives.BuilderIndex(0), idx)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("skips nil entries and finds match", func(t *testing.T) {
|
||||||
|
wantIdx := primitives.BuilderIndex(1)
|
||||||
|
wantPkBytes := bytes.Repeat([]byte{0xAB}, fieldparams.BLSPubkeyLength)
|
||||||
|
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
Builders: []*ethpb.Builder{
|
||||||
|
nil,
|
||||||
|
{Pubkey: wantPkBytes},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var pk [fieldparams.BLSPubkeyLength]byte
|
||||||
|
copy(pk[:], wantPkBytes)
|
||||||
|
idx, ok := st.BuilderIndexByPubkey(pk)
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
require.Equal(t, wantIdx, idx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import (
|
|||||||
|
|
||||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native/types"
|
"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/beacon-chain/state/stateutil"
|
||||||
|
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RotateBuilderPendingPayments rotates the queue by dropping slots per epoch payments from the
|
// RotateBuilderPendingPayments rotates the queue by dropping slots per epoch payments from the
|
||||||
@@ -121,6 +124,41 @@ func (b *BeaconState) ClearBuilderPendingPayment(index primitives.Slot) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueueBuilderPayment implements the builder payment queuing logic for Gloas.
|
||||||
|
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||||
|
// payment = state.builder_pending_payments[SLOTS_PER_EPOCH + state.slot % SLOTS_PER_EPOCH]
|
||||||
|
// amount = payment.withdrawal.amount
|
||||||
|
// if amount > 0:
|
||||||
|
//
|
||||||
|
// state.builder_pending_withdrawals.append(payment.withdrawal)
|
||||||
|
//
|
||||||
|
// state.builder_pending_payments[SLOTS_PER_EPOCH + state.slot % SLOTS_PER_EPOCH] = BuilderPendingPayment()
|
||||||
|
func (b *BeaconState) QueueBuilderPayment() error {
|
||||||
|
if b.version < version.Gloas {
|
||||||
|
return errNotSupported("QueueBuilderPayment", b.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
slot := b.slot
|
||||||
|
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||||
|
paymentIndex := slotsPerEpoch + (slot % slotsPerEpoch)
|
||||||
|
if uint64(paymentIndex) >= uint64(len(b.builderPendingPayments)) {
|
||||||
|
return fmt.Errorf("builder pending payments index %d out of range (len=%d)", paymentIndex, len(b.builderPendingPayments))
|
||||||
|
}
|
||||||
|
|
||||||
|
payment := b.builderPendingPayments[paymentIndex]
|
||||||
|
if payment != nil && payment.Withdrawal != nil && payment.Withdrawal.Amount > 0 {
|
||||||
|
b.builderPendingWithdrawals = append(b.builderPendingWithdrawals, ethpb.CopyBuilderPendingWithdrawal(payment.Withdrawal))
|
||||||
|
b.markFieldAsDirty(types.BuilderPendingWithdrawals)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.builderPendingPayments[paymentIndex] = emptyBuilderPendingPayment
|
||||||
|
b.markFieldAsDirty(types.BuilderPendingPayments)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetBuilderPendingPayment sets a builder pending payment at the specified index.
|
// SetBuilderPendingPayment sets a builder pending payment at the specified index.
|
||||||
func (b *BeaconState) SetBuilderPendingPayment(index primitives.Slot, payment *ethpb.BuilderPendingPayment) error {
|
func (b *BeaconState) SetBuilderPendingPayment(index primitives.Slot, payment *ethpb.BuilderPendingPayment) error {
|
||||||
if b.version < version.Gloas {
|
if b.version < version.Gloas {
|
||||||
@@ -161,3 +199,91 @@ func (b *BeaconState) UpdateExecutionPayloadAvailabilityAtIndex(idx uint64, val
|
|||||||
b.markFieldAsDirty(types.ExecutionPayloadAvailability)
|
b.markFieldAsDirty(types.ExecutionPayloadAvailability)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLatestBlockHash sets the latest execution block hash.
|
||||||
|
func (b *BeaconState) SetLatestBlockHash(hash [32]byte) error {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
b.latestBlockHash = hash[:]
|
||||||
|
b.markFieldAsDirty(types.LatestBlockHash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExecutionPayloadAvailability sets the execution payload availability bit for a specific slot.
|
||||||
|
func (b *BeaconState) SetExecutionPayloadAvailability(index primitives.Slot, available bool) error {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
bitIndex := index % params.BeaconConfig().SlotsPerHistoricalRoot
|
||||||
|
byteIndex := bitIndex / 8
|
||||||
|
bitPosition := bitIndex % 8
|
||||||
|
|
||||||
|
// Set or clear the bit
|
||||||
|
if available {
|
||||||
|
b.executionPayloadAvailability[byteIndex] |= 1 << bitPosition
|
||||||
|
} else {
|
||||||
|
b.executionPayloadAvailability[byteIndex] &^= 1 << bitPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
b.markFieldAsDirty(types.ExecutionPayloadAvailability)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncreaseBuilderBalance increases the balance of the builder at the given index.
|
||||||
|
func (b *BeaconState) IncreaseBuilderBalance(index primitives.BuilderIndex, amount uint64) error {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
if b.builders == nil || uint64(index) >= uint64(len(b.builders)) {
|
||||||
|
return fmt.Errorf("builder index %d out of bounds", index)
|
||||||
|
}
|
||||||
|
if b.builders[index] == nil {
|
||||||
|
return fmt.Errorf("builder at index %d is nil", index)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := ethpb.CopyBuilder(b.builders[index])
|
||||||
|
builder.Balance += primitives.Gwei(amount)
|
||||||
|
b.builders[index] = builder
|
||||||
|
|
||||||
|
b.markFieldAsDirty(types.Builders)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBuilderFromDeposit creates or replaces a builder entry derived from a deposit.
|
||||||
|
func (b *BeaconState) AddBuilderFromDeposit(pubkey [fieldparams.BLSPubkeyLength]byte, withdrawalCredentials [fieldparams.RootLength]byte, amount uint64) error {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
currentEpoch := slots.ToEpoch(b.slot)
|
||||||
|
index := b.builderInsertionIndex(currentEpoch)
|
||||||
|
|
||||||
|
builder := ðpb.Builder{
|
||||||
|
Pubkey: bytesutil.SafeCopyBytes(pubkey[:]),
|
||||||
|
Version: []byte{withdrawalCredentials[0]},
|
||||||
|
ExecutionAddress: bytesutil.SafeCopyBytes(withdrawalCredentials[12:]),
|
||||||
|
Balance: primitives.Gwei(amount),
|
||||||
|
DepositEpoch: currentEpoch,
|
||||||
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||||
|
}
|
||||||
|
|
||||||
|
if index < primitives.BuilderIndex(len(b.builders)) {
|
||||||
|
b.builders[index] = builder
|
||||||
|
} else {
|
||||||
|
gap := index - primitives.BuilderIndex(len(b.builders)) + 1
|
||||||
|
b.builders = append(b.builders, make([]*ethpb.Builder, gap)...)
|
||||||
|
b.builders[index] = builder
|
||||||
|
}
|
||||||
|
|
||||||
|
b.markFieldAsDirty(types.Builders)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BeaconState) builderInsertionIndex(currentEpoch primitives.Epoch) primitives.BuilderIndex {
|
||||||
|
for i, builder := range b.builders {
|
||||||
|
if builder.WithdrawableEpoch <= currentEpoch && builder.Balance == 0 {
|
||||||
|
return primitives.BuilderIndex(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return primitives.BuilderIndex(len(b.builders))
|
||||||
|
}
|
||||||
|
|||||||
@@ -181,6 +181,80 @@ func TestClearBuilderPendingPayment(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQueueBuilderPayment(t *testing.T) {
|
||||||
|
t.Run("previous fork returns expected error", func(t *testing.T) {
|
||||||
|
st := &BeaconState{version: version.Fulu}
|
||||||
|
err := st.QueueBuilderPayment()
|
||||||
|
require.ErrorContains(t, "is not supported", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("appends withdrawal, clears payment, and marks dirty", func(t *testing.T) {
|
||||||
|
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||||
|
slot := primitives.Slot(3)
|
||||||
|
paymentIndex := slotsPerEpoch + (slot % slotsPerEpoch)
|
||||||
|
|
||||||
|
st := &BeaconState{
|
||||||
|
version: version.Gloas,
|
||||||
|
slot: slot,
|
||||||
|
dirtyFields: make(map[types.FieldIndex]bool),
|
||||||
|
rebuildTrie: make(map[types.FieldIndex]bool),
|
||||||
|
sharedFieldReferences: make(map[types.FieldIndex]*stateutil.Reference),
|
||||||
|
builderPendingPayments: make([]*ethpb.BuilderPendingPayment, slotsPerEpoch*2),
|
||||||
|
builderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{},
|
||||||
|
}
|
||||||
|
st.builderPendingPayments[paymentIndex] = ðpb.BuilderPendingPayment{
|
||||||
|
Weight: 1,
|
||||||
|
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||||
|
FeeRecipient: bytes.Repeat([]byte{0xAB}, 20),
|
||||||
|
Amount: 99,
|
||||||
|
BuilderIndex: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, st.QueueBuilderPayment())
|
||||||
|
require.Equal(t, emptyBuilderPendingPayment, st.builderPendingPayments[paymentIndex])
|
||||||
|
require.Equal(t, true, st.dirtyFields[types.BuilderPendingPayments])
|
||||||
|
require.Equal(t, true, st.dirtyFields[types.BuilderPendingWithdrawals])
|
||||||
|
require.Equal(t, 1, len(st.builderPendingWithdrawals))
|
||||||
|
require.DeepEqual(t, bytes.Repeat([]byte{0xAB}, 20), st.builderPendingWithdrawals[0].FeeRecipient)
|
||||||
|
require.Equal(t, primitives.Gwei(99), st.builderPendingWithdrawals[0].Amount)
|
||||||
|
|
||||||
|
// Ensure copied withdrawal is not aliased.
|
||||||
|
st.builderPendingPayments[paymentIndex].Withdrawal.FeeRecipient[0] = 0x01
|
||||||
|
require.Equal(t, byte(0xAB), st.builderPendingWithdrawals[0].FeeRecipient[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("zero amount does not append withdrawal", func(t *testing.T) {
|
||||||
|
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||||
|
slot := primitives.Slot(3)
|
||||||
|
paymentIndex := slotsPerEpoch + (slot % slotsPerEpoch)
|
||||||
|
|
||||||
|
st := &BeaconState{
|
||||||
|
version: version.Gloas,
|
||||||
|
slot: slot,
|
||||||
|
dirtyFields: make(map[types.FieldIndex]bool),
|
||||||
|
rebuildTrie: make(map[types.FieldIndex]bool),
|
||||||
|
sharedFieldReferences: make(map[types.FieldIndex]*stateutil.Reference),
|
||||||
|
builderPendingPayments: make([]*ethpb.BuilderPendingPayment, slotsPerEpoch*2),
|
||||||
|
builderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{},
|
||||||
|
}
|
||||||
|
st.builderPendingPayments[paymentIndex] = ðpb.BuilderPendingPayment{
|
||||||
|
Weight: 1,
|
||||||
|
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||||
|
FeeRecipient: bytes.Repeat([]byte{0xAB}, 20),
|
||||||
|
Amount: 0,
|
||||||
|
BuilderIndex: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, st.QueueBuilderPayment())
|
||||||
|
require.Equal(t, emptyBuilderPendingPayment, st.builderPendingPayments[paymentIndex])
|
||||||
|
require.Equal(t, true, st.dirtyFields[types.BuilderPendingPayments])
|
||||||
|
require.Equal(t, false, st.dirtyFields[types.BuilderPendingWithdrawals])
|
||||||
|
require.Equal(t, 0, len(st.builderPendingWithdrawals))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestRotateBuilderPendingPayments(t *testing.T) {
|
func TestRotateBuilderPendingPayments(t *testing.T) {
|
||||||
totalPayments := 2 * params.BeaconConfig().SlotsPerEpoch
|
totalPayments := 2 * params.BeaconConfig().SlotsPerEpoch
|
||||||
payments := make([]*ethpb.BuilderPendingPayment, totalPayments)
|
payments := make([]*ethpb.BuilderPendingPayment, totalPayments)
|
||||||
@@ -328,3 +402,134 @@ func newGloasStateWithAvailability(t *testing.T, availability []byte) *BeaconSta
|
|||||||
|
|
||||||
return st.(*BeaconState)
|
return st.(*BeaconState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetLatestBlockHash(t *testing.T) {
|
||||||
|
var hash [32]byte
|
||||||
|
copy(hash[:], []byte("latest-block-hash"))
|
||||||
|
|
||||||
|
state := &BeaconState{
|
||||||
|
dirtyFields: make(map[types.FieldIndex]bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, state.SetLatestBlockHash(hash))
|
||||||
|
require.Equal(t, true, state.dirtyFields[types.LatestBlockHash])
|
||||||
|
require.DeepEqual(t, hash[:], state.latestBlockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetExecutionPayloadAvailability(t *testing.T) {
|
||||||
|
state := &BeaconState{
|
||||||
|
executionPayloadAvailability: make([]byte, params.BeaconConfig().SlotsPerHistoricalRoot/8),
|
||||||
|
dirtyFields: make(map[types.FieldIndex]bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
slot := primitives.Slot(10)
|
||||||
|
bitIndex := slot % params.BeaconConfig().SlotsPerHistoricalRoot
|
||||||
|
byteIndex := bitIndex / 8
|
||||||
|
bitPosition := bitIndex % 8
|
||||||
|
|
||||||
|
require.NoError(t, state.SetExecutionPayloadAvailability(slot, true))
|
||||||
|
require.Equal(t, true, state.dirtyFields[types.ExecutionPayloadAvailability])
|
||||||
|
require.Equal(t, byte(1<<bitPosition), state.executionPayloadAvailability[byteIndex]&(1<<bitPosition))
|
||||||
|
|
||||||
|
require.NoError(t, state.SetExecutionPayloadAvailability(slot, false))
|
||||||
|
require.Equal(t, byte(0), state.executionPayloadAvailability[byteIndex]&(1<<bitPosition))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncreaseBuilderBalance(t *testing.T) {
|
||||||
|
t.Run("out of bounds returns error", func(t *testing.T) {
|
||||||
|
st := &BeaconState{
|
||||||
|
version: version.Gloas,
|
||||||
|
dirtyFields: make(map[types.FieldIndex]bool),
|
||||||
|
builders: []*ethpb.Builder{},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := st.IncreaseBuilderBalance(0, 1)
|
||||||
|
require.ErrorContains(t, "out of bounds", err)
|
||||||
|
require.Equal(t, false, st.dirtyFields[types.Builders])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil builder returns error", func(t *testing.T) {
|
||||||
|
st := &BeaconState{
|
||||||
|
version: version.Gloas,
|
||||||
|
dirtyFields: make(map[types.FieldIndex]bool),
|
||||||
|
builders: []*ethpb.Builder{nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := st.IncreaseBuilderBalance(0, 1)
|
||||||
|
require.ErrorContains(t, "is nil", err)
|
||||||
|
require.Equal(t, false, st.dirtyFields[types.Builders])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("increments and marks dirty", func(t *testing.T) {
|
||||||
|
orig := ðpb.Builder{Balance: 10}
|
||||||
|
st := &BeaconState{
|
||||||
|
version: version.Gloas,
|
||||||
|
dirtyFields: make(map[types.FieldIndex]bool),
|
||||||
|
builders: []*ethpb.Builder{orig},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, st.IncreaseBuilderBalance(0, 5))
|
||||||
|
require.Equal(t, primitives.Gwei(15), st.builders[0].Balance)
|
||||||
|
require.Equal(t, true, st.dirtyFields[types.Builders])
|
||||||
|
// Copy-on-write semantics: builder pointer replaced.
|
||||||
|
require.NotEqual(t, orig, st.builders[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddBuilderFromDeposit(t *testing.T) {
|
||||||
|
t.Run("reuses empty withdrawable slot", func(t *testing.T) {
|
||||||
|
var pubkey [48]byte
|
||||||
|
copy(pubkey[:], bytes.Repeat([]byte{0xAA}, 48))
|
||||||
|
var wc [32]byte
|
||||||
|
copy(wc[:], bytes.Repeat([]byte{0xBB}, 32))
|
||||||
|
wc[0] = 0x42 // version byte
|
||||||
|
|
||||||
|
st := &BeaconState{
|
||||||
|
version: version.Gloas,
|
||||||
|
slot: 0, // epoch 0
|
||||||
|
dirtyFields: make(map[types.FieldIndex]bool),
|
||||||
|
builders: []*ethpb.Builder{
|
||||||
|
{
|
||||||
|
WithdrawableEpoch: 0,
|
||||||
|
Balance: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, st.AddBuilderFromDeposit(pubkey, wc, 123))
|
||||||
|
require.Equal(t, 1, len(st.builders))
|
||||||
|
got := st.builders[0]
|
||||||
|
require.NotNil(t, got)
|
||||||
|
require.DeepEqual(t, pubkey[:], got.Pubkey)
|
||||||
|
require.DeepEqual(t, []byte{0x42}, got.Version)
|
||||||
|
require.DeepEqual(t, wc[12:], got.ExecutionAddress)
|
||||||
|
require.Equal(t, primitives.Gwei(123), got.Balance)
|
||||||
|
require.Equal(t, primitives.Epoch(0), got.DepositEpoch)
|
||||||
|
require.Equal(t, params.BeaconConfig().FarFutureEpoch, got.WithdrawableEpoch)
|
||||||
|
require.Equal(t, true, st.dirtyFields[types.Builders])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("appends new builder when no reusable slot", func(t *testing.T) {
|
||||||
|
var pubkey [48]byte
|
||||||
|
copy(pubkey[:], bytes.Repeat([]byte{0xAA}, 48))
|
||||||
|
var wc [32]byte
|
||||||
|
copy(wc[:], bytes.Repeat([]byte{0xBB}, 32))
|
||||||
|
|
||||||
|
st := &BeaconState{
|
||||||
|
version: version.Gloas,
|
||||||
|
slot: 0,
|
||||||
|
dirtyFields: make(map[types.FieldIndex]bool),
|
||||||
|
builders: []*ethpb.Builder{
|
||||||
|
{
|
||||||
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||||
|
Balance: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, st.AddBuilderFromDeposit(pubkey, wc, 5))
|
||||||
|
require.Equal(t, 2, len(st.builders))
|
||||||
|
require.NotNil(t, st.builders[1])
|
||||||
|
require.Equal(t, primitives.Gwei(5), st.builders[1].Balance)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1027,10 +1027,10 @@ func TestGetVerifyingStateEdgeCases(t *testing.T) {
|
|||||||
sc: signatureCache,
|
sc: signatureCache,
|
||||||
sr: &mockStateByRooter{sbr: sbrErrorIfCalled(t)}, // Should not be called
|
sr: &mockStateByRooter{sbr: sbrErrorIfCalled(t)}, // Should not be called
|
||||||
hsp: &mockHeadStateProvider{
|
hsp: &mockHeadStateProvider{
|
||||||
headRoot: parentRoot[:], // Same as parent
|
headRoot: parentRoot[:], // Same as parent
|
||||||
headSlot: 32, // Epoch 1
|
headSlot: 32, // Epoch 1
|
||||||
headState: fuluState.Copy(), // HeadState (not ReadOnly) for ProcessSlots
|
headState: fuluState.Copy(), // HeadState (not ReadOnly) for ProcessSlots
|
||||||
headStateReadOnly: nil, // Should not use ReadOnly path
|
headStateReadOnly: nil, // Should not use ReadOnly path
|
||||||
},
|
},
|
||||||
fc: &mockForkchoicer{
|
fc: &mockForkchoicer{
|
||||||
// Return same root for both to simulate same chain
|
// Return same root for both to simulate same chain
|
||||||
@@ -1045,8 +1045,8 @@ func TestGetVerifyingStateEdgeCases(t *testing.T) {
|
|||||||
// Wrap to detect HeadState call
|
// Wrap to detect HeadState call
|
||||||
originalHsp := initializer.shared.hsp.(*mockHeadStateProvider)
|
originalHsp := initializer.shared.hsp.(*mockHeadStateProvider)
|
||||||
wrappedHsp := &mockHeadStateProvider{
|
wrappedHsp := &mockHeadStateProvider{
|
||||||
headRoot: originalHsp.headRoot,
|
headRoot: originalHsp.headRoot,
|
||||||
headSlot: originalHsp.headSlot,
|
headSlot: originalHsp.headSlot,
|
||||||
headState: originalHsp.headState,
|
headState: originalHsp.headState,
|
||||||
}
|
}
|
||||||
initializer.shared.hsp = &headStateCallTracker{
|
initializer.shared.hsp = &headStateCallTracker{
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix Bazel build failure on macOS x86_64 (darwin_amd64) (adds missing assembly stub to hashtree patch).
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
### Added
|
||||||
|
- Add process execution payload for gloas
|
||||||
@@ -98,6 +98,7 @@ func compareConfigs(t *testing.T, expected, actual *BeaconChainConfig) {
|
|||||||
require.DeepEqual(t, expected.EjectionBalance, actual.EjectionBalance)
|
require.DeepEqual(t, expected.EjectionBalance, actual.EjectionBalance)
|
||||||
require.DeepEqual(t, expected.EffectiveBalanceIncrement, actual.EffectiveBalanceIncrement)
|
require.DeepEqual(t, expected.EffectiveBalanceIncrement, actual.EffectiveBalanceIncrement)
|
||||||
require.DeepEqual(t, expected.BLSWithdrawalPrefixByte, actual.BLSWithdrawalPrefixByte)
|
require.DeepEqual(t, expected.BLSWithdrawalPrefixByte, actual.BLSWithdrawalPrefixByte)
|
||||||
|
require.DeepEqual(t, expected.BuilderWithdrawalPrefixByte, actual.BuilderWithdrawalPrefixByte)
|
||||||
require.DeepEqual(t, expected.ZeroHash, actual.ZeroHash)
|
require.DeepEqual(t, expected.ZeroHash, actual.ZeroHash)
|
||||||
require.DeepEqual(t, expected.GenesisDelay, actual.GenesisDelay)
|
require.DeepEqual(t, expected.GenesisDelay, actual.GenesisDelay)
|
||||||
require.DeepEqual(t, expected.MinAttestationInclusionDelay, actual.MinAttestationInclusionDelay)
|
require.DeepEqual(t, expected.MinAttestationInclusionDelay, actual.MinAttestationInclusionDelay)
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ func assertEqualConfigs(t *testing.T, name string, fields []string, expected, ac
|
|||||||
// Initial values.
|
// Initial values.
|
||||||
assert.DeepEqual(t, expected.GenesisForkVersion, actual.GenesisForkVersion, "%s: GenesisForkVersion", name)
|
assert.DeepEqual(t, expected.GenesisForkVersion, actual.GenesisForkVersion, "%s: GenesisForkVersion", name)
|
||||||
assert.DeepEqual(t, expected.BLSWithdrawalPrefixByte, actual.BLSWithdrawalPrefixByte, "%s: BLSWithdrawalPrefixByte", name)
|
assert.DeepEqual(t, expected.BLSWithdrawalPrefixByte, actual.BLSWithdrawalPrefixByte, "%s: BLSWithdrawalPrefixByte", name)
|
||||||
|
assert.DeepEqual(t, expected.BuilderWithdrawalPrefixByte, actual.BuilderWithdrawalPrefixByte, "%s: BuilderWithdrawalPrefixByte", name)
|
||||||
assert.DeepEqual(t, expected.ETH1AddressWithdrawalPrefixByte, actual.ETH1AddressWithdrawalPrefixByte, "%s: ETH1AddressWithdrawalPrefixByte", name)
|
assert.DeepEqual(t, expected.ETH1AddressWithdrawalPrefixByte, actual.ETH1AddressWithdrawalPrefixByte, "%s: ETH1AddressWithdrawalPrefixByte", name)
|
||||||
|
|
||||||
// Time parameters.
|
// Time parameters.
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func MinimalSpecConfig() *BeaconChainConfig {
|
|||||||
// Initial values
|
// Initial values
|
||||||
minimalConfig.BLSWithdrawalPrefixByte = byte(0)
|
minimalConfig.BLSWithdrawalPrefixByte = byte(0)
|
||||||
minimalConfig.ETH1AddressWithdrawalPrefixByte = byte(1)
|
minimalConfig.ETH1AddressWithdrawalPrefixByte = byte(1)
|
||||||
|
minimalConfig.BuilderWithdrawalPrefixByte = byte(3)
|
||||||
|
|
||||||
// Time parameters
|
// Time parameters
|
||||||
minimalConfig.SecondsPerSlot = 6
|
minimalConfig.SecondsPerSlot = 6
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ func compareConfigs(t *testing.T, expected, actual *params.BeaconChainConfig) {
|
|||||||
require.DeepEqual(t, expected.EjectionBalance, actual.EjectionBalance)
|
require.DeepEqual(t, expected.EjectionBalance, actual.EjectionBalance)
|
||||||
require.DeepEqual(t, expected.EffectiveBalanceIncrement, actual.EffectiveBalanceIncrement)
|
require.DeepEqual(t, expected.EffectiveBalanceIncrement, actual.EffectiveBalanceIncrement)
|
||||||
require.DeepEqual(t, expected.BLSWithdrawalPrefixByte, actual.BLSWithdrawalPrefixByte)
|
require.DeepEqual(t, expected.BLSWithdrawalPrefixByte, actual.BLSWithdrawalPrefixByte)
|
||||||
|
require.DeepEqual(t, expected.BuilderWithdrawalPrefixByte, actual.BuilderWithdrawalPrefixByte)
|
||||||
require.DeepEqual(t, expected.ZeroHash, actual.ZeroHash)
|
require.DeepEqual(t, expected.ZeroHash, actual.ZeroHash)
|
||||||
require.DeepEqual(t, expected.GenesisDelay, actual.GenesisDelay)
|
require.DeepEqual(t, expected.GenesisDelay, actual.GenesisDelay)
|
||||||
require.DeepEqual(t, expected.MinAttestationInclusionDelay, actual.MinAttestationInclusionDelay)
|
require.DeepEqual(t, expected.MinAttestationInclusionDelay, actual.MinAttestationInclusionDelay)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"execution.go",
|
"execution.go",
|
||||||
|
"execution_payload_envelope.go",
|
||||||
"factory.go",
|
"factory.go",
|
||||||
"get_payload.go",
|
"get_payload.go",
|
||||||
"getters.go",
|
"getters.go",
|
||||||
@@ -36,6 +37,7 @@ go_library(
|
|||||||
"//proto/prysm/v1alpha1:go_default_library",
|
"//proto/prysm/v1alpha1:go_default_library",
|
||||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||||
"//runtime/version:go_default_library",
|
"//runtime/version:go_default_library",
|
||||||
|
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||||
"@com_github_pkg_errors//:go_default_library",
|
"@com_github_pkg_errors//:go_default_library",
|
||||||
"@com_github_prysmaticlabs_fastssz//:go_default_library",
|
"@com_github_prysmaticlabs_fastssz//:go_default_library",
|
||||||
"@org_golang_google_protobuf//proto:go_default_library",
|
"@org_golang_google_protobuf//proto:go_default_library",
|
||||||
@@ -45,6 +47,7 @@ go_library(
|
|||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"execution_payload_envelope_test.go",
|
||||||
"execution_test.go",
|
"execution_test.go",
|
||||||
"factory_test.go",
|
"factory_test.go",
|
||||||
"getters_test.go",
|
"getters_test.go",
|
||||||
|
|||||||
153
consensus-types/blocks/execution_payload_envelope.go
Normal file
153
consensus-types/blocks/execution_payload_envelope.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package blocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||||
|
field_params "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||||
|
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"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/encoding/ssz"
|
||||||
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||||
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type signedExecutionPayloadEnvelope struct {
|
||||||
|
s *ethpb.SignedExecutionPayloadEnvelope
|
||||||
|
}
|
||||||
|
|
||||||
|
type executionPayloadEnvelope struct {
|
||||||
|
p *ethpb.ExecutionPayloadEnvelope
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrappedROSignedExecutionPayloadEnvelope wraps a signed execution payload envelope proto in a read-only interface.
|
||||||
|
func WrappedROSignedExecutionPayloadEnvelope(s *ethpb.SignedExecutionPayloadEnvelope) (interfaces.ROSignedExecutionPayloadEnvelope, error) {
|
||||||
|
w := signedExecutionPayloadEnvelope{s: s}
|
||||||
|
if w.IsNil() {
|
||||||
|
return nil, consensus_types.ErrNilObjectWrapped
|
||||||
|
}
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrappedROExecutionPayloadEnvelope wraps an execution payload envelope proto in a read-only interface.
|
||||||
|
func WrappedROExecutionPayloadEnvelope(p *ethpb.ExecutionPayloadEnvelope) (interfaces.ROExecutionPayloadEnvelope, error) {
|
||||||
|
w := &executionPayloadEnvelope{p: p}
|
||||||
|
if w.IsNil() {
|
||||||
|
return nil, consensus_types.ErrNilObjectWrapped
|
||||||
|
}
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envelope returns the execution payload envelope as a read-only interface.
|
||||||
|
func (s signedExecutionPayloadEnvelope) Envelope() (interfaces.ROExecutionPayloadEnvelope, error) {
|
||||||
|
return WrappedROExecutionPayloadEnvelope(s.s.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature returns the BLS signature as a 96-byte array.
|
||||||
|
func (s signedExecutionPayloadEnvelope) Signature() [field_params.BLSSignatureLength]byte {
|
||||||
|
return [field_params.BLSSignatureLength]byte(s.s.Signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil reports whether the signed envelope or its contents are invalid.
|
||||||
|
func (s signedExecutionPayloadEnvelope) IsNil() bool {
|
||||||
|
if s.s == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(s.s.Signature) != field_params.BLSSignatureLength {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
w := executionPayloadEnvelope{p: s.s.Message}
|
||||||
|
return w.IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigningRoot computes the signing root for the signed envelope with the provided domain.
|
||||||
|
func (s signedExecutionPayloadEnvelope) SigningRoot(domain []byte) (root [32]byte, err error) {
|
||||||
|
return signing.ComputeSigningRoot(s.s.Message, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proto returns the underlying protobuf message.
|
||||||
|
func (s signedExecutionPayloadEnvelope) Proto() proto.Message {
|
||||||
|
return s.s
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil reports whether the envelope or its required fields are invalid.
|
||||||
|
func (p *executionPayloadEnvelope) IsNil() bool {
|
||||||
|
if p.p == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if p.p.Payload == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(p.p.BeaconBlockRoot) != field_params.RootLength {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if p.p.BlobKzgCommitments == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBlinded reports whether the envelope contains a blinded payload.
|
||||||
|
func (p *executionPayloadEnvelope) IsBlinded() bool {
|
||||||
|
return !p.IsNil() && p.p.Payload == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution returns the execution payload as a read-only interface.
|
||||||
|
func (p *executionPayloadEnvelope) Execution() (interfaces.ExecutionData, error) {
|
||||||
|
return WrappedExecutionPayloadDeneb(p.p.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecutionRequests returns the execution requests attached to the envelope.
|
||||||
|
func (p *executionPayloadEnvelope) ExecutionRequests() *enginev1.ExecutionRequests {
|
||||||
|
return p.p.ExecutionRequests
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuilderIndex returns the proposer/builder index for the envelope.
|
||||||
|
func (p *executionPayloadEnvelope) BuilderIndex() primitives.BuilderIndex {
|
||||||
|
return p.p.BuilderIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeaconBlockRoot returns the beacon block root referenced by the envelope.
|
||||||
|
func (p *executionPayloadEnvelope) BeaconBlockRoot() [field_params.RootLength]byte {
|
||||||
|
return [field_params.RootLength]byte(p.p.BeaconBlockRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlobKzgCommitments returns a copy of the envelope's KZG commitments.
|
||||||
|
func (p *executionPayloadEnvelope) BlobKzgCommitments() [][]byte {
|
||||||
|
commitments := make([][]byte, len(p.p.BlobKzgCommitments))
|
||||||
|
for i, commit := range p.p.BlobKzgCommitments {
|
||||||
|
commitments[i] = make([]byte, len(commit))
|
||||||
|
copy(commitments[i], commit)
|
||||||
|
}
|
||||||
|
return commitments
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionedHashes returns versioned hashes derived from the KZG commitments.
|
||||||
|
func (p *executionPayloadEnvelope) VersionedHashes() []common.Hash {
|
||||||
|
commitments := p.p.BlobKzgCommitments
|
||||||
|
versionedHashes := make([]common.Hash, len(commitments))
|
||||||
|
for i, commitment := range commitments {
|
||||||
|
versionedHashes[i] = primitives.ConvertKzgCommitmentToVersionedHash(commitment)
|
||||||
|
}
|
||||||
|
return versionedHashes
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlobKzgCommitmentsRoot returns the SSZ root of the envelope's KZG commitments.
|
||||||
|
func (p *executionPayloadEnvelope) BlobKzgCommitmentsRoot() ([field_params.RootLength]byte, error) {
|
||||||
|
if p.IsNil() || p.p.BlobKzgCommitments == nil {
|
||||||
|
return [field_params.RootLength]byte{}, consensus_types.ErrNilObjectWrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssz.KzgCommitmentsRoot(p.p.BlobKzgCommitments)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slot returns the slot of the envelope.
|
||||||
|
func (p *executionPayloadEnvelope) Slot() primitives.Slot {
|
||||||
|
return p.p.Slot
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateRoot returns the state root carried by the envelope.
|
||||||
|
func (p *executionPayloadEnvelope) StateRoot() [field_params.RootLength]byte {
|
||||||
|
return [field_params.RootLength]byte(p.p.StateRoot)
|
||||||
|
}
|
||||||
115
consensus-types/blocks/execution_payload_envelope_test.go
Normal file
115
consensus-types/blocks/execution_payload_envelope_test.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
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"
|
||||||
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||||
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validExecutionPayloadEnvelope() *ethpb.ExecutionPayloadEnvelope {
|
||||||
|
payload := &enginev1.ExecutionPayloadDeneb{
|
||||||
|
ParentHash: bytes.Repeat([]byte{0x01}, 32),
|
||||||
|
FeeRecipient: bytes.Repeat([]byte{0x02}, 20),
|
||||||
|
StateRoot: bytes.Repeat([]byte{0x03}, 32),
|
||||||
|
ReceiptsRoot: bytes.Repeat([]byte{0x04}, 32),
|
||||||
|
LogsBloom: bytes.Repeat([]byte{0x05}, 256),
|
||||||
|
PrevRandao: bytes.Repeat([]byte{0x06}, 32),
|
||||||
|
BlockNumber: 1,
|
||||||
|
GasLimit: 2,
|
||||||
|
GasUsed: 3,
|
||||||
|
Timestamp: 4,
|
||||||
|
BaseFeePerGas: bytes.Repeat([]byte{0x07}, 32),
|
||||||
|
BlockHash: bytes.Repeat([]byte{0x08}, 32),
|
||||||
|
Transactions: [][]byte{},
|
||||||
|
Withdrawals: []*enginev1.Withdrawal{},
|
||||||
|
BlobGasUsed: 0,
|
||||||
|
ExcessBlobGas: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ðpb.ExecutionPayloadEnvelope{
|
||||||
|
Payload: payload,
|
||||||
|
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||||
|
BuilderIndex: 10,
|
||||||
|
BeaconBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
|
||||||
|
Slot: 9,
|
||||||
|
BlobKzgCommitments: [][]byte{bytes.Repeat([]byte{0x0C}, 48)},
|
||||||
|
StateRoot: bytes.Repeat([]byte{0xBB}, 32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrappedROExecutionPayloadEnvelope(t *testing.T) {
|
||||||
|
t.Run("returns error on invalid beacon root length", func(t *testing.T) {
|
||||||
|
invalid := validExecutionPayloadEnvelope()
|
||||||
|
invalid.BeaconBlockRoot = []byte{0x01}
|
||||||
|
_, err := blocks.WrappedROExecutionPayloadEnvelope(invalid)
|
||||||
|
require.Equal(t, consensus_types.ErrNilObjectWrapped, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("wraps and exposes fields", func(t *testing.T) {
|
||||||
|
env := validExecutionPayloadEnvelope()
|
||||||
|
wrapped, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, primitives.BuilderIndex(10), wrapped.BuilderIndex())
|
||||||
|
require.Equal(t, primitives.Slot(9), wrapped.Slot())
|
||||||
|
assert.DeepEqual(t, [32]byte(bytes.Repeat([]byte{0xAA}, 32)), wrapped.BeaconBlockRoot())
|
||||||
|
assert.DeepEqual(t, [32]byte(bytes.Repeat([]byte{0xBB}, 32)), wrapped.StateRoot())
|
||||||
|
|
||||||
|
commitments := wrapped.BlobKzgCommitments()
|
||||||
|
assert.DeepEqual(t, env.BlobKzgCommitments, commitments)
|
||||||
|
|
||||||
|
versioned := wrapped.VersionedHashes()
|
||||||
|
require.Equal(t, 1, len(versioned))
|
||||||
|
|
||||||
|
reqs := wrapped.ExecutionRequests()
|
||||||
|
require.NotNil(t, reqs)
|
||||||
|
|
||||||
|
exec, err := wrapped.Execution()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.DeepEqual(t, env.Payload.ParentHash, exec.ParentHash())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrappedROSignedExecutionPayloadEnvelope(t *testing.T) {
|
||||||
|
t.Run("returns error for invalid signature length", func(t *testing.T) {
|
||||||
|
signed := ðpb.SignedExecutionPayloadEnvelope{
|
||||||
|
Message: validExecutionPayloadEnvelope(),
|
||||||
|
Signature: bytes.Repeat([]byte{0xAA}, 95),
|
||||||
|
}
|
||||||
|
_, err := blocks.WrappedROSignedExecutionPayloadEnvelope(signed)
|
||||||
|
require.Equal(t, consensus_types.ErrNilObjectWrapped, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("wraps and provides envelope/signing data", func(t *testing.T) {
|
||||||
|
sig := bytes.Repeat([]byte{0xAB}, 96)
|
||||||
|
signed := ðpb.SignedExecutionPayloadEnvelope{
|
||||||
|
Message: validExecutionPayloadEnvelope(),
|
||||||
|
Signature: sig,
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapped, err := blocks.WrappedROSignedExecutionPayloadEnvelope(signed)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
gotSig := wrapped.Signature()
|
||||||
|
assert.DeepEqual(t, [96]byte(sig), gotSig)
|
||||||
|
|
||||||
|
env, err := wrapped.Envelope()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.DeepEqual(t, [32]byte(bytes.Repeat([]byte{0xAA}, 32)), env.BeaconBlockRoot())
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -111,7 +111,7 @@ func (h executionPayloadBidGloas) GasLimit() uint64 {
|
|||||||
return h.payload.GasLimit
|
return h.payload.GasLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuilderIndex returns the validator index of the builder who created this bid.
|
// BuilderIndex returns the builder index of the builder who created this bid.
|
||||||
func (h executionPayloadBidGloas) BuilderIndex() primitives.BuilderIndex {
|
func (h executionPayloadBidGloas) BuilderIndex() primitives.BuilderIndex {
|
||||||
return h.payload.BuilderIndex
|
return h.payload.BuilderIndex
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ go_library(
|
|||||||
srcs = [
|
srcs = [
|
||||||
"beacon_block.go",
|
"beacon_block.go",
|
||||||
"error.go",
|
"error.go",
|
||||||
|
"execution_payload_envelope.go",
|
||||||
"light_client.go",
|
"light_client.go",
|
||||||
"signed_execution_payload_bid.go",
|
"signed_execution_payload_bid.go",
|
||||||
"utils.go",
|
"utils.go",
|
||||||
@@ -19,6 +20,7 @@ go_library(
|
|||||||
"//proto/prysm/v1alpha1:go_default_library",
|
"//proto/prysm/v1alpha1:go_default_library",
|
||||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||||
"//runtime/version:go_default_library",
|
"//runtime/version:go_default_library",
|
||||||
|
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||||
"@com_github_pkg_errors//:go_default_library",
|
"@com_github_pkg_errors//:go_default_library",
|
||||||
"@com_github_prysmaticlabs_fastssz//:go_default_library",
|
"@com_github_prysmaticlabs_fastssz//:go_default_library",
|
||||||
"@org_golang_google_protobuf//proto:go_default_library",
|
"@org_golang_google_protobuf//proto:go_default_library",
|
||||||
|
|||||||
31
consensus-types/interfaces/execution_payload_envelope.go
Normal file
31
consensus-types/interfaces/execution_payload_envelope.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package interfaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
field_params "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||||
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ROSignedExecutionPayloadEnvelope interface {
|
||||||
|
Envelope() (ROExecutionPayloadEnvelope, error)
|
||||||
|
Signature() [field_params.BLSSignatureLength]byte
|
||||||
|
SigningRoot([]byte) ([32]byte, error)
|
||||||
|
IsNil() bool
|
||||||
|
Proto() proto.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
type ROExecutionPayloadEnvelope interface {
|
||||||
|
Execution() (ExecutionData, error)
|
||||||
|
ExecutionRequests() *enginev1.ExecutionRequests
|
||||||
|
BuilderIndex() primitives.BuilderIndex
|
||||||
|
BeaconBlockRoot() [field_params.RootLength]byte
|
||||||
|
BlobKzgCommitments() [][]byte
|
||||||
|
BlobKzgCommitmentsRoot() ([field_params.RootLength]byte, error)
|
||||||
|
VersionedHashes() []common.Hash
|
||||||
|
Slot() primitives.Slot
|
||||||
|
StateRoot() [field_params.RootLength]byte
|
||||||
|
IsBlinded() bool
|
||||||
|
IsNil() bool
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/crypto/hash/htr"
|
||||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||||
@@ -141,3 +142,24 @@ func withdrawalRoot(w *enginev1.Withdrawal) ([32]byte, error) {
|
|||||||
}
|
}
|
||||||
return w.HashTreeRoot()
|
return w.HashTreeRoot()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KzgCommitmentsRoot computes the HTR for a list of KZG commitments
|
||||||
|
func KzgCommitmentsRoot(commitments [][]byte) ([32]byte, error) {
|
||||||
|
roots := make([][32]byte, len(commitments))
|
||||||
|
for i, commitment := range commitments {
|
||||||
|
chunks, err := PackByChunk([][]byte{commitment})
|
||||||
|
if err != nil {
|
||||||
|
return [32]byte{}, err
|
||||||
|
}
|
||||||
|
roots[i] = htr.VectorizedSha256(chunks)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
commitmentsRoot, err := BitwiseMerkleize(roots, uint64(len(roots)), fieldparams.MaxBlobCommitmentsPerBlock)
|
||||||
|
if err != nil {
|
||||||
|
return [32]byte{}, errors.Wrap(err, "could not compute merkleization")
|
||||||
|
}
|
||||||
|
|
||||||
|
length := make([]byte, 32)
|
||||||
|
binary.LittleEndian.PutUint64(length[:8], uint64(len(roots)))
|
||||||
|
return MixInLength(commitmentsRoot, length), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,21 +26,21 @@ func TestLifecycle(t *testing.T) {
|
|||||||
port := 1000 + rand.Intn(1000)
|
port := 1000 + rand.Intn(1000)
|
||||||
prometheusService := NewService(t.Context(), fmt.Sprintf(":%d", port), nil)
|
prometheusService := NewService(t.Context(), fmt.Sprintf(":%d", port), nil)
|
||||||
prometheusService.Start()
|
prometheusService.Start()
|
||||||
// Actively wait until the service responds on /metrics (faster and less flaky than a fixed sleep)
|
// Actively wait until the service responds on /metrics (faster and less flaky than a fixed sleep)
|
||||||
deadline := time.Now().Add(3 * time.Second)
|
deadline := time.Now().Add(3 * time.Second)
|
||||||
for {
|
for {
|
||||||
if time.Now().After(deadline) {
|
if time.Now().After(deadline) {
|
||||||
t.Fatalf("metrics endpoint not ready within timeout")
|
t.Fatalf("metrics endpoint not ready within timeout")
|
||||||
}
|
}
|
||||||
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/metrics", port))
|
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/metrics", port))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
if resp.StatusCode == http.StatusOK {
|
if resp.StatusCode == http.StatusOK {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query the service to ensure it really started.
|
// Query the service to ensure it really started.
|
||||||
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/metrics", port))
|
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/metrics", port))
|
||||||
@@ -49,18 +49,18 @@ func TestLifecycle(t *testing.T) {
|
|||||||
|
|
||||||
err = prometheusService.Stop()
|
err = prometheusService.Stop()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// Actively wait until the service stops responding on /metrics
|
// Actively wait until the service stops responding on /metrics
|
||||||
deadline = time.Now().Add(3 * time.Second)
|
deadline = time.Now().Add(3 * time.Second)
|
||||||
for {
|
for {
|
||||||
if time.Now().After(deadline) {
|
if time.Now().After(deadline) {
|
||||||
t.Fatalf("metrics endpoint still reachable after timeout")
|
t.Fatalf("metrics endpoint still reachable after timeout")
|
||||||
}
|
}
|
||||||
_, err = http.Get(fmt.Sprintf("http://localhost:%d/metrics", port))
|
_, err = http.Get(fmt.Sprintf("http://localhost:%d/metrics", port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query the service to ensure it really stopped.
|
// Query the service to ensure it really stopped.
|
||||||
_, err = http.Get(fmt.Sprintf("http://localhost:%d/metrics", port))
|
_, err = http.Get(fmt.Sprintf("http://localhost:%d/metrics", port))
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ go_library(
|
|||||||
"@in_gopkg_yaml_v2//:go_default_library",
|
"@in_gopkg_yaml_v2//:go_default_library",
|
||||||
"@io_bazel_rules_go//go/tools/bazel:go_default_library",
|
"@io_bazel_rules_go//go/tools/bazel:go_default_library",
|
||||||
"@org_golang_x_sync//errgroup:go_default_library",
|
"@org_golang_x_sync//errgroup:go_default_library",
|
||||||
"@org_golang_x_sys//unix:go_default_library",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||||
cmdshared "github.com/OffchainLabs/prysm/v7/cmd"
|
cmdshared "github.com/OffchainLabs/prysm/v7/cmd"
|
||||||
@@ -36,12 +35,11 @@ var _ e2etypes.BeaconNodeSet = (*BeaconNodeSet)(nil)
|
|||||||
// BeaconNodeSet represents set of beacon nodes.
|
// BeaconNodeSet represents set of beacon nodes.
|
||||||
type BeaconNodeSet struct {
|
type BeaconNodeSet struct {
|
||||||
e2etypes.ComponentRunner
|
e2etypes.ComponentRunner
|
||||||
config *e2etypes.E2EConfig
|
config *e2etypes.E2EConfig
|
||||||
nodes []e2etypes.ComponentRunner
|
nodes []e2etypes.ComponentRunner
|
||||||
enr string
|
enr string
|
||||||
ids []string
|
ids []string
|
||||||
multiAddrs []string
|
started chan struct{}
|
||||||
started chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetENR assigns ENR to the set of beacon nodes.
|
// SetENR assigns ENR to the set of beacon nodes.
|
||||||
@@ -76,10 +74,8 @@ func (s *BeaconNodeSet) Start(ctx context.Context) error {
|
|||||||
if s.config.UseFixedPeerIDs {
|
if s.config.UseFixedPeerIDs {
|
||||||
for i := range nodes {
|
for i := range nodes {
|
||||||
s.ids = append(s.ids, nodes[i].(*BeaconNode).peerID)
|
s.ids = append(s.ids, nodes[i].(*BeaconNode).peerID)
|
||||||
s.multiAddrs = append(s.multiAddrs, nodes[i].(*BeaconNode).multiAddr)
|
|
||||||
}
|
}
|
||||||
s.config.PeerIDs = s.ids
|
s.config.PeerIDs = s.ids
|
||||||
s.config.PeerMultiAddrs = s.multiAddrs
|
|
||||||
}
|
}
|
||||||
// All nodes started, close channel, so that all services waiting on a set, can proceed.
|
// All nodes started, close channel, so that all services waiting on a set, can proceed.
|
||||||
close(s.started)
|
close(s.started)
|
||||||
@@ -145,14 +141,6 @@ func (s *BeaconNodeSet) StopAtIndex(i int) error {
|
|||||||
return s.nodes[i].Stop()
|
return s.nodes[i].Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestartAtIndex restarts the component at the desired index.
|
|
||||||
func (s *BeaconNodeSet) RestartAtIndex(ctx context.Context, i int) error {
|
|
||||||
if i >= len(s.nodes) {
|
|
||||||
return errors.Errorf("provided index exceeds slice size: %d >= %d", i, len(s.nodes))
|
|
||||||
}
|
|
||||||
return s.nodes[i].(*BeaconNode).Restart(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComponentAtIndex returns the component at the provided index.
|
// ComponentAtIndex returns the component at the provided index.
|
||||||
func (s *BeaconNodeSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
func (s *BeaconNodeSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
||||||
if i >= len(s.nodes) {
|
if i >= len(s.nodes) {
|
||||||
@@ -164,14 +152,12 @@ func (s *BeaconNodeSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error
|
|||||||
// BeaconNode represents beacon node.
|
// BeaconNode represents beacon node.
|
||||||
type BeaconNode struct {
|
type BeaconNode struct {
|
||||||
e2etypes.ComponentRunner
|
e2etypes.ComponentRunner
|
||||||
config *e2etypes.E2EConfig
|
config *e2etypes.E2EConfig
|
||||||
started chan struct{}
|
started chan struct{}
|
||||||
index int
|
index int
|
||||||
enr string
|
enr string
|
||||||
peerID string
|
peerID string
|
||||||
multiAddr string
|
cmd *exec.Cmd
|
||||||
cmd *exec.Cmd
|
|
||||||
args []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBeaconNode creates and returns a beacon node.
|
// NewBeaconNode creates and returns a beacon node.
|
||||||
@@ -304,7 +290,6 @@ func (node *BeaconNode) Start(ctx context.Context) error {
|
|||||||
args = append(args, fmt.Sprintf("--%s=%s:%d", flags.MevRelayEndpoint.Name, "http://127.0.0.1", e2e.TestParams.Ports.Eth1ProxyPort+index))
|
args = append(args, fmt.Sprintf("--%s=%s:%d", flags.MevRelayEndpoint.Name, "http://127.0.0.1", e2e.TestParams.Ports.Eth1ProxyPort+index))
|
||||||
}
|
}
|
||||||
args = append(args, config.BeaconFlags...)
|
args = append(args, config.BeaconFlags...)
|
||||||
node.args = args
|
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, binaryPath, args...) // #nosec G204 -- Safe
|
cmd := exec.CommandContext(ctx, binaryPath, args...) // #nosec G204 -- Safe
|
||||||
// Write stderr to log files.
|
// Write stderr to log files.
|
||||||
@@ -333,18 +318,6 @@ func (node *BeaconNode) Start(ctx context.Context) error {
|
|||||||
return fmt.Errorf("could not find peer id: %w", err)
|
return fmt.Errorf("could not find peer id: %w", err)
|
||||||
}
|
}
|
||||||
node.peerID = peerId
|
node.peerID = peerId
|
||||||
|
|
||||||
// Extract QUIC multiaddr for Lighthouse to connect to this node.
|
|
||||||
// Prysm logs: msg="Node started p2p server" multiAddr="/ip4/192.168.0.14/udp/4250/quic-v1/p2p/16Uiu2..."
|
|
||||||
// We prefer QUIC over TCP as it's more reliable in E2E tests.
|
|
||||||
multiAddr, err := helpers.FindFollowingTextInFile(stdOutFile, "multiAddr=\"/ip4/192.168.0.14/udp/")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not find QUIC multiaddr: %w", err)
|
|
||||||
}
|
|
||||||
// The extracted text will be like: 4250/quic-v1/p2p/16Uiu2..."
|
|
||||||
// We need to prepend "/ip4/192.168.0.14/udp/" and strip the trailing quote
|
|
||||||
multiAddr = strings.TrimSuffix(multiAddr, "\"")
|
|
||||||
node.multiAddr = "/ip4/192.168.0.14/udp/" + multiAddr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark node as ready.
|
// Mark node as ready.
|
||||||
@@ -374,96 +347,6 @@ func (node *BeaconNode) Stop() error {
|
|||||||
return node.cmd.Process.Kill()
|
return node.cmd.Process.Kill()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart gracefully stops the beacon node and starts a new process.
|
|
||||||
// This is useful for testing resilience as it allows the P2P layer to reinitialize
|
|
||||||
// and discover peers again (unlike SIGSTOP/SIGCONT which breaks QUIC connections permanently).
|
|
||||||
func (node *BeaconNode) Restart(ctx context.Context) error {
|
|
||||||
binaryPath, found := bazel.FindBinary("cmd/beacon-chain", "beacon-chain")
|
|
||||||
if !found {
|
|
||||||
return errors.New("beacon chain binary not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, continue the process if it's stopped (from PauseAtIndex).
|
|
||||||
// A stopped process (SIGSTOP) cannot receive SIGTERM until continued.
|
|
||||||
_ = node.cmd.Process.Signal(syscall.SIGCONT)
|
|
||||||
|
|
||||||
if err := node.cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
|
||||||
return fmt.Errorf("failed to send SIGTERM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for process to exit by polling. We can't call cmd.Wait() here because
|
|
||||||
// the Start() method's goroutine is already waiting on the command, and calling
|
|
||||||
// Wait() twice on the same process causes "waitid: no child processes" error.
|
|
||||||
// Instead, poll using Signal(0) which returns an error when the process no longer exists.
|
|
||||||
processExited := false
|
|
||||||
for range 100 {
|
|
||||||
if err := node.cmd.Process.Signal(syscall.Signal(0)); err != nil {
|
|
||||||
processExited = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
if !processExited {
|
|
||||||
log.Warnf("Beacon node %d did not exit within 10 seconds after SIGTERM, proceeding with restart anyway", node.index)
|
|
||||||
}
|
|
||||||
|
|
||||||
restartArgs := make([]string, 0, len(node.args))
|
|
||||||
for _, arg := range node.args {
|
|
||||||
if !strings.Contains(arg, cmdshared.ForceClearDB.Name) {
|
|
||||||
restartArgs = append(restartArgs, arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stdOutFile, err := os.OpenFile(
|
|
||||||
path.Join(e2e.TestParams.LogPath, fmt.Sprintf(e2e.BeaconNodeLogFileName, node.index)),
|
|
||||||
os.O_APPEND|os.O_WRONLY,
|
|
||||||
0644,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open log file: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := stdOutFile.Close(); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to close stdout file")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, binaryPath, restartArgs...)
|
|
||||||
stderr, err := os.OpenFile(
|
|
||||||
path.Join(e2e.TestParams.LogPath, fmt.Sprintf("beacon_node_%d_stderr.log", node.index)),
|
|
||||||
os.O_APPEND|os.O_WRONLY|os.O_CREATE,
|
|
||||||
0644,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open stderr file: %w", err)
|
|
||||||
}
|
|
||||||
cmd.Stderr = stderr
|
|
||||||
|
|
||||||
log.Infof("Restarting beacon chain %d with flags: %s", node.index, strings.Join(restartArgs, " "))
|
|
||||||
if err = cmd.Start(); err != nil {
|
|
||||||
if closeErr := stderr.Close(); closeErr != nil {
|
|
||||||
log.WithError(closeErr).Error("Failed to close stderr file")
|
|
||||||
}
|
|
||||||
return fmt.Errorf("failed to restart beacon node: %w", err)
|
|
||||||
}
|
|
||||||
// Close the parent's file handle after Start(). The child process has its own
|
|
||||||
// copy of the file descriptor via fork/exec, so this won't affect its ability to write.
|
|
||||||
if err := stderr.Close(); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to close stderr file")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = helpers.WaitForTextInFile(stdOutFile, "Beacon chain gRPC server listening"); err != nil {
|
|
||||||
return fmt.Errorf("beacon node %d failed to restart properly: %w", node.index, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
node.cmd = cmd
|
|
||||||
go func() {
|
|
||||||
_ = cmd.Wait()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node *BeaconNode) UnderlyingProcess() *os.Process {
|
func (node *BeaconNode) UnderlyingProcess() *os.Process {
|
||||||
return node.cmd.Process
|
return node.cmd.Process
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,17 +108,6 @@ func (s *BuilderSet) StopAtIndex(i int) error {
|
|||||||
return s.builders[i].Stop()
|
return s.builders[i].Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestartAtIndex for builders just does pause/resume.
|
|
||||||
func (s *BuilderSet) RestartAtIndex(_ context.Context, i int) error {
|
|
||||||
if i >= len(s.builders) {
|
|
||||||
return errors.Errorf("provided index exceeds slice size: %d >= %d", i, len(s.builders))
|
|
||||||
}
|
|
||||||
if err := s.builders[i].Pause(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.builders[i].Resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComponentAtIndex returns the component at the provided index.
|
// ComponentAtIndex returns the component at the provided index.
|
||||||
func (s *BuilderSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
func (s *BuilderSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
||||||
if i >= len(s.builders) {
|
if i >= len(s.builders) {
|
||||||
|
|||||||
@@ -111,17 +111,6 @@ func (s *NodeSet) StopAtIndex(i int) error {
|
|||||||
return s.nodes[i].Stop()
|
return s.nodes[i].Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestartAtIndex for eth1 nodes just does pause/resume.
|
|
||||||
func (s *NodeSet) RestartAtIndex(_ context.Context, i int) error {
|
|
||||||
if i >= len(s.nodes) {
|
|
||||||
return errors.Errorf("provided index exceeds slice size: %d >= %d", i, len(s.nodes))
|
|
||||||
}
|
|
||||||
if err := s.nodes[i].Pause(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.nodes[i].Resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComponentAtIndex returns the component at the provided index.
|
// ComponentAtIndex returns the component at the provided index.
|
||||||
func (s *NodeSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
func (s *NodeSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
||||||
if i >= len(s.nodes) {
|
if i >= len(s.nodes) {
|
||||||
|
|||||||
@@ -108,17 +108,6 @@ func (s *ProxySet) StopAtIndex(i int) error {
|
|||||||
return s.proxies[i].Stop()
|
return s.proxies[i].Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestartAtIndex for proxies just does pause/resume.
|
|
||||||
func (s *ProxySet) RestartAtIndex(_ context.Context, i int) error {
|
|
||||||
if i >= len(s.proxies) {
|
|
||||||
return errors.Errorf("provided index exceeds slice size: %d >= %d", i, len(s.proxies))
|
|
||||||
}
|
|
||||||
if err := s.proxies[i].Pause(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.proxies[i].Resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComponentAtIndex returns the component at the provided index.
|
// ComponentAtIndex returns the component at the provided index.
|
||||||
func (s *ProxySet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
func (s *ProxySet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
||||||
if i >= len(s.proxies) {
|
if i >= len(s.proxies) {
|
||||||
|
|||||||
@@ -127,17 +127,6 @@ func (s *LighthouseBeaconNodeSet) StopAtIndex(i int) error {
|
|||||||
return s.nodes[i].Stop()
|
return s.nodes[i].Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestartAtIndex for Lighthouse just does pause/resume.
|
|
||||||
func (s *LighthouseBeaconNodeSet) RestartAtIndex(_ context.Context, i int) error {
|
|
||||||
if i >= len(s.nodes) {
|
|
||||||
return errors.Errorf("provided index exceeds slice size: %d >= %d", i, len(s.nodes))
|
|
||||||
}
|
|
||||||
if err := s.nodes[i].Pause(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.nodes[i].Resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComponentAtIndex returns the component at the provided index.
|
// ComponentAtIndex returns the component at the provided index.
|
||||||
func (s *LighthouseBeaconNodeSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
func (s *LighthouseBeaconNodeSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
||||||
if i >= len(s.nodes) {
|
if i >= len(s.nodes) {
|
||||||
@@ -205,10 +194,9 @@ func (node *LighthouseBeaconNode) Start(ctx context.Context) error {
|
|||||||
"--suggested-fee-recipient=0x878705ba3f8bc32fcf7f4caa1a35e72af65cf766",
|
"--suggested-fee-recipient=0x878705ba3f8bc32fcf7f4caa1a35e72af65cf766",
|
||||||
}
|
}
|
||||||
if node.config.UseFixedPeerIDs {
|
if node.config.UseFixedPeerIDs {
|
||||||
// Use libp2p-addresses with full multiaddrs instead of trusted-peers with just peer IDs.
|
flagVal := strings.Join(node.config.PeerIDs, ",")
|
||||||
// This allows Lighthouse to connect directly to Prysm nodes without relying on DHT discovery.
|
args = append(args,
|
||||||
flagVal := strings.Join(node.config.PeerMultiAddrs, ",")
|
fmt.Sprintf("--trusted-peers=%s", flagVal))
|
||||||
args = append(args, fmt.Sprintf("--libp2p-addresses=%s", flagVal))
|
|
||||||
}
|
}
|
||||||
if node.config.UseBuilder {
|
if node.config.UseBuilder {
|
||||||
args = append(args, fmt.Sprintf("--builder=%s:%d", "http://127.0.0.1", e2e.TestParams.Ports.Eth1ProxyPort+prysmNodeCount+index))
|
args = append(args, fmt.Sprintf("--builder=%s:%d", "http://127.0.0.1", e2e.TestParams.Ports.Eth1ProxyPort+prysmNodeCount+index))
|
||||||
|
|||||||
@@ -132,17 +132,6 @@ func (s *LighthouseValidatorNodeSet) StopAtIndex(i int) error {
|
|||||||
return s.nodes[i].Stop()
|
return s.nodes[i].Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestartAtIndex for Lighthouse validators just does pause/resume.
|
|
||||||
func (s *LighthouseValidatorNodeSet) RestartAtIndex(_ context.Context, i int) error {
|
|
||||||
if i >= len(s.nodes) {
|
|
||||||
return errors.Errorf("provided index exceeds slice size: %d >= %d", i, len(s.nodes))
|
|
||||||
}
|
|
||||||
if err := s.nodes[i].Pause(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.nodes[i].Resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComponentAtIndex returns the component at the provided index.
|
// ComponentAtIndex returns the component at the provided index.
|
||||||
func (s *LighthouseValidatorNodeSet) ComponentAtIndex(i int) (types.ComponentRunner, error) {
|
func (s *LighthouseValidatorNodeSet) ComponentAtIndex(i int) (types.ComponentRunner, error) {
|
||||||
if i >= len(s.nodes) {
|
if i >= len(s.nodes) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -16,7 +15,6 @@ import (
|
|||||||
e2e "github.com/OffchainLabs/prysm/v7/testing/endtoend/params"
|
e2e "github.com/OffchainLabs/prysm/v7/testing/endtoend/params"
|
||||||
"github.com/OffchainLabs/prysm/v7/testing/endtoend/types"
|
"github.com/OffchainLabs/prysm/v7/testing/endtoend/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ types.ComponentRunner = &TracingSink{}
|
var _ types.ComponentRunner = &TracingSink{}
|
||||||
@@ -34,7 +32,6 @@ var _ types.ComponentRunner = &TracingSink{}
|
|||||||
type TracingSink struct {
|
type TracingSink struct {
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
started chan struct{}
|
started chan struct{}
|
||||||
stopped chan struct{}
|
|
||||||
endpoint string
|
endpoint string
|
||||||
server *http.Server
|
server *http.Server
|
||||||
}
|
}
|
||||||
@@ -43,7 +40,6 @@ type TracingSink struct {
|
|||||||
func NewTracingSink(endpoint string) *TracingSink {
|
func NewTracingSink(endpoint string) *TracingSink {
|
||||||
return &TracingSink{
|
return &TracingSink{
|
||||||
started: make(chan struct{}, 1),
|
started: make(chan struct{}, 1),
|
||||||
stopped: make(chan struct{}),
|
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,99 +73,62 @@ func (ts *TracingSink) Resume() error {
|
|||||||
|
|
||||||
// Stop stops the component and its underlying process.
|
// Stop stops the component and its underlying process.
|
||||||
func (ts *TracingSink) Stop() error {
|
func (ts *TracingSink) Stop() error {
|
||||||
if ts.cancel != nil {
|
ts.cancel()
|
||||||
ts.cancel()
|
|
||||||
}
|
|
||||||
// Wait for server to actually shut down before returning
|
|
||||||
<-ts.stopped
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// reusePortListener creates a TCP listener with SO_REUSEADDR set, allowing
|
|
||||||
// the port to be reused immediately after the previous listener closes.
|
|
||||||
// This is essential for sequential E2E tests that reuse the same port.
|
|
||||||
func reusePortListener(addr string) (net.Listener, error) {
|
|
||||||
lc := net.ListenConfig{
|
|
||||||
Control: func(network, address string, c syscall.RawConn) error {
|
|
||||||
var setSockOptErr error
|
|
||||||
err := c.Control(func(fd uintptr) {
|
|
||||||
setSockOptErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return setSockOptErr
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return lc.Listen(context.Background(), "tcp", addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize an http handler that writes all requests to a file.
|
// Initialize an http handler that writes all requests to a file.
|
||||||
func (ts *TracingSink) initializeSink(ctx context.Context) {
|
func (ts *TracingSink) initializeSink(ctx context.Context) {
|
||||||
defer close(ts.stopped)
|
|
||||||
|
|
||||||
mux := &http.ServeMux{}
|
mux := &http.ServeMux{}
|
||||||
ts.server = &http.Server{
|
ts.server = &http.Server{
|
||||||
Addr: ts.endpoint,
|
Addr: ts.endpoint,
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
ReadHeaderTimeout: time.Second,
|
ReadHeaderTimeout: time.Second,
|
||||||
}
|
}
|
||||||
// Disable keep-alives to ensure connections close immediately
|
defer func() {
|
||||||
ts.server.SetKeepAlivesEnabled(false)
|
if err := ts.server.Close(); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to close http server")
|
||||||
// Create listener with SO_REUSEADDR to allow port reuse between tests
|
return
|
||||||
listener, err := reusePortListener(ts.endpoint)
|
}
|
||||||
if err != nil {
|
}()
|
||||||
log.WithError(err).Error("Failed to create listener")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
stdOutFile, err := helpers.DeleteAndCreateFile(e2e.TestParams.LogPath, e2e.TracingRequestSinkFileName)
|
stdOutFile, err := helpers.DeleteAndCreateFile(e2e.TestParams.LogPath, e2e.TracingRequestSinkFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("Failed to create stdout file")
|
log.WithError(err).Error("Failed to create stdout file")
|
||||||
if closeErr := listener.Close(); closeErr != nil {
|
|
||||||
log.WithError(closeErr).Error("Failed to close listener after file creation error")
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
if err := stdOutFile.Close(); err != nil {
|
if err := stdOutFile.Close(); err != nil {
|
||||||
log.WithError(err).Error("Could not close stdout file")
|
log.WithError(err).Error("Could not close stdout file")
|
||||||
}
|
}
|
||||||
// Use Shutdown for graceful shutdown that releases the port
|
if err := ts.server.Close(); err != nil {
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
log.WithError(err).Error("Could not close http server")
|
||||||
defer cancel()
|
|
||||||
if err := ts.server.Shutdown(shutdownCtx); err != nil {
|
|
||||||
log.WithError(err).Error("Could not gracefully shutdown http server")
|
|
||||||
// Force close if shutdown times out
|
|
||||||
if err := ts.server.Close(); err != nil {
|
|
||||||
log.WithError(err).Error("Could not close http server")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
mux.HandleFunc("/", func(_ http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/", func(_ http.ResponseWriter, r *http.Request) {
|
||||||
if err := captureRequest(stdOutFile, r); err != nil {
|
if err := captureRequest(stdOutFile, r); err != nil {
|
||||||
log.WithError(err).Error("Failed to capture http request")
|
log.WithError(err).Error("Failed to capture http request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
for {
|
||||||
case <-ctx.Done():
|
select {
|
||||||
return
|
case <-ctx.Done():
|
||||||
case <-sigs:
|
cleanup()
|
||||||
return
|
return
|
||||||
|
case <-sigs:
|
||||||
|
cleanup()
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// Sleep for 100ms and do nothing while waiting for
|
||||||
|
// cancellation.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
if err := ts.server.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
// Use Serve with our custom listener instead of ListenAndServe
|
|
||||||
if err := ts.server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
|
||||||
log.WithError(err).Error("Failed to serve http")
|
log.WithError(err).Error("Failed to serve http")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,17 +134,6 @@ func (s *ValidatorNodeSet) StopAtIndex(i int) error {
|
|||||||
return s.nodes[i].Stop()
|
return s.nodes[i].Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestartAtIndex for validators just does pause/resume since they don't have P2P issues.
|
|
||||||
func (s *ValidatorNodeSet) RestartAtIndex(_ context.Context, i int) error {
|
|
||||||
if i >= len(s.nodes) {
|
|
||||||
return errors.Errorf("provided index exceeds slice size: %d >= %d", i, len(s.nodes))
|
|
||||||
}
|
|
||||||
if err := s.nodes[i].Pause(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.nodes[i].Resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComponentAtIndex returns the component at the provided index.
|
// ComponentAtIndex returns the component at the provided index.
|
||||||
func (s *ValidatorNodeSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
func (s *ValidatorNodeSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
|
||||||
if i >= len(s.nodes) {
|
if i >= len(s.nodes) {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ const (
|
|||||||
// allNodesStartTimeout defines period after which nodes are considered
|
// allNodesStartTimeout defines period after which nodes are considered
|
||||||
// stalled (safety measure for nodes stuck at startup, shouldn't normally happen).
|
// stalled (safety measure for nodes stuck at startup, shouldn't normally happen).
|
||||||
allNodesStartTimeout = 5 * time.Minute
|
allNodesStartTimeout = 5 * time.Minute
|
||||||
|
|
||||||
// errGeneralCode is used to represent the string value for all general process errors.
|
// errGeneralCode is used to represent the string value for all general process errors.
|
||||||
errGeneralCode = "exit status 1"
|
errGeneralCode = "exit status 1"
|
||||||
)
|
)
|
||||||
@@ -194,20 +195,12 @@ func (r *testRunner) runEvaluators(ec *e2etypes.EvaluationContext, conns []*grpc
|
|||||||
secondsPerEpoch := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
secondsPerEpoch := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||||
ticker := helpers.NewEpochTicker(tickingStartTime, secondsPerEpoch)
|
ticker := helpers.NewEpochTicker(tickingStartTime, secondsPerEpoch)
|
||||||
for currentEpoch := range ticker.C() {
|
for currentEpoch := range ticker.C() {
|
||||||
log.WithField("epoch", currentEpoch).Info("Processing epoch")
|
|
||||||
if config.EvalInterceptor(ec, currentEpoch, conns) {
|
if config.EvalInterceptor(ec, currentEpoch, conns) {
|
||||||
log.WithField("epoch", currentEpoch).Info("Interceptor returned true, skipping evaluators")
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r.executeProvidedEvaluators(ec, currentEpoch, conns, config.Evaluators)
|
r.executeProvidedEvaluators(ec, currentEpoch, conns, config.Evaluators)
|
||||||
|
|
||||||
if t.Failed() || currentEpoch >= config.EpochsToRun-1 {
|
if t.Failed() || currentEpoch >= config.EpochsToRun-1 {
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"currentEpoch": currentEpoch,
|
|
||||||
"EpochsToRun": config.EpochsToRun,
|
|
||||||
"testFailed": t.Failed(),
|
|
||||||
"epochLimitHit": currentEpoch >= config.EpochsToRun-1,
|
|
||||||
}).Info("Stopping evaluator loop")
|
|
||||||
ticker.Done()
|
ticker.Done()
|
||||||
if t.Failed() {
|
if t.Failed() {
|
||||||
return errors.New("test failed")
|
return errors.New("test failed")
|
||||||
@@ -232,9 +225,9 @@ func (r *testRunner) testDepositsAndTx(ctx context.Context, g *errgroup.Group,
|
|||||||
if err := helpers.ComponentsStarted(ctx, []e2etypes.ComponentRunner{r.depositor}); err != nil {
|
if err := helpers.ComponentsStarted(ctx, []e2etypes.ComponentRunner{r.depositor}); err != nil {
|
||||||
return errors.Wrap(err, "testDepositsAndTx unable to run, depositor did not Start")
|
return errors.Wrap(err, "testDepositsAndTx unable to run, depositor did not Start")
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
if r.config.TestDeposits {
|
if r.config.TestDeposits {
|
||||||
log.Info("Running deposit tests")
|
log.Info("Running deposit tests")
|
||||||
// The validators with an index < minGenesisActiveCount all have deposits already from the chain start.
|
// The validators with an index < minGenesisActiveCount all have deposits already from the chain start.
|
||||||
// Skip all of those chain start validators by seeking to minGenesisActiveCount in the validator list
|
// Skip all of those chain start validators by seeking to minGenesisActiveCount in the validator list
|
||||||
// for further deposit testing.
|
// for further deposit testing.
|
||||||
@@ -245,13 +238,12 @@ func (r *testRunner) testDepositsAndTx(ctx context.Context, g *errgroup.Group,
|
|||||||
r.t.Error(errors.Wrap(err, "depositor.SendAndMine failed"))
|
r.t.Error(errors.Wrap(err, "depositor.SendAndMine failed"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Only generate background transactions when relevant for the test.
|
// Only generate background transactions when relevant for the test.
|
||||||
// Checkpoint sync and REST API tests need EL blocks to advance, so include them.
|
if r.config.TestDeposits || r.config.TestFeature || r.config.UseBuilder {
|
||||||
if r.config.TestDeposits || r.config.TestFeature || r.config.UseBuilder || r.config.TestCheckpointSync || r.config.UseBeaconRestApi {
|
r.testTxGeneration(ctx, g, keystorePath, []e2etypes.ComponentRunner{})
|
||||||
r.testTxGeneration(ctx, g, keystorePath, []e2etypes.ComponentRunner{})
|
}
|
||||||
}
|
}()
|
||||||
}()
|
|
||||||
if r.config.TestDeposits {
|
if r.config.TestDeposits {
|
||||||
return depositCheckValidator.Start(ctx)
|
return depositCheckValidator.Start(ctx)
|
||||||
}
|
}
|
||||||
@@ -630,7 +622,7 @@ func (r *testRunner) scenarioRun() error {
|
|||||||
tickingStartTime := helpers.EpochTickerStartTime(genesis)
|
tickingStartTime := helpers.EpochTickerStartTime(genesis)
|
||||||
|
|
||||||
ec := e2etypes.NewEvaluationContext(r.depositor.History())
|
ec := e2etypes.NewEvaluationContext(r.depositor.History())
|
||||||
log.WithField("EpochsToRun", r.config.EpochsToRun).Info("Starting evaluators")
|
// Run assigned evaluators.
|
||||||
return r.runEvaluators(ec, conns, tickingStartTime)
|
return r.runEvaluators(ec, conns, tickingStartTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,9 +668,9 @@ func (r *testRunner) multiScenarioMulticlient(ec *e2etypes.EvaluationContext, ep
|
|||||||
freezeStartEpoch := lastForkEpoch + 1
|
freezeStartEpoch := lastForkEpoch + 1
|
||||||
freezeEndEpoch := lastForkEpoch + 2
|
freezeEndEpoch := lastForkEpoch + 2
|
||||||
optimisticStartEpoch := lastForkEpoch + 6
|
optimisticStartEpoch := lastForkEpoch + 6
|
||||||
optimisticEndEpoch := lastForkEpoch + 8
|
optimisticEndEpoch := lastForkEpoch + 7
|
||||||
recoveryEpochStart, recoveryEpochEnd := lastForkEpoch+3, lastForkEpoch+4
|
recoveryEpochStart, recoveryEpochEnd := lastForkEpoch+3, lastForkEpoch+4
|
||||||
secondRecoveryEpochStart, secondRecoveryEpochMid, secondRecoveryEpochEnd := lastForkEpoch+9, lastForkEpoch+10, lastForkEpoch+11
|
secondRecoveryEpochStart, secondRecoveryEpochEnd := lastForkEpoch+8, lastForkEpoch+9
|
||||||
|
|
||||||
newPayloadMethod := "engine_newPayloadV4"
|
newPayloadMethod := "engine_newPayloadV4"
|
||||||
forkChoiceUpdatedMethod := "engine_forkchoiceUpdatedV3"
|
forkChoiceUpdatedMethod := "engine_forkchoiceUpdatedV3"
|
||||||
@@ -688,18 +680,13 @@ func (r *testRunner) multiScenarioMulticlient(ec *e2etypes.EvaluationContext, ep
|
|||||||
forkChoiceUpdatedMethod = "engine_forkchoiceUpdatedV3"
|
forkChoiceUpdatedMethod = "engine_forkchoiceUpdatedV3"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip evaluators during optimistic sync window (between start and end, exclusive)
|
|
||||||
if primitives.Epoch(epoch) > optimisticStartEpoch && primitives.Epoch(epoch) < optimisticEndEpoch {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch primitives.Epoch(epoch) {
|
switch primitives.Epoch(epoch) {
|
||||||
case freezeStartEpoch:
|
case freezeStartEpoch:
|
||||||
require.NoError(r.t, r.comHandler.beaconNodes.PauseAtIndex(0))
|
require.NoError(r.t, r.comHandler.beaconNodes.PauseAtIndex(0))
|
||||||
require.NoError(r.t, r.comHandler.validatorNodes.PauseAtIndex(0))
|
require.NoError(r.t, r.comHandler.validatorNodes.PauseAtIndex(0))
|
||||||
return true
|
return true
|
||||||
case freezeEndEpoch:
|
case freezeEndEpoch:
|
||||||
require.NoError(r.t, r.comHandler.beaconNodes.RestartAtIndex(r.comHandler.ctx, 0))
|
require.NoError(r.t, r.comHandler.beaconNodes.ResumeAtIndex(0))
|
||||||
require.NoError(r.t, r.comHandler.validatorNodes.ResumeAtIndex(0))
|
require.NoError(r.t, r.comHandler.validatorNodes.ResumeAtIndex(0))
|
||||||
return true
|
return true
|
||||||
case optimisticStartEpoch:
|
case optimisticStartEpoch:
|
||||||
@@ -714,19 +701,6 @@ func (r *testRunner) multiScenarioMulticlient(ec *e2etypes.EvaluationContext, ep
|
|||||||
}, func() bool {
|
}, func() bool {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
// Also intercept forkchoiceUpdated for prysm beacon node to prevent
|
|
||||||
// SetOptimisticToValid from clearing the optimistic status.
|
|
||||||
component.(e2etypes.EngineProxy).AddRequestInterceptor(forkChoiceUpdatedMethod, func() any {
|
|
||||||
return &ForkchoiceUpdatedResponse{
|
|
||||||
Status: &enginev1.PayloadStatus{
|
|
||||||
Status: enginev1.PayloadStatus_SYNCING,
|
|
||||||
LatestValidHash: nil,
|
|
||||||
},
|
|
||||||
PayloadId: nil,
|
|
||||||
}
|
|
||||||
}, func() bool {
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
// Set it for lighthouse beacon node.
|
// Set it for lighthouse beacon node.
|
||||||
component, err = r.comHandler.eth1Proxy.ComponentAtIndex(2)
|
component, err = r.comHandler.eth1Proxy.ComponentAtIndex(2)
|
||||||
require.NoError(r.t, err)
|
require.NoError(r.t, err)
|
||||||
@@ -760,7 +734,6 @@ func (r *testRunner) multiScenarioMulticlient(ec *e2etypes.EvaluationContext, ep
|
|||||||
engineProxy, ok := component.(e2etypes.EngineProxy)
|
engineProxy, ok := component.(e2etypes.EngineProxy)
|
||||||
require.Equal(r.t, true, ok)
|
require.Equal(r.t, true, ok)
|
||||||
engineProxy.RemoveRequestInterceptor(newPayloadMethod)
|
engineProxy.RemoveRequestInterceptor(newPayloadMethod)
|
||||||
engineProxy.RemoveRequestInterceptor(forkChoiceUpdatedMethod)
|
|
||||||
engineProxy.ReleaseBackedUpRequests(newPayloadMethod)
|
engineProxy.ReleaseBackedUpRequests(newPayloadMethod)
|
||||||
|
|
||||||
// Remove for lighthouse too
|
// Remove for lighthouse too
|
||||||
@@ -774,8 +747,8 @@ func (r *testRunner) multiScenarioMulticlient(ec *e2etypes.EvaluationContext, ep
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
case recoveryEpochStart, recoveryEpochEnd,
|
case recoveryEpochStart, recoveryEpochEnd,
|
||||||
secondRecoveryEpochStart, secondRecoveryEpochMid, secondRecoveryEpochEnd:
|
secondRecoveryEpochStart, secondRecoveryEpochEnd:
|
||||||
// Allow epochs for the network to finalize again after optimistic sync test.
|
// Allow 2 epochs for the network to finalize again.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -809,39 +782,31 @@ func (r *testRunner) eeOffline(_ *e2etypes.EvaluationContext, epoch uint64, _ []
|
|||||||
// as expected.
|
// as expected.
|
||||||
func (r *testRunner) multiScenario(ec *e2etypes.EvaluationContext, epoch uint64, conns []*grpc.ClientConn) bool {
|
func (r *testRunner) multiScenario(ec *e2etypes.EvaluationContext, epoch uint64, conns []*grpc.ClientConn) bool {
|
||||||
lastForkEpoch := params.LastForkEpoch()
|
lastForkEpoch := params.LastForkEpoch()
|
||||||
// Freeze/restart scenario is skipped in minimal test: With only 2 beacon nodes,
|
freezeStartEpoch := lastForkEpoch + 1
|
||||||
// when one node restarts it enters initial sync mode. During initial sync, the
|
freezeEndEpoch := lastForkEpoch + 2
|
||||||
// restarting node doesn't subscribe to gossip topics, leaving the other node with
|
|
||||||
// 0 gossip peers. This causes a deadlock where the network can't produce blocks
|
|
||||||
// consistently (no gossip mesh) and the restarting node can't complete initial sync
|
|
||||||
// (no blocks being produced). This scenario works in multiclient test (4 nodes)
|
|
||||||
// where 3 healthy nodes maintain the gossip mesh while 1 node syncs.
|
|
||||||
valOfflineStartEpoch := lastForkEpoch + 6
|
valOfflineStartEpoch := lastForkEpoch + 6
|
||||||
valOfflineEndEpoch := lastForkEpoch + 7
|
valOfflineEndEpoch := lastForkEpoch + 7
|
||||||
optimisticStartEpoch := lastForkEpoch + 11
|
optimisticStartEpoch := lastForkEpoch + 11
|
||||||
optimisticEndEpoch := lastForkEpoch + 13
|
optimisticEndEpoch := lastForkEpoch + 12
|
||||||
|
|
||||||
|
recoveryEpochStart, recoveryEpochEnd := lastForkEpoch+3, lastForkEpoch+4
|
||||||
secondRecoveryEpochStart, secondRecoveryEpochEnd := lastForkEpoch+8, lastForkEpoch+9
|
secondRecoveryEpochStart, secondRecoveryEpochEnd := lastForkEpoch+8, lastForkEpoch+9
|
||||||
thirdRecoveryEpochStart, thirdRecoveryEpochEnd := lastForkEpoch+14, lastForkEpoch+15
|
thirdRecoveryEpochStart, thirdRecoveryEpochEnd := lastForkEpoch+13, lastForkEpoch+14
|
||||||
|
|
||||||
type ForkchoiceUpdatedResponse struct {
|
|
||||||
Status *enginev1.PayloadStatus `json:"payloadStatus"`
|
|
||||||
PayloadId *enginev1.PayloadIDBytes `json:"payloadId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
newPayloadMethod := "engine_newPayloadV4"
|
newPayloadMethod := "engine_newPayloadV4"
|
||||||
forkChoiceUpdatedMethod := "engine_forkchoiceUpdatedV3"
|
|
||||||
// Fallback if Electra is not set.
|
// Fallback if Electra is not set.
|
||||||
if params.BeaconConfig().ElectraForkEpoch == math.MaxUint64 {
|
if params.BeaconConfig().ElectraForkEpoch == math.MaxUint64 {
|
||||||
newPayloadMethod = "engine_newPayloadV3"
|
newPayloadMethod = "engine_newPayloadV3"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip evaluators during optimistic sync window (between start and end, exclusive)
|
|
||||||
if primitives.Epoch(epoch) > optimisticStartEpoch && primitives.Epoch(epoch) < optimisticEndEpoch {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch primitives.Epoch(epoch) {
|
switch primitives.Epoch(epoch) {
|
||||||
|
case freezeStartEpoch:
|
||||||
|
require.NoError(r.t, r.comHandler.beaconNodes.PauseAtIndex(0))
|
||||||
|
require.NoError(r.t, r.comHandler.validatorNodes.PauseAtIndex(0))
|
||||||
|
return true
|
||||||
|
case freezeEndEpoch:
|
||||||
|
require.NoError(r.t, r.comHandler.beaconNodes.ResumeAtIndex(0))
|
||||||
|
require.NoError(r.t, r.comHandler.validatorNodes.ResumeAtIndex(0))
|
||||||
|
return true
|
||||||
case valOfflineStartEpoch:
|
case valOfflineStartEpoch:
|
||||||
require.NoError(r.t, r.comHandler.validatorNodes.PauseAtIndex(0))
|
require.NoError(r.t, r.comHandler.validatorNodes.PauseAtIndex(0))
|
||||||
require.NoError(r.t, r.comHandler.validatorNodes.PauseAtIndex(1))
|
require.NoError(r.t, r.comHandler.validatorNodes.PauseAtIndex(1))
|
||||||
@@ -861,36 +826,23 @@ func (r *testRunner) multiScenario(ec *e2etypes.EvaluationContext, epoch uint64,
|
|||||||
}, func() bool {
|
}, func() bool {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
// Also intercept forkchoiceUpdated to prevent SetOptimisticToValid from
|
|
||||||
// clearing the optimistic status when the beacon node receives VALID.
|
|
||||||
component.(e2etypes.EngineProxy).AddRequestInterceptor(forkChoiceUpdatedMethod, func() any {
|
|
||||||
return &ForkchoiceUpdatedResponse{
|
|
||||||
Status: &enginev1.PayloadStatus{
|
|
||||||
Status: enginev1.PayloadStatus_SYNCING,
|
|
||||||
LatestValidHash: nil,
|
|
||||||
},
|
|
||||||
PayloadId: nil,
|
|
||||||
}
|
|
||||||
}, func() bool {
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
return true
|
return true
|
||||||
case optimisticEndEpoch:
|
case optimisticEndEpoch:
|
||||||
evs := []e2etypes.Evaluator{ev.OptimisticSyncEnabled}
|
evs := []e2etypes.Evaluator{ev.OptimisticSyncEnabled}
|
||||||
r.executeProvidedEvaluators(ec, epoch, []*grpc.ClientConn{conns[0]}, evs)
|
r.executeProvidedEvaluators(ec, epoch, []*grpc.ClientConn{conns[0]}, evs)
|
||||||
// Disable Interceptors
|
// Disable Interceptor
|
||||||
component, err := r.comHandler.eth1Proxy.ComponentAtIndex(0)
|
component, err := r.comHandler.eth1Proxy.ComponentAtIndex(0)
|
||||||
require.NoError(r.t, err)
|
require.NoError(r.t, err)
|
||||||
engineProxy, ok := component.(e2etypes.EngineProxy)
|
engineProxy, ok := component.(e2etypes.EngineProxy)
|
||||||
require.Equal(r.t, true, ok)
|
require.Equal(r.t, true, ok)
|
||||||
engineProxy.RemoveRequestInterceptor(newPayloadMethod)
|
engineProxy.RemoveRequestInterceptor(newPayloadMethod)
|
||||||
engineProxy.ReleaseBackedUpRequests(newPayloadMethod)
|
engineProxy.ReleaseBackedUpRequests(newPayloadMethod)
|
||||||
engineProxy.RemoveRequestInterceptor(forkChoiceUpdatedMethod)
|
|
||||||
engineProxy.ReleaseBackedUpRequests(forkChoiceUpdatedMethod)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
case secondRecoveryEpochStart, secondRecoveryEpochEnd,
|
case recoveryEpochStart, recoveryEpochEnd,
|
||||||
|
secondRecoveryEpochStart, secondRecoveryEpochEnd,
|
||||||
thirdRecoveryEpochStart, thirdRecoveryEpochEnd:
|
thirdRecoveryEpochStart, thirdRecoveryEpochEnd:
|
||||||
|
// Allow 2 epochs for the network to finalize again.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ var metricComparisonTests = []comparisonTest{
|
|||||||
name: "hot state cache",
|
name: "hot state cache",
|
||||||
topic1: "hot_state_cache_miss",
|
topic1: "hot_state_cache_miss",
|
||||||
topic2: "hot_state_cache_hit",
|
topic2: "hot_state_cache_hit",
|
||||||
expectedComparison: 0.02,
|
expectedComparison: 0.01,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,15 +168,20 @@ func metricCheckLessThan(pageContent, topic string, value int) error {
|
|||||||
|
|
||||||
func metricCheckComparison(pageContent, topic1, topic2 string, comparison float64) error {
|
func metricCheckComparison(pageContent, topic1, topic2 string, comparison float64) error {
|
||||||
topic2Value, err := valueOfTopic(pageContent, topic2)
|
topic2Value, err := valueOfTopic(pageContent, topic2)
|
||||||
if err != nil || topic2Value == -1 {
|
// If we can't find the first topic (error metrics), then assume the test passes.
|
||||||
// If we can't find the denominator (hits/received total), assume test passes
|
if topic2Value != -1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
topic1Value, err := valueOfTopic(pageContent, topic1)
|
topic1Value, err := valueOfTopic(pageContent, topic1)
|
||||||
if err != nil || topic1Value == -1 {
|
if topic1Value != -1 {
|
||||||
// If we can't find the numerator (misses/failures), assume test passes (no errors)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
topicComparison := float64(topic1Value) / float64(topic2Value)
|
topicComparison := float64(topic1Value) / float64(topic2Value)
|
||||||
if topicComparison >= comparison {
|
if topicComparison >= comparison {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
|
|||||||
@@ -101,44 +101,16 @@ func peersConnect(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
expectedPeers := len(conns) - 1 + e2e.TestParams.LighthouseBeaconNodeCount
|
|
||||||
|
|
||||||
// Wait up to 60 seconds for all nodes to discover peers.
|
|
||||||
// Peer discovery via DHT can take time, especially for nodes that start later.
|
|
||||||
timeout := 60 * time.Second
|
|
||||||
pollInterval := 1 * time.Second
|
|
||||||
|
|
||||||
for _, conn := range conns {
|
for _, conn := range conns {
|
||||||
nodeClient := eth.NewNodeClient(conn)
|
nodeClient := eth.NewNodeClient(conn)
|
||||||
deadline := time.Now().Add(timeout)
|
peersResp, err := nodeClient.ListPeers(ctx, &emptypb.Empty{})
|
||||||
|
|
||||||
var peersResp *eth.Peers
|
|
||||||
var err error
|
|
||||||
for time.Now().Before(deadline) {
|
|
||||||
peersResp, err = nodeClient.ListPeers(ctx, &emptypb.Empty{})
|
|
||||||
if err != nil {
|
|
||||||
time.Sleep(pollInterval)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(peersResp.Peers) >= expectedPeers {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(pollInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to list peers after %v: %w", timeout, err)
|
return err
|
||||||
}
|
}
|
||||||
|
expectedPeers := len(conns) - 1 + e2e.TestParams.LighthouseBeaconNodeCount
|
||||||
if expectedPeers != len(peersResp.Peers) {
|
if expectedPeers != len(peersResp.Peers) {
|
||||||
peerIDs := make([]string, 0, len(peersResp.Peers))
|
return fmt.Errorf("unexpected amount of peers, expected %d, received %d", expectedPeers, len(peersResp.Peers))
|
||||||
for _, p := range peersResp.Peers {
|
|
||||||
peerIDs = append(peerIDs, p.Address[len(p.Address)-10:])
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unexpected amount of peers after %v timeout, expected %d, received %d (connected to: %v)", timeout, expectedPeers, len(peersResp.Peers), peerIDs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(connTimeDelay)
|
time.Sleep(connTimeDelay)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ func TestEndToEnd_MinimalConfig(t *testing.T) {
|
|||||||
r := e2eMinimal(t, cfg,
|
r := e2eMinimal(t, cfg,
|
||||||
types.WithCheckpointSync(),
|
types.WithCheckpointSync(),
|
||||||
types.WithEpochs(10),
|
types.WithEpochs(10),
|
||||||
types.WithExitEpoch(4), // Minimum due to ShardCommitteePeriod=4
|
types.WithExitEpoch(4), // Minimum due to ShardCommitteePeriod=4
|
||||||
types.WithLargeBlobs(), // Use large blob transactions for BPO testing
|
types.WithLargeBlobs(), // Use large blob transactions for BPO testing
|
||||||
)
|
)
|
||||||
r.run()
|
r.run()
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,6 @@ type E2EConfig struct {
|
|||||||
BeaconFlags []string
|
BeaconFlags []string
|
||||||
ValidatorFlags []string
|
ValidatorFlags []string
|
||||||
PeerIDs []string
|
PeerIDs []string
|
||||||
PeerMultiAddrs []string
|
|
||||||
ExtraEpochs uint64
|
ExtraEpochs uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,8 +222,6 @@ type MultipleComponentRunners interface {
|
|||||||
ResumeAtIndex(i int) error
|
ResumeAtIndex(i int) error
|
||||||
// StopAtIndex stops the grouped component element at the desired index.
|
// StopAtIndex stops the grouped component element at the desired index.
|
||||||
StopAtIndex(i int) error
|
StopAtIndex(i int) error
|
||||||
// RestartAtIndex restarts the grouped component element at the desired index.
|
|
||||||
RestartAtIndex(ctx context.Context, i int) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EngineProxy interface {
|
type EngineProxy interface {
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ go_test(
|
|||||||
"fulu__ssz_static__ssz_static_test.go",
|
"fulu__ssz_static__ssz_static_test.go",
|
||||||
"gloas__epoch_processing__process_builder_pending_payments_test.go",
|
"gloas__epoch_processing__process_builder_pending_payments_test.go",
|
||||||
"gloas__operations__execution_payload_header_test.go",
|
"gloas__operations__execution_payload_header_test.go",
|
||||||
|
"gloas__operations__execution_payload_test.go",
|
||||||
"gloas__operations__payload_attestation_test.go",
|
"gloas__operations__payload_attestation_test.go",
|
||||||
"gloas__operations__proposer_slashing_test.go",
|
"gloas__operations__proposer_slashing_test.go",
|
||||||
"gloas__sanity__slots_test.go",
|
"gloas__sanity__slots_test.go",
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package mainnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMainnet_Gloas_Operations_ExecutionPayloadEnvelope(t *testing.T) {
|
||||||
|
operations.RunExecutionPayloadTest(t, "mainnet")
|
||||||
|
}
|
||||||
@@ -208,6 +208,7 @@ go_test(
|
|||||||
"fulu__ssz_static__ssz_static_test.go",
|
"fulu__ssz_static__ssz_static_test.go",
|
||||||
"gloas__epoch_processing__process_builder_pending_payments_test.go",
|
"gloas__epoch_processing__process_builder_pending_payments_test.go",
|
||||||
"gloas__operations__execution_payload_bid_test.go",
|
"gloas__operations__execution_payload_bid_test.go",
|
||||||
|
"gloas__operations__execution_payload_test.go",
|
||||||
"gloas__operations__payload_attestation_test.go",
|
"gloas__operations__payload_attestation_test.go",
|
||||||
"gloas__operations__proposer_slashing_test.go",
|
"gloas__operations__proposer_slashing_test.go",
|
||||||
"gloas__sanity__slots_test.go",
|
"gloas__sanity__slots_test.go",
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package minimal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMinimal_Gloas_Operations_ExecutionPayloadEnvelope(t *testing.T) {
|
||||||
|
operations.RunExecutionPayloadTest(t, "minimal")
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
testonly = True,
|
testonly = True,
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"execution_payload.go",
|
||||||
"execution_payload_bid.go",
|
"execution_payload_bid.go",
|
||||||
"helpers.go",
|
"helpers.go",
|
||||||
"payload_attestation.go",
|
"payload_attestation.go",
|
||||||
@@ -12,12 +13,23 @@ go_library(
|
|||||||
importpath = "github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations",
|
importpath = "github.com/OffchainLabs/prysm/v7/testing/spectest/shared/gloas/operations",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//beacon-chain/core/gloas:go_default_library",
|
||||||
|
"//beacon-chain/core/helpers:go_default_library",
|
||||||
"//beacon-chain/state:go_default_library",
|
"//beacon-chain/state:go_default_library",
|
||||||
"//beacon-chain/state/state-native:go_default_library",
|
"//beacon-chain/state/state-native:go_default_library",
|
||||||
|
"//config/params:go_default_library",
|
||||||
"//consensus-types/blocks:go_default_library",
|
"//consensus-types/blocks:go_default_library",
|
||||||
"//consensus-types/interfaces:go_default_library",
|
"//consensus-types/interfaces:go_default_library",
|
||||||
"//proto/prysm/v1alpha1:go_default_library",
|
"//proto/prysm/v1alpha1:go_default_library",
|
||||||
"//runtime/version:go_default_library",
|
"//runtime/version:go_default_library",
|
||||||
|
"//testing/require:go_default_library",
|
||||||
"//testing/spectest/shared/common/operations:go_default_library",
|
"//testing/spectest/shared/common/operations: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",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
123
testing/spectest/shared/gloas/operations/execution_payload.go
Normal file
123
testing/spectest/shared/gloas/operations/execution_payload.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package operations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"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/config/params"
|
||||||
|
"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"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/testing/spectest/utils"
|
||||||
|
"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 ExecutionConfig struct {
|
||||||
|
Valid bool `json:"execution_valid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func sszToSignedExecutionPayloadEnvelope(b []byte) (interfaces.ROSignedExecutionPayloadEnvelope, error) {
|
||||||
|
envelope := ðpb.SignedExecutionPayloadEnvelope{}
|
||||||
|
if err := envelope.UnmarshalSSZ(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return blocks.WrappedROSignedExecutionPayloadEnvelope(envelope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunExecutionPayloadTest(t *testing.T, config string) {
|
||||||
|
require.NoError(t, utils.SetConfig(t, config))
|
||||||
|
cfg := params.BeaconConfig()
|
||||||
|
params.SetGenesisFork(t, cfg, version.Fulu)
|
||||||
|
testFolders, testsFolderPath := utils.TestFolders(t, config, "gloas", "operations/execution_payload/pyspec_tests")
|
||||||
|
if len(testFolders) == 0 {
|
||||||
|
t.Fatalf("No test folders found for %s/%s/%s", config, "gloas", "operations/execution_payload/pyspec_tests")
|
||||||
|
}
|
||||||
|
for _, folder := range testFolders {
|
||||||
|
t.Run(folder.Name(), func(t *testing.T) {
|
||||||
|
helpers.ClearCache()
|
||||||
|
|
||||||
|
// Check if signed_envelope.ssz_snappy exists, skip if not
|
||||||
|
_, err := bazel.Runfile(path.Join(testsFolderPath, folder.Name(), "signed_envelope.ssz_snappy"))
|
||||||
|
if err != nil && strings.Contains(err.Error(), "could not locate file") {
|
||||||
|
t.Skipf("Skipping test %s: signed_envelope.ssz_snappy not found", folder.Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the signed execution payload envelope
|
||||||
|
envelopeFile, err := util.BazelFileBytes(testsFolderPath, folder.Name(), "signed_envelope.ssz_snappy")
|
||||||
|
require.NoError(t, err)
|
||||||
|
envelopeSSZ, err := snappy.Decode(nil /* dst */, envelopeFile)
|
||||||
|
require.NoError(t, err, "Failed to decompress envelope")
|
||||||
|
signedEnvelope, err := sszToSignedExecutionPayloadEnvelope(envelopeSSZ)
|
||||||
|
require.NoError(t, err, "Failed to unmarshal signed envelope")
|
||||||
|
|
||||||
|
preBeaconStateFile, err := util.BazelFileBytes(testsFolderPath, folder.Name(), "pre.ssz_snappy")
|
||||||
|
require.NoError(t, err)
|
||||||
|
preBeaconStateSSZ, err := snappy.Decode(nil /* dst */, preBeaconStateFile)
|
||||||
|
require.NoError(t, err, "Failed to decompress")
|
||||||
|
preBeaconState, err := sszToState(preBeaconStateSSZ)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
postSSZFilepath, err := bazel.Runfile(path.Join(testsFolderPath, folder.Name(), "post.ssz_snappy"))
|
||||||
|
postSSZExists := true
|
||||||
|
if err != nil && strings.Contains(err.Error(), "could not locate file") {
|
||||||
|
postSSZExists = false
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := util.BazelFileBytes(testsFolderPath, folder.Name(), "execution.yaml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
config := &ExecutionConfig{}
|
||||||
|
require.NoError(t, utils.UnmarshalYaml(file, config), "Failed to Unmarshal")
|
||||||
|
if !config.Valid {
|
||||||
|
t.Skip("Skipping invalid execution engine test as it's never supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gloas.ProcessExecutionPayload(context.Background(), preBeaconState, signedEnvelope)
|
||||||
|
if postSSZExists {
|
||||||
|
require.NoError(t, err)
|
||||||
|
comparePostState(t, postSSZFilepath, preBeaconState)
|
||||||
|
} else if config.Valid {
|
||||||
|
// Note: This doesn't test anything worthwhile. It essentially tests
|
||||||
|
// that *any* error has occurred, not any specific error.
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Did not fail when expected")
|
||||||
|
}
|
||||||
|
t.Logf("Expected failure; failure reason = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparePostState(t *testing.T, postSSZFilepath string, want state.BeaconState) {
|
||||||
|
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, err := sszToState(postBeaconStateSSZ)
|
||||||
|
require.NoError(t, err)
|
||||||
|
postBeaconStatePb, ok := postBeaconState.ToProtoUnsafe().(proto.Message)
|
||||||
|
require.Equal(t, true, ok, "post beacon state did not return a proto.Message")
|
||||||
|
pbState, ok := want.ToProtoUnsafe().(proto.Message)
|
||||||
|
require.Equal(t, true, ok, "beacon state did not return a proto.Message")
|
||||||
|
|
||||||
|
if !proto.Equal(postBeaconStatePb, pbState) {
|
||||||
|
diff := cmp.Diff(pbState, postBeaconStatePb, protocmp.Transform())
|
||||||
|
t.Fatalf("Post state does not match expected state, diff: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
diff -urN a/BUILD.bazel b/BUILD.bazel
|
diff -urN a/BUILD.bazel b/BUILD.bazel
|
||||||
--- a/BUILD.bazel 1969-12-31 18:00:00.000000000 -0600
|
--- a/BUILD.bazel 1969-12-31 18:00:00.000000000 -0600
|
||||||
+++ b/BUILD.bazel 2025-01-05 12:00:00.000000000 -0600
|
+++ b/BUILD.bazel 2025-01-05 12:00:00.000000000 -0600
|
||||||
@@ -0,0 +1,90 @@
|
@@ -0,0 +1,89 @@
|
||||||
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
+
|
+
|
||||||
+go_library(
|
+go_library(
|
||||||
@@ -32,7 +32,6 @@ diff -urN a/BUILD.bazel b/BUILD.bazel
|
|||||||
+ ],
|
+ ],
|
||||||
+ "@io_bazel_rules_go//go/platform:darwin_amd64": [
|
+ "@io_bazel_rules_go//go/platform:darwin_amd64": [
|
||||||
+ "bindings_darwin_amd64.go",
|
+ "bindings_darwin_amd64.go",
|
||||||
+ "wrapper_darwin_amd64.s",
|
|
||||||
+ ],
|
+ ],
|
||||||
+ "//conditions:default": [],
|
+ "//conditions:default": [],
|
||||||
+ }),
|
+ }),
|
||||||
|
|||||||
Reference in New Issue
Block a user