mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-29 15:18:10 -05:00
Compare commits
3 Commits
develop
...
debug-stat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f318ffe05 | ||
|
|
3a15c02162 | ||
|
|
fb4847deaa |
@@ -9,6 +9,7 @@ go_library(
|
|||||||
"conversions_blob.go",
|
"conversions_blob.go",
|
||||||
"conversions_block.go",
|
"conversions_block.go",
|
||||||
"conversions_block_execution.go",
|
"conversions_block_execution.go",
|
||||||
|
"conversions_gloas.go",
|
||||||
"conversions_lightclient.go",
|
"conversions_lightclient.go",
|
||||||
"conversions_state.go",
|
"conversions_state.go",
|
||||||
"endpoints_beacon.go",
|
"endpoints_beacon.go",
|
||||||
@@ -57,10 +58,12 @@ go_test(
|
|||||||
],
|
],
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//consensus-types/blocks: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",
|
||||||
"//testing/assert:go_default_library",
|
"//testing/assert:go_default_library",
|
||||||
"//testing/require:go_default_library",
|
"//testing/require:go_default_library",
|
||||||
|
"//testing/util:go_default_library",
|
||||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||||
],
|
],
|
||||||
|
|||||||
81
api/server/structs/conversions_gloas.go
Normal file
81
api/server/structs/conversions_gloas.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||||
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ROExecutionPayloadBidFromConsensus(b interfaces.ROExecutionPayloadBid) *ExecutionPayloadBid {
|
||||||
|
pbh := b.ParentBlockHash()
|
||||||
|
pbr := b.ParentBlockRoot()
|
||||||
|
bh := b.BlockHash()
|
||||||
|
pr := b.PrevRandao()
|
||||||
|
fr := b.FeeRecipient()
|
||||||
|
bcr := b.BlobKzgCommitmentsRoot()
|
||||||
|
return &ExecutionPayloadBid{
|
||||||
|
ParentBlockHash: hexutil.Encode(pbh[:]),
|
||||||
|
ParentBlockRoot: hexutil.Encode(pbr[:]),
|
||||||
|
BlockHash: hexutil.Encode(bh[:]),
|
||||||
|
PrevRandao: hexutil.Encode(pr[:]),
|
||||||
|
FeeRecipient: hexutil.Encode(fr[:]),
|
||||||
|
GasLimit: fmt.Sprintf("%d", b.GasLimit()),
|
||||||
|
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex()),
|
||||||
|
Slot: fmt.Sprintf("%d", b.Slot()),
|
||||||
|
Value: fmt.Sprintf("%d", b.Value()),
|
||||||
|
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment()),
|
||||||
|
BlobKzgCommitmentsRoot: hexutil.Encode(bcr[:]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildersFromConsensus(builders []*ethpb.Builder) []*Builder {
|
||||||
|
newBuilders := make([]*Builder, len(builders))
|
||||||
|
for i, b := range builders {
|
||||||
|
newBuilders[i] = BuilderFromConsensus(b)
|
||||||
|
}
|
||||||
|
return newBuilders
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuilderFromConsensus(b *ethpb.Builder) *Builder {
|
||||||
|
return &Builder{
|
||||||
|
Pubkey: hexutil.Encode(b.Pubkey),
|
||||||
|
Version: hexutil.Encode(b.Version),
|
||||||
|
ExecutionAddress: hexutil.Encode(b.ExecutionAddress),
|
||||||
|
Balance: fmt.Sprintf("%d", b.Balance),
|
||||||
|
DepositEpoch: fmt.Sprintf("%d", b.DepositEpoch),
|
||||||
|
WithdrawableEpoch: fmt.Sprintf("%d", b.WithdrawableEpoch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuilderPendingPaymentsFromConsensus(payments []*ethpb.BuilderPendingPayment) []*BuilderPendingPayment {
|
||||||
|
newPayments := make([]*BuilderPendingPayment, len(payments))
|
||||||
|
for i, p := range payments {
|
||||||
|
newPayments[i] = BuilderPendingPaymentFromConsensus(p)
|
||||||
|
}
|
||||||
|
return newPayments
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuilderPendingPaymentFromConsensus(p *ethpb.BuilderPendingPayment) *BuilderPendingPayment {
|
||||||
|
return &BuilderPendingPayment{
|
||||||
|
Weight: fmt.Sprintf("%d", p.Weight),
|
||||||
|
Withdrawal: BuilderPendingWithdrawalFromConsensus(p.Withdrawal),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuilderPendingWithdrawalsFromConsensus(withdrawals []*ethpb.BuilderPendingWithdrawal) []*BuilderPendingWithdrawal {
|
||||||
|
newWithdrawals := make([]*BuilderPendingWithdrawal, len(withdrawals))
|
||||||
|
for i, w := range withdrawals {
|
||||||
|
newWithdrawals[i] = BuilderPendingWithdrawalFromConsensus(w)
|
||||||
|
}
|
||||||
|
return newWithdrawals
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuilderPendingWithdrawalFromConsensus(w *ethpb.BuilderPendingWithdrawal) *BuilderPendingWithdrawal {
|
||||||
|
return &BuilderPendingWithdrawal{
|
||||||
|
FeeRecipient: hexutil.Encode(w.FeeRecipient),
|
||||||
|
Amount: fmt.Sprintf("%d", w.Amount),
|
||||||
|
BuilderIndex: fmt.Sprintf("%d", w.BuilderIndex),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -972,3 +972,223 @@ func BeaconStateFuluFromConsensus(st beaconState.BeaconState) (*BeaconStateFulu,
|
|||||||
ProposerLookahead: lookahead,
|
ProposerLookahead: lookahead,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Gloas
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func BeaconStateGloasFromConsensus(st beaconState.BeaconState) (*BeaconStateGloas, error) {
|
||||||
|
srcBr := st.BlockRoots()
|
||||||
|
br := make([]string, len(srcBr))
|
||||||
|
for i, r := range srcBr {
|
||||||
|
br[i] = hexutil.Encode(r)
|
||||||
|
}
|
||||||
|
srcSr := st.StateRoots()
|
||||||
|
sr := make([]string, len(srcSr))
|
||||||
|
for i, r := range srcSr {
|
||||||
|
sr[i] = hexutil.Encode(r)
|
||||||
|
}
|
||||||
|
srcHr := st.HistoricalRoots()
|
||||||
|
hr := make([]string, len(srcHr))
|
||||||
|
for i, r := range srcHr {
|
||||||
|
hr[i] = hexutil.Encode(r)
|
||||||
|
}
|
||||||
|
srcVotes := st.Eth1DataVotes()
|
||||||
|
votes := make([]*Eth1Data, len(srcVotes))
|
||||||
|
for i, e := range srcVotes {
|
||||||
|
votes[i] = Eth1DataFromConsensus(e)
|
||||||
|
}
|
||||||
|
srcVals := st.Validators()
|
||||||
|
vals := make([]*Validator, len(srcVals))
|
||||||
|
for i, v := range srcVals {
|
||||||
|
vals[i] = ValidatorFromConsensus(v)
|
||||||
|
}
|
||||||
|
srcBals := st.Balances()
|
||||||
|
bals := make([]string, len(srcBals))
|
||||||
|
for i, b := range srcBals {
|
||||||
|
bals[i] = fmt.Sprintf("%d", b)
|
||||||
|
}
|
||||||
|
srcRm := st.RandaoMixes()
|
||||||
|
rm := make([]string, len(srcRm))
|
||||||
|
for i, m := range srcRm {
|
||||||
|
rm[i] = hexutil.Encode(m)
|
||||||
|
}
|
||||||
|
srcSlashings := st.Slashings()
|
||||||
|
slashings := make([]string, len(srcSlashings))
|
||||||
|
for i, s := range srcSlashings {
|
||||||
|
slashings[i] = fmt.Sprintf("%d", s)
|
||||||
|
}
|
||||||
|
srcPrevPart, err := st.PreviousEpochParticipation()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
prevPart := make([]string, len(srcPrevPart))
|
||||||
|
for i, p := range srcPrevPart {
|
||||||
|
prevPart[i] = fmt.Sprintf("%d", p)
|
||||||
|
}
|
||||||
|
srcCurrPart, err := st.CurrentEpochParticipation()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
currPart := make([]string, len(srcCurrPart))
|
||||||
|
for i, p := range srcCurrPart {
|
||||||
|
currPart[i] = fmt.Sprintf("%d", p)
|
||||||
|
}
|
||||||
|
srcIs, err := st.InactivityScores()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
is := make([]string, len(srcIs))
|
||||||
|
for i, s := range srcIs {
|
||||||
|
is[i] = fmt.Sprintf("%d", s)
|
||||||
|
}
|
||||||
|
currSc, err := st.CurrentSyncCommittee()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nextSc, err := st.NextSyncCommittee()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
srcHs, err := st.HistoricalSummaries()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hs := make([]*HistoricalSummary, len(srcHs))
|
||||||
|
for i, s := range srcHs {
|
||||||
|
hs[i] = HistoricalSummaryFromConsensus(s)
|
||||||
|
}
|
||||||
|
nwi, err := st.NextWithdrawalIndex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nwvi, err := st.NextWithdrawalValidatorIndex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
drsi, err := st.DepositRequestsStartIndex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dbtc, err := st.DepositBalanceToConsume()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ebtc, err := st.ExitBalanceToConsume()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
eee, err := st.EarliestExitEpoch()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cbtc, err := st.ConsolidationBalanceToConsume()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ece, err := st.EarliestConsolidationEpoch()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pbd, err := st.PendingDeposits()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ppw, err := st.PendingPartialWithdrawals()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pc, err := st.PendingConsolidations()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
srcLookahead, err := st.ProposerLookahead()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lookahead := make([]string, len(srcLookahead))
|
||||||
|
for i, v := range srcLookahead {
|
||||||
|
lookahead[i] = fmt.Sprintf("%d", uint64(v))
|
||||||
|
}
|
||||||
|
// Gloas-specific fields
|
||||||
|
lepb, err := st.LatestExecutionPayloadBid()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
builders, err := st.Builders()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nwbi, err := st.NextWithdrawalBuilderIndex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
epa, err := st.ExecutionPayloadAvailability()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bpp, err := st.BuilderPendingPayments()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bpw, err := st.BuilderPendingWithdrawals()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lbh, err := st.LatestBlockHash()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pew, err := st.PayloadExpectedWithdrawals()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BeaconStateGloas{
|
||||||
|
GenesisTime: fmt.Sprintf("%d", st.GenesisTime().Unix()),
|
||||||
|
GenesisValidatorsRoot: hexutil.Encode(st.GenesisValidatorsRoot()),
|
||||||
|
Slot: fmt.Sprintf("%d", st.Slot()),
|
||||||
|
Fork: ForkFromConsensus(st.Fork()),
|
||||||
|
LatestBlockHeader: BeaconBlockHeaderFromConsensus(st.LatestBlockHeader()),
|
||||||
|
BlockRoots: br,
|
||||||
|
StateRoots: sr,
|
||||||
|
HistoricalRoots: hr,
|
||||||
|
Eth1Data: Eth1DataFromConsensus(st.Eth1Data()),
|
||||||
|
Eth1DataVotes: votes,
|
||||||
|
Eth1DepositIndex: fmt.Sprintf("%d", st.Eth1DepositIndex()),
|
||||||
|
Validators: vals,
|
||||||
|
Balances: bals,
|
||||||
|
RandaoMixes: rm,
|
||||||
|
Slashings: slashings,
|
||||||
|
PreviousEpochParticipation: prevPart,
|
||||||
|
CurrentEpochParticipation: currPart,
|
||||||
|
JustificationBits: hexutil.Encode(st.JustificationBits()),
|
||||||
|
PreviousJustifiedCheckpoint: CheckpointFromConsensus(st.PreviousJustifiedCheckpoint()),
|
||||||
|
CurrentJustifiedCheckpoint: CheckpointFromConsensus(st.CurrentJustifiedCheckpoint()),
|
||||||
|
FinalizedCheckpoint: CheckpointFromConsensus(st.FinalizedCheckpoint()),
|
||||||
|
InactivityScores: is,
|
||||||
|
CurrentSyncCommittee: SyncCommitteeFromConsensus(currSc),
|
||||||
|
NextSyncCommittee: SyncCommitteeFromConsensus(nextSc),
|
||||||
|
NextWithdrawalIndex: fmt.Sprintf("%d", nwi),
|
||||||
|
NextWithdrawalValidatorIndex: fmt.Sprintf("%d", nwvi),
|
||||||
|
HistoricalSummaries: hs,
|
||||||
|
DepositRequestsStartIndex: fmt.Sprintf("%d", drsi),
|
||||||
|
DepositBalanceToConsume: fmt.Sprintf("%d", dbtc),
|
||||||
|
ExitBalanceToConsume: fmt.Sprintf("%d", ebtc),
|
||||||
|
EarliestExitEpoch: fmt.Sprintf("%d", eee),
|
||||||
|
ConsolidationBalanceToConsume: fmt.Sprintf("%d", cbtc),
|
||||||
|
EarliestConsolidationEpoch: fmt.Sprintf("%d", ece),
|
||||||
|
PendingDeposits: PendingDepositsFromConsensus(pbd),
|
||||||
|
PendingPartialWithdrawals: PendingPartialWithdrawalsFromConsensus(ppw),
|
||||||
|
PendingConsolidations: PendingConsolidationsFromConsensus(pc),
|
||||||
|
ProposerLookahead: lookahead,
|
||||||
|
LatestExecutionPayloadBid: ROExecutionPayloadBidFromConsensus(lepb),
|
||||||
|
Builders: BuildersFromConsensus(builders),
|
||||||
|
NextWithdrawalBuilderIndex: fmt.Sprintf("%d", nwbi),
|
||||||
|
ExecutionPayloadAvailability: hexutil.Encode(epa),
|
||||||
|
BuilderPendingPayments: BuilderPendingPaymentsFromConsensus(bpp),
|
||||||
|
BuilderPendingWithdrawals: BuilderPendingWithdrawalsFromConsensus(bpw),
|
||||||
|
LatestBlockHash: hexutil.Encode(lbh[:]),
|
||||||
|
PayloadExpectedWithdrawals: WithdrawalsFromConsensus(pew),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package structs
|
package structs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||||
|
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||||
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||||
|
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -346,6 +350,176 @@ func TestAttesterSlashing_FromConsensus(t *testing.T) {
|
|||||||
assert.DeepEqual(t, expectedResult, result)
|
assert.DeepEqual(t, expectedResult, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestROExecutionPayloadBidFromConsensus(t *testing.T) {
|
||||||
|
bid := ð.ExecutionPayloadBid{
|
||||||
|
ParentBlockHash: bytes.Repeat([]byte{0x01}, 32),
|
||||||
|
ParentBlockRoot: bytes.Repeat([]byte{0x02}, 32),
|
||||||
|
BlockHash: bytes.Repeat([]byte{0x03}, 32),
|
||||||
|
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
|
||||||
|
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
|
||||||
|
GasLimit: 100,
|
||||||
|
BuilderIndex: 7,
|
||||||
|
Slot: 9,
|
||||||
|
Value: 11,
|
||||||
|
ExecutionPayment: 22,
|
||||||
|
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x06}, 32),
|
||||||
|
}
|
||||||
|
roBid, err := blocks.WrappedROExecutionPayloadBid(bid)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got := ROExecutionPayloadBidFromConsensus(roBid)
|
||||||
|
want := &ExecutionPayloadBid{
|
||||||
|
ParentBlockHash: hexutil.Encode(bid.ParentBlockHash),
|
||||||
|
ParentBlockRoot: hexutil.Encode(bid.ParentBlockRoot),
|
||||||
|
BlockHash: hexutil.Encode(bid.BlockHash),
|
||||||
|
PrevRandao: hexutil.Encode(bid.PrevRandao),
|
||||||
|
FeeRecipient: hexutil.Encode(bid.FeeRecipient),
|
||||||
|
GasLimit: "100",
|
||||||
|
BuilderIndex: "7",
|
||||||
|
Slot: "9",
|
||||||
|
Value: "11",
|
||||||
|
ExecutionPayment: "22",
|
||||||
|
BlobKzgCommitmentsRoot: hexutil.Encode(bid.BlobKzgCommitmentsRoot),
|
||||||
|
}
|
||||||
|
assert.DeepEqual(t, want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderConversionsFromConsensus(t *testing.T) {
|
||||||
|
builder := ð.Builder{
|
||||||
|
Pubkey: bytes.Repeat([]byte{0xAA}, 48),
|
||||||
|
Version: bytes.Repeat([]byte{0x01}, 4),
|
||||||
|
ExecutionAddress: bytes.Repeat([]byte{0xBB}, 20),
|
||||||
|
Balance: 42,
|
||||||
|
DepositEpoch: 3,
|
||||||
|
WithdrawableEpoch: 4,
|
||||||
|
}
|
||||||
|
wantBuilder := &Builder{
|
||||||
|
Pubkey: hexutil.Encode(builder.Pubkey),
|
||||||
|
Version: hexutil.Encode(builder.Version),
|
||||||
|
ExecutionAddress: hexutil.Encode(builder.ExecutionAddress),
|
||||||
|
Balance: "42",
|
||||||
|
DepositEpoch: "3",
|
||||||
|
WithdrawableEpoch: "4",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.DeepEqual(t, wantBuilder, BuilderFromConsensus(builder))
|
||||||
|
assert.DeepEqual(t, []*Builder{wantBuilder}, BuildersFromConsensus([]*eth.Builder{builder}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPendingPaymentConversionsFromConsensus(t *testing.T) {
|
||||||
|
withdrawal := ð.BuilderPendingWithdrawal{
|
||||||
|
FeeRecipient: bytes.Repeat([]byte{0x10}, 20),
|
||||||
|
Amount: 15,
|
||||||
|
BuilderIndex: 2,
|
||||||
|
}
|
||||||
|
payment := ð.BuilderPendingPayment{
|
||||||
|
Weight: 5,
|
||||||
|
Withdrawal: withdrawal,
|
||||||
|
}
|
||||||
|
wantWithdrawal := &BuilderPendingWithdrawal{
|
||||||
|
FeeRecipient: hexutil.Encode(withdrawal.FeeRecipient),
|
||||||
|
Amount: "15",
|
||||||
|
BuilderIndex: "2",
|
||||||
|
}
|
||||||
|
wantPayment := &BuilderPendingPayment{
|
||||||
|
Weight: "5",
|
||||||
|
Withdrawal: wantWithdrawal,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.DeepEqual(t, wantPayment, BuilderPendingPaymentFromConsensus(payment))
|
||||||
|
assert.DeepEqual(t, []*BuilderPendingPayment{wantPayment}, BuilderPendingPaymentsFromConsensus([]*eth.BuilderPendingPayment{payment}))
|
||||||
|
assert.DeepEqual(t, wantWithdrawal, BuilderPendingWithdrawalFromConsensus(withdrawal))
|
||||||
|
assert.DeepEqual(t, []*BuilderPendingWithdrawal{wantWithdrawal}, BuilderPendingWithdrawalsFromConsensus([]*eth.BuilderPendingWithdrawal{withdrawal}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBeaconStateGloasFromConsensus(t *testing.T) {
|
||||||
|
st, err := util.NewBeaconStateGloas(func(state *eth.BeaconStateGloas) error {
|
||||||
|
state.GenesisTime = 123
|
||||||
|
state.GenesisValidatorsRoot = bytes.Repeat([]byte{0x10}, 32)
|
||||||
|
state.Slot = 5
|
||||||
|
state.ProposerLookahead = []uint64{1, 2}
|
||||||
|
state.LatestExecutionPayloadBid = ð.ExecutionPayloadBid{
|
||||||
|
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32),
|
||||||
|
ParentBlockRoot: bytes.Repeat([]byte{0x12}, 32),
|
||||||
|
BlockHash: bytes.Repeat([]byte{0x13}, 32),
|
||||||
|
PrevRandao: bytes.Repeat([]byte{0x14}, 32),
|
||||||
|
FeeRecipient: bytes.Repeat([]byte{0x15}, 20),
|
||||||
|
GasLimit: 64,
|
||||||
|
BuilderIndex: 3,
|
||||||
|
Slot: 5,
|
||||||
|
Value: 99,
|
||||||
|
ExecutionPayment: 7,
|
||||||
|
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x16}, 32),
|
||||||
|
}
|
||||||
|
state.Builders = []*eth.Builder{
|
||||||
|
{
|
||||||
|
Pubkey: bytes.Repeat([]byte{0x20}, 48),
|
||||||
|
Version: bytes.Repeat([]byte{0x21}, 4),
|
||||||
|
ExecutionAddress: bytes.Repeat([]byte{0x22}, 20),
|
||||||
|
Balance: 88,
|
||||||
|
DepositEpoch: 1,
|
||||||
|
WithdrawableEpoch: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
state.NextWithdrawalBuilderIndex = 9
|
||||||
|
state.ExecutionPayloadAvailability = []byte{0x01, 0x02}
|
||||||
|
state.BuilderPendingPayments = []*eth.BuilderPendingPayment{
|
||||||
|
{
|
||||||
|
Weight: 3,
|
||||||
|
Withdrawal: ð.BuilderPendingWithdrawal{
|
||||||
|
FeeRecipient: bytes.Repeat([]byte{0x23}, 20),
|
||||||
|
Amount: 4,
|
||||||
|
BuilderIndex: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
state.BuilderPendingWithdrawals = []*eth.BuilderPendingWithdrawal{
|
||||||
|
{
|
||||||
|
FeeRecipient: bytes.Repeat([]byte{0x24}, 20),
|
||||||
|
Amount: 6,
|
||||||
|
BuilderIndex: 7,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
state.LatestBlockHash = bytes.Repeat([]byte{0x25}, 32)
|
||||||
|
state.PayloadExpectedWithdrawals = []*enginev1.Withdrawal{
|
||||||
|
{Index: 1, ValidatorIndex: 2, Address: bytes.Repeat([]byte{0x26}, 20), Amount: 10},
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got, err := BeaconStateGloasFromConsensus(st)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "123", got.GenesisTime)
|
||||||
|
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x10}, 32)), got.GenesisValidatorsRoot)
|
||||||
|
require.Equal(t, "5", got.Slot)
|
||||||
|
require.DeepEqual(t, []string{"1", "2"}, got.ProposerLookahead)
|
||||||
|
require.Equal(t, "9", got.NextWithdrawalBuilderIndex)
|
||||||
|
require.Equal(t, hexutil.Encode([]byte{0x01, 0x02}), got.ExecutionPayloadAvailability)
|
||||||
|
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x25}, 32)), got.LatestBlockHash)
|
||||||
|
|
||||||
|
require.NotNil(t, got.LatestExecutionPayloadBid)
|
||||||
|
require.Equal(t, "64", got.LatestExecutionPayloadBid.GasLimit)
|
||||||
|
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x11}, 32)), got.LatestExecutionPayloadBid.ParentBlockHash)
|
||||||
|
|
||||||
|
require.NotNil(t, got.Builders)
|
||||||
|
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x20}, 48)), got.Builders[0].Pubkey)
|
||||||
|
require.Equal(t, "88", got.Builders[0].Balance)
|
||||||
|
|
||||||
|
require.Equal(t, "3", got.BuilderPendingPayments[0].Weight)
|
||||||
|
require.Equal(t, "4", got.BuilderPendingPayments[0].Withdrawal.Amount)
|
||||||
|
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x23}, 20)), got.BuilderPendingPayments[0].Withdrawal.FeeRecipient)
|
||||||
|
|
||||||
|
require.Equal(t, "6", got.BuilderPendingWithdrawals[0].Amount)
|
||||||
|
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x24}, 20)), got.BuilderPendingWithdrawals[0].FeeRecipient)
|
||||||
|
|
||||||
|
require.Equal(t, "1", got.PayloadExpectedWithdrawals[0].WithdrawalIndex)
|
||||||
|
require.Equal(t, "2", got.PayloadExpectedWithdrawals[0].ValidatorIndex)
|
||||||
|
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x26}, 20)), got.PayloadExpectedWithdrawals[0].ExecutionAddress)
|
||||||
|
require.Equal(t, "10", got.PayloadExpectedWithdrawals[0].Amount)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIndexedAttestation_ToConsensus(t *testing.T) {
|
func TestIndexedAttestation_ToConsensus(t *testing.T) {
|
||||||
a := &IndexedAttestation{
|
a := &IndexedAttestation{
|
||||||
AttestingIndices: []string{"1"},
|
AttestingIndices: []string{"1"},
|
||||||
|
|||||||
@@ -262,3 +262,23 @@ type PendingConsolidation struct {
|
|||||||
SourceIndex string `json:"source_index"`
|
SourceIndex string `json:"source_index"`
|
||||||
TargetIndex string `json:"target_index"`
|
TargetIndex string `json:"target_index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
Pubkey string `json:"pubkey"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
ExecutionAddress string `json:"execution_address"`
|
||||||
|
Balance string `json:"balance"`
|
||||||
|
DepositEpoch string `json:"deposit_epoch"`
|
||||||
|
WithdrawableEpoch string `json:"withdrawable_epoch"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BuilderPendingPayment struct {
|
||||||
|
Weight string `json:"weight"`
|
||||||
|
Withdrawal *BuilderPendingWithdrawal `json:"withdrawal"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BuilderPendingWithdrawal struct {
|
||||||
|
FeeRecipient string `json:"fee_recipient"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
BuilderIndex string `json:"builder_index"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -221,3 +221,51 @@ type BeaconStateFulu struct {
|
|||||||
PendingConsolidations []*PendingConsolidation `json:"pending_consolidations"`
|
PendingConsolidations []*PendingConsolidation `json:"pending_consolidations"`
|
||||||
ProposerLookahead []string `json:"proposer_lookahead"`
|
ProposerLookahead []string `json:"proposer_lookahead"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BeaconStateGloas struct {
|
||||||
|
GenesisTime string `json:"genesis_time"`
|
||||||
|
GenesisValidatorsRoot string `json:"genesis_validators_root"`
|
||||||
|
Slot string `json:"slot"`
|
||||||
|
Fork *Fork `json:"fork"`
|
||||||
|
LatestBlockHeader *BeaconBlockHeader `json:"latest_block_header"`
|
||||||
|
BlockRoots []string `json:"block_roots"`
|
||||||
|
StateRoots []string `json:"state_roots"`
|
||||||
|
HistoricalRoots []string `json:"historical_roots"`
|
||||||
|
Eth1Data *Eth1Data `json:"eth1_data"`
|
||||||
|
Eth1DataVotes []*Eth1Data `json:"eth1_data_votes"`
|
||||||
|
Eth1DepositIndex string `json:"eth1_deposit_index"`
|
||||||
|
Validators []*Validator `json:"validators"`
|
||||||
|
Balances []string `json:"balances"`
|
||||||
|
RandaoMixes []string `json:"randao_mixes"`
|
||||||
|
Slashings []string `json:"slashings"`
|
||||||
|
PreviousEpochParticipation []string `json:"previous_epoch_participation"`
|
||||||
|
CurrentEpochParticipation []string `json:"current_epoch_participation"`
|
||||||
|
JustificationBits string `json:"justification_bits"`
|
||||||
|
PreviousJustifiedCheckpoint *Checkpoint `json:"previous_justified_checkpoint"`
|
||||||
|
CurrentJustifiedCheckpoint *Checkpoint `json:"current_justified_checkpoint"`
|
||||||
|
FinalizedCheckpoint *Checkpoint `json:"finalized_checkpoint"`
|
||||||
|
InactivityScores []string `json:"inactivity_scores"`
|
||||||
|
CurrentSyncCommittee *SyncCommittee `json:"current_sync_committee"`
|
||||||
|
NextSyncCommittee *SyncCommittee `json:"next_sync_committee"`
|
||||||
|
NextWithdrawalIndex string `json:"next_withdrawal_index"`
|
||||||
|
NextWithdrawalValidatorIndex string `json:"next_withdrawal_validator_index"`
|
||||||
|
HistoricalSummaries []*HistoricalSummary `json:"historical_summaries"`
|
||||||
|
DepositRequestsStartIndex string `json:"deposit_requests_start_index"`
|
||||||
|
DepositBalanceToConsume string `json:"deposit_balance_to_consume"`
|
||||||
|
ExitBalanceToConsume string `json:"exit_balance_to_consume"`
|
||||||
|
EarliestExitEpoch string `json:"earliest_exit_epoch"`
|
||||||
|
ConsolidationBalanceToConsume string `json:"consolidation_balance_to_consume"`
|
||||||
|
EarliestConsolidationEpoch string `json:"earliest_consolidation_epoch"`
|
||||||
|
PendingDeposits []*PendingDeposit `json:"pending_deposits"`
|
||||||
|
PendingPartialWithdrawals []*PendingPartialWithdrawal `json:"pending_partial_withdrawals"`
|
||||||
|
PendingConsolidations []*PendingConsolidation `json:"pending_consolidations"`
|
||||||
|
ProposerLookahead []string `json:"proposer_lookahead"`
|
||||||
|
LatestExecutionPayloadBid *ExecutionPayloadBid `json:"latest_execution_payload_bid"`
|
||||||
|
Builders []*Builder `json:"builders"`
|
||||||
|
NextWithdrawalBuilderIndex string `json:"next_withdrawal_builder_index"`
|
||||||
|
ExecutionPayloadAvailability string `json:"execution_payload_availability"`
|
||||||
|
BuilderPendingPayments []*BuilderPendingPayment `json:"builder_pending_payments"`
|
||||||
|
BuilderPendingWithdrawals []*BuilderPendingWithdrawal `json:"builder_pending_withdrawals"`
|
||||||
|
LatestBlockHash string `json:"latest_block_hash"`
|
||||||
|
PayloadExpectedWithdrawals []*Withdrawal `json:"payload_expected_withdrawals"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
160
beacon-chain/core/gloas/deposit_request.go
Normal file
160
beacon-chain/core/gloas/deposit_request.go
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if beaconState.Version() >= version.Gloas {
|
||||||
|
pubkey := bytesutil.ToBytes48(request.Pubkey)
|
||||||
|
_, isValidator := beaconState.ValidatorIndexByPubkey(pubkey)
|
||||||
|
_, isBuilder := beaconState.BuilderIndexByPubkey(pubkey)
|
||||||
|
isBuilderPrefix := IsBuilderWithdrawalCredential(request.WithdrawalCredentials)
|
||||||
|
if isBuilder || (isBuilderPrefix && !isValidator) {
|
||||||
|
if err := ApplyDepositForBuilder(
|
||||||
|
beaconState,
|
||||||
|
request.Pubkey,
|
||||||
|
request.WithdrawalCredentials,
|
||||||
|
request.Amount,
|
||||||
|
request.Signature,
|
||||||
|
); err != nil {
|
||||||
|
return errors.Wrap(err, "could not apply builder deposit")
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
330
beacon-chain/core/gloas/payload.go
Normal file
330
beacon-chain/core/gloas/payload.go
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
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/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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
var publicKey bls.PublicKey
|
||||||
|
if builderIdx == params.BeaconConfig().BuilderIndexSelfBuild {
|
||||||
|
header := st.LatestBlockHeader()
|
||||||
|
if header == nil {
|
||||||
|
return fmt.Errorf("latest block header is nil")
|
||||||
|
}
|
||||||
|
proposerPubkey := st.PubkeyAtIndex(header.ProposerIndex)
|
||||||
|
key, err := bls.PublicKeyFromBytes(proposerPubkey[:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid proposer public key: %w", err)
|
||||||
|
}
|
||||||
|
publicKey = key
|
||||||
|
} else {
|
||||||
|
builder, err := st.Builder(builderIdx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get builder: %w", err)
|
||||||
|
}
|
||||||
|
if builder == nil {
|
||||||
|
return fmt.Errorf("builder at index %d not found", builderIdx)
|
||||||
|
}
|
||||||
|
key, err := bls.PublicKeyFromBytes(builder.Pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid builder public key: %w", err)
|
||||||
|
}
|
||||||
|
publicKey = key
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -114,32 +114,17 @@ func payloadCommittee(ctx context.Context, st state.ReadOnlyBeaconState, slot pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
committeesPerSlot := helpers.SlotCommitteeCount(activeCount)
|
committeesPerSlot := helpers.SlotCommitteeCount(activeCount)
|
||||||
|
out := make([]primitives.ValidatorIndex, 0, activeCount/uint64(params.BeaconConfig().SlotsPerEpoch))
|
||||||
|
|
||||||
selected := make([]primitives.ValidatorIndex, 0, fieldparams.PTCSize)
|
for i := primitives.CommitteeIndex(0); i < primitives.CommitteeIndex(committeesPerSlot); i++ {
|
||||||
var i uint64
|
committee, err := helpers.BeaconCommitteeFromState(ctx, st, slot, i)
|
||||||
for uint64(len(selected)) < fieldparams.PTCSize {
|
if err != nil {
|
||||||
if ctx.Err() != nil {
|
return nil, errors.Wrapf(err, "failed to get beacon committee %d", i)
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
for committeeIndex := primitives.CommitteeIndex(0); committeeIndex < primitives.CommitteeIndex(committeesPerSlot); committeeIndex++ {
|
|
||||||
if uint64(len(selected)) >= fieldparams.PTCSize {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
committee, err := helpers.BeaconCommitteeFromState(ctx, st, slot, committeeIndex)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to get beacon committee %d", committeeIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
selected, i, err = selectByBalanceFill(ctx, st, committee, seed, selected, i)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to sample beacon committee %d", committeeIndex)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
out = append(out, committee...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return selected, nil
|
return selectByBalance(ctx, st, out, seed, fieldparams.PTCSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ptcSeed computes the seed for the payload timeliness committee.
|
// ptcSeed computes the seed for the payload timeliness committee.
|
||||||
@@ -163,39 +148,33 @@ func ptcSeed(st state.ReadOnlyBeaconState, epoch primitives.Epoch, slot primitiv
|
|||||||
// if compute_balance_weighted_acceptance(state, indices[next], seed, i):
|
// if compute_balance_weighted_acceptance(state, indices[next], seed, i):
|
||||||
// selected.append(indices[next])
|
// selected.append(indices[next])
|
||||||
// i += 1
|
// i += 1
|
||||||
func selectByBalanceFill(
|
func selectByBalance(ctx context.Context, st state.ReadOnlyBeaconState, candidates []primitives.ValidatorIndex, seed [32]byte, count uint64) ([]primitives.ValidatorIndex, error) {
|
||||||
ctx context.Context,
|
if len(candidates) == 0 {
|
||||||
st state.ReadOnlyBeaconState,
|
return nil, errors.New("no candidates for balance weighted selection")
|
||||||
candidates []primitives.ValidatorIndex,
|
}
|
||||||
seed [32]byte,
|
|
||||||
selected []primitives.ValidatorIndex,
|
|
||||||
i uint64,
|
|
||||||
) ([]primitives.ValidatorIndex, uint64, error) {
|
|
||||||
hashFunc := hash.CustomSHA256Hasher()
|
hashFunc := hash.CustomSHA256Hasher()
|
||||||
// Pre-allocate buffer for hash input: seed (32 bytes) + round counter (8 bytes).
|
// Pre-allocate buffer for hash input: seed (32 bytes) + round counter (8 bytes).
|
||||||
var buf [40]byte
|
var buf [40]byte
|
||||||
copy(buf[:], seed[:])
|
copy(buf[:], seed[:])
|
||||||
maxBalance := params.BeaconConfig().MaxEffectiveBalanceElectra
|
maxBalance := params.BeaconConfig().MaxEffectiveBalanceElectra
|
||||||
|
|
||||||
for _, idx := range candidates {
|
selected := make([]primitives.ValidatorIndex, 0, count)
|
||||||
|
total := uint64(len(candidates))
|
||||||
|
for i := uint64(0); uint64(len(selected)) < count; i++ {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return nil, i, ctx.Err()
|
return nil, ctx.Err()
|
||||||
}
|
}
|
||||||
|
idx := candidates[i%total]
|
||||||
ok, err := acceptByBalance(st, idx, buf[:], hashFunc, maxBalance, i)
|
ok, err := acceptByBalance(st, idx, buf[:], hashFunc, maxBalance, i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, i, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
selected = append(selected, idx)
|
selected = append(selected, idx)
|
||||||
}
|
}
|
||||||
if uint64(len(selected)) == fieldparams.PTCSize {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
}
|
||||||
|
return selected, nil
|
||||||
return selected, i, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// acceptByBalance determines if a validator is accepted based on its effective balance.
|
// acceptByBalance determines if a validator is accepted based on its effective balance.
|
||||||
|
|||||||
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ func (s *Server) getBeaconStateV2(ctx context.Context, w http.ResponseWriter, id
|
|||||||
httputil.HandleError(w, errMsgStateFromConsensus+": "+err.Error(), http.StatusInternalServerError)
|
httputil.HandleError(w, errMsgStateFromConsensus+": "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case version.Gloas:
|
||||||
|
respSt, err = structs.BeaconStateGloasFromConsensus(st)
|
||||||
|
if err != nil {
|
||||||
|
httputil.HandleError(w, errMsgStateFromConsensus+": "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
httputil.HandleError(w, "Unsupported state version", http.StatusInternalServerError)
|
httputil.HandleError(w, "Unsupported state version", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -232,6 +232,35 @@ func TestGetBeaconStateV2(t *testing.T) {
|
|||||||
assert.Equal(t, "123", st.Slot)
|
assert.Equal(t, "123", st.Slot)
|
||||||
assert.Equal(t, int(params.BeaconConfig().MinSeedLookahead+1)*int(params.BeaconConfig().SlotsPerEpoch), len(st.ProposerLookahead))
|
assert.Equal(t, int(params.BeaconConfig().MinSeedLookahead+1)*int(params.BeaconConfig().SlotsPerEpoch), len(st.ProposerLookahead))
|
||||||
})
|
})
|
||||||
|
t.Run("Gloas", func(t *testing.T) {
|
||||||
|
fakeState, err := util.NewBeaconStateGloas()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, fakeState.SetSlot(123))
|
||||||
|
chainService := &blockchainmock.ChainService{}
|
||||||
|
s := &Server{
|
||||||
|
Stater: &testutil.MockStater{
|
||||||
|
BeaconState: fakeState,
|
||||||
|
},
|
||||||
|
HeadFetcher: chainService,
|
||||||
|
OptimisticModeFetcher: chainService,
|
||||||
|
FinalizationFetcher: chainService,
|
||||||
|
}
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/debug/beacon/states/{state_id}", nil)
|
||||||
|
request.SetPathValue("state_id", "head")
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
writer.Body = &bytes.Buffer{}
|
||||||
|
|
||||||
|
s.GetBeaconStateV2(writer, request)
|
||||||
|
require.Equal(t, http.StatusOK, writer.Code)
|
||||||
|
resp := &structs.GetBeaconStateV2Response{}
|
||||||
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||||
|
assert.Equal(t, version.String(version.Gloas), resp.Version)
|
||||||
|
st := &structs.BeaconStateGloas{}
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, st))
|
||||||
|
assert.Equal(t, "123", st.Slot)
|
||||||
|
assert.Equal(t, int(params.BeaconConfig().MinSeedLookahead+1)*int(params.BeaconConfig().SlotsPerEpoch), len(st.ProposerLookahead))
|
||||||
|
})
|
||||||
t.Run("execution optimistic", func(t *testing.T) {
|
t.Run("execution optimistic", func(t *testing.T) {
|
||||||
parentRoot := [32]byte{'a'}
|
parentRoot := [32]byte{'a'}
|
||||||
blk := util.NewBeaconBlock()
|
blk := util.NewBeaconBlock()
|
||||||
@@ -427,6 +456,78 @@ func TestGetBeaconStateSSZV2(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
||||||
})
|
})
|
||||||
|
t.Run("Electra", func(t *testing.T) {
|
||||||
|
fakeState, err := util.NewBeaconStateElectra()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, fakeState.SetSlot(123))
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Stater: &testutil.MockStater{
|
||||||
|
BeaconState: fakeState,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/debug/beacon/states/{state_id}", nil)
|
||||||
|
request.SetPathValue("state_id", "head")
|
||||||
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
writer.Body = &bytes.Buffer{}
|
||||||
|
|
||||||
|
s.GetBeaconStateV2(writer, request)
|
||||||
|
require.Equal(t, http.StatusOK, writer.Code)
|
||||||
|
assert.Equal(t, version.String(version.Electra), writer.Header().Get(api.VersionHeader))
|
||||||
|
sszExpected, err := fakeState.MarshalSSZ()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
||||||
|
})
|
||||||
|
t.Run("Fulu", func(t *testing.T) {
|
||||||
|
fakeState, err := util.NewBeaconStateFulu()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, fakeState.SetSlot(123))
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Stater: &testutil.MockStater{
|
||||||
|
BeaconState: fakeState,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/debug/beacon/states/{state_id}", nil)
|
||||||
|
request.SetPathValue("state_id", "head")
|
||||||
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
writer.Body = &bytes.Buffer{}
|
||||||
|
|
||||||
|
s.GetBeaconStateV2(writer, request)
|
||||||
|
require.Equal(t, http.StatusOK, writer.Code)
|
||||||
|
assert.Equal(t, version.String(version.Fulu), writer.Header().Get(api.VersionHeader))
|
||||||
|
sszExpected, err := fakeState.MarshalSSZ()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
||||||
|
})
|
||||||
|
t.Run("Gloas", func(t *testing.T) {
|
||||||
|
fakeState, err := util.NewBeaconStateGloas()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, fakeState.SetSlot(123))
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Stater: &testutil.MockStater{
|
||||||
|
BeaconState: fakeState,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/debug/beacon/states/{state_id}", nil)
|
||||||
|
request.SetPathValue("state_id", "head")
|
||||||
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
writer.Body = &bytes.Buffer{}
|
||||||
|
|
||||||
|
s.GetBeaconStateV2(writer, request)
|
||||||
|
require.Equal(t, http.StatusOK, writer.Code)
|
||||||
|
assert.Equal(t, version.String(version.Gloas), writer.Header().Get(api.VersionHeader))
|
||||||
|
sszExpected, err := fakeState.MarshalSSZ()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetForkChoiceHeadsV2(t *testing.T) {
|
func TestGetForkChoiceHeadsV2(t *testing.T) {
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ go_test(
|
|||||||
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
|
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
|
||||||
"@com_github_ethereum_go_ethereum//p2p/enode:go_default_library",
|
"@com_github_ethereum_go_ethereum//p2p/enode:go_default_library",
|
||||||
"@org_golang_google_grpc//:go_default_library",
|
"@org_golang_google_grpc//:go_default_library",
|
||||||
"@org_golang_google_grpc//metadata:go_default_library",
|
|
||||||
"@org_golang_google_grpc//reflection:go_default_library",
|
"@org_golang_google_grpc//reflection:go_default_library",
|
||||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||||
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",
|
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",
|
||||||
|
|||||||
@@ -35,19 +35,18 @@ import (
|
|||||||
// providing RPC endpoints for verifying a beacon node's sync status, genesis and
|
// providing RPC endpoints for verifying a beacon node's sync status, genesis and
|
||||||
// version information, and services the node implements and runs.
|
// version information, and services the node implements and runs.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
LogsStreamer logs.Streamer
|
LogsStreamer logs.Streamer
|
||||||
StreamLogsBufferSize int
|
StreamLogsBufferSize int
|
||||||
SyncChecker sync.Checker
|
SyncChecker sync.Checker
|
||||||
Server *grpc.Server
|
Server *grpc.Server
|
||||||
BeaconDB db.ReadOnlyDatabase
|
BeaconDB db.ReadOnlyDatabase
|
||||||
PeersFetcher p2p.PeersProvider
|
PeersFetcher p2p.PeersProvider
|
||||||
PeerManager p2p.PeerManager
|
PeerManager p2p.PeerManager
|
||||||
GenesisTimeFetcher blockchain.TimeFetcher
|
GenesisTimeFetcher blockchain.TimeFetcher
|
||||||
GenesisFetcher blockchain.GenesisFetcher
|
GenesisFetcher blockchain.GenesisFetcher
|
||||||
POWChainInfoFetcher execution.ChainInfoFetcher
|
POWChainInfoFetcher execution.ChainInfoFetcher
|
||||||
BeaconMonitoringHost string
|
BeaconMonitoringHost string
|
||||||
BeaconMonitoringPort int
|
BeaconMonitoringPort int
|
||||||
OptimisticModeFetcher blockchain.OptimisticModeFetcher
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: The gRPC API will remain the default and fully supported through v8 (expected in 2026) but will be eventually removed in favor of REST API.
|
// Deprecated: The gRPC API will remain the default and fully supported through v8 (expected in 2026) but will be eventually removed in favor of REST API.
|
||||||
@@ -62,28 +61,21 @@ func (ns *Server) GetHealth(ctx context.Context, request *ethpb.HealthRequest) (
|
|||||||
ctx, cancel := context.WithTimeout(ctx, timeoutDuration)
|
ctx, cancel := context.WithTimeout(ctx, timeoutDuration)
|
||||||
defer cancel() // Important to avoid a context leak
|
defer cancel() // Important to avoid a context leak
|
||||||
|
|
||||||
// Check optimistic status - validators should not participate when optimistic
|
if ns.SyncChecker.Synced() {
|
||||||
isOptimistic, err := ns.OptimisticModeFetcher.IsOptimistic(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return &empty.Empty{}, status.Errorf(codes.Internal, "Could not check optimistic status: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ns.SyncChecker.Synced() && !isOptimistic {
|
|
||||||
return &empty.Empty{}, nil
|
return &empty.Empty{}, nil
|
||||||
}
|
}
|
||||||
if ns.SyncChecker.Syncing() || ns.SyncChecker.Initialized() {
|
if ns.SyncChecker.Syncing() || ns.SyncChecker.Initialized() {
|
||||||
// Set header for REST API clients (via gRPC-gateway)
|
if request.SyncingStatus != 0 {
|
||||||
if err := grpc.SetHeader(ctx, metadata.Pairs("x-http-code", strconv.FormatUint(http.StatusPartialContent, 10))); err != nil {
|
// override the 200 success with the provided request status
|
||||||
return &empty.Empty{}, status.Errorf(codes.Internal, "Could not set status code header: %v", err)
|
if err := grpc.SetHeader(ctx, metadata.Pairs("x-http-code", strconv.FormatUint(request.SyncingStatus, 10))); err != nil {
|
||||||
|
return &empty.Empty{}, status.Errorf(codes.Internal, "Could not set custom success code header: %v", err)
|
||||||
|
}
|
||||||
|
return &empty.Empty{}, nil
|
||||||
}
|
}
|
||||||
return &empty.Empty{}, status.Error(codes.Unavailable, "node is syncing")
|
|
||||||
}
|
|
||||||
if isOptimistic {
|
|
||||||
// Set header for REST API clients (via gRPC-gateway)
|
|
||||||
if err := grpc.SetHeader(ctx, metadata.Pairs("x-http-code", strconv.FormatUint(http.StatusPartialContent, 10))); err != nil {
|
if err := grpc.SetHeader(ctx, metadata.Pairs("x-http-code", strconv.FormatUint(http.StatusPartialContent, 10))); err != nil {
|
||||||
return &empty.Empty{}, status.Errorf(codes.Internal, "Could not set status code header: %v", err)
|
return &empty.Empty{}, status.Errorf(codes.Internal, "Could not set custom success code header: %v", err)
|
||||||
}
|
}
|
||||||
return &empty.Empty{}, status.Error(codes.Unavailable, "node is optimistic")
|
return &empty.Empty{}, nil
|
||||||
}
|
}
|
||||||
return &empty.Empty{}, status.Errorf(codes.Unavailable, "service unavailable")
|
return &empty.Empty{}, status.Errorf(codes.Unavailable, "service unavailable")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package node
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"maps"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -22,7 +21,6 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/metadata"
|
|
||||||
"google.golang.org/grpc/reflection"
|
"google.golang.org/grpc/reflection"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
@@ -189,71 +187,32 @@ func TestNodeServer_GetETH1ConnectionStatus(t *testing.T) {
|
|||||||
assert.Equal(t, errStr, res.CurrentConnectionError)
|
assert.Equal(t, errStr, res.CurrentConnectionError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mockServerTransportStream implements grpc.ServerTransportStream for testing
|
|
||||||
type mockServerTransportStream struct {
|
|
||||||
headers map[string][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockServerTransportStream) Method() string { return "" }
|
|
||||||
func (m *mockServerTransportStream) SetHeader(md metadata.MD) error {
|
|
||||||
maps.Copy(m.headers, md)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (m *mockServerTransportStream) SendHeader(metadata.MD) error { return nil }
|
|
||||||
func (m *mockServerTransportStream) SetTrailer(metadata.MD) error { return nil }
|
|
||||||
|
|
||||||
func TestNodeServer_GetHealth(t *testing.T) {
|
func TestNodeServer_GetHealth(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input *mockSync.Sync
|
input *mockSync.Sync
|
||||||
isOptimistic bool
|
customStatus uint64
|
||||||
wantedErr string
|
wantedErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "happy path - synced and not optimistic",
|
name: "happy path",
|
||||||
input: &mockSync.Sync{IsSyncing: false, IsSynced: true},
|
input: &mockSync.Sync{IsSyncing: false, IsSynced: true},
|
||||||
isOptimistic: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "returns error when not synced and not syncing",
|
name: "syncing",
|
||||||
input: &mockSync.Sync{IsSyncing: false, IsSynced: false},
|
input: &mockSync.Sync{IsSyncing: false},
|
||||||
isOptimistic: false,
|
wantedErr: "service unavailable",
|
||||||
wantedErr: "service unavailable",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "returns error when syncing",
|
|
||||||
input: &mockSync.Sync{IsSyncing: true, IsSynced: false},
|
|
||||||
isOptimistic: false,
|
|
||||||
wantedErr: "node is syncing",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "returns error when synced but optimistic",
|
|
||||||
input: &mockSync.Sync{IsSyncing: false, IsSynced: true},
|
|
||||||
isOptimistic: true,
|
|
||||||
wantedErr: "node is optimistic",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "returns error when syncing and optimistic",
|
|
||||||
input: &mockSync.Sync{IsSyncing: true, IsSynced: false},
|
|
||||||
isOptimistic: true,
|
|
||||||
wantedErr: "node is syncing",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
server := grpc.NewServer()
|
server := grpc.NewServer()
|
||||||
ns := &Server{
|
ns := &Server{
|
||||||
SyncChecker: tt.input,
|
SyncChecker: tt.input,
|
||||||
OptimisticModeFetcher: &mock.ChainService{Optimistic: tt.isOptimistic},
|
|
||||||
}
|
}
|
||||||
ethpb.RegisterNodeServer(server, ns)
|
ethpb.RegisterNodeServer(server, ns)
|
||||||
reflection.Register(server)
|
reflection.Register(server)
|
||||||
|
_, err := ns.GetHealth(t.Context(), ðpb.HealthRequest{SyncingStatus: tt.customStatus})
|
||||||
// Create context with mock transport stream so grpc.SetHeader works
|
|
||||||
stream := &mockServerTransportStream{headers: make(map[string][]string)}
|
|
||||||
ctx := grpc.NewContextWithServerTransportStream(t.Context(), stream)
|
|
||||||
|
|
||||||
_, err := ns.GetHealth(ctx, ðpb.HealthRequest{})
|
|
||||||
if tt.wantedErr == "" {
|
if tt.wantedErr == "" {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -259,19 +259,18 @@ func NewService(ctx context.Context, cfg *Config) *Service {
|
|||||||
}
|
}
|
||||||
s.validatorServer = validatorServer
|
s.validatorServer = validatorServer
|
||||||
nodeServer := &nodev1alpha1.Server{
|
nodeServer := &nodev1alpha1.Server{
|
||||||
LogsStreamer: logs.NewStreamServer(),
|
LogsStreamer: logs.NewStreamServer(),
|
||||||
StreamLogsBufferSize: 1000, // Enough to handle bursts of beacon node logs for gRPC streaming.
|
StreamLogsBufferSize: 1000, // Enough to handle bursts of beacon node logs for gRPC streaming.
|
||||||
BeaconDB: s.cfg.BeaconDB,
|
BeaconDB: s.cfg.BeaconDB,
|
||||||
Server: s.grpcServer,
|
Server: s.grpcServer,
|
||||||
SyncChecker: s.cfg.SyncService,
|
SyncChecker: s.cfg.SyncService,
|
||||||
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
|
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
|
||||||
PeersFetcher: s.cfg.PeersFetcher,
|
PeersFetcher: s.cfg.PeersFetcher,
|
||||||
PeerManager: s.cfg.PeerManager,
|
PeerManager: s.cfg.PeerManager,
|
||||||
GenesisFetcher: s.cfg.GenesisFetcher,
|
GenesisFetcher: s.cfg.GenesisFetcher,
|
||||||
POWChainInfoFetcher: s.cfg.ExecutionChainInfoFetcher,
|
POWChainInfoFetcher: s.cfg.ExecutionChainInfoFetcher,
|
||||||
BeaconMonitoringHost: s.cfg.BeaconMonitoringHost,
|
BeaconMonitoringHost: s.cfg.BeaconMonitoringHost,
|
||||||
BeaconMonitoringPort: s.cfg.BeaconMonitoringPort,
|
BeaconMonitoringPort: s.cfg.BeaconMonitoringPort,
|
||||||
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
|
|
||||||
}
|
}
|
||||||
beaconChainServer := &beaconv1alpha1.Server{
|
beaconChainServer := &beaconv1alpha1.Server{
|
||||||
Ctx: s.ctx,
|
Ctx: s.ctx,
|
||||||
|
|||||||
@@ -1,24 +1,56 @@
|
|||||||
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)
|
||||||
|
BuilderPendingWithdrawals() ([]*ethpb.BuilderPendingWithdrawal, error)
|
||||||
|
PayloadExpectedWithdrawals() ([]*enginev1.Withdrawal, error)
|
||||||
|
|
||||||
|
// Misc.
|
||||||
|
LatestBlockHash() ([32]byte, error)
|
||||||
|
ExecutionPayloadAvailability() ([]byte, error)
|
||||||
|
|
||||||
|
// Builders.
|
||||||
|
Builder(index primitives.BuilderIndex) (*ethpb.Builder, error)
|
||||||
|
Builders() ([]*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)
|
NextWithdrawalBuilderIndex() (primitives.BuilderIndex, 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,138 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuilderPendingWithdrawals returns a copy of the builder pending withdrawals.
|
||||||
|
func (b *BeaconState) BuilderPendingWithdrawals() ([]*ethpb.BuilderPendingWithdrawal, error) {
|
||||||
|
if b.version < version.Gloas {
|
||||||
|
return nil, errNotSupported("BuilderPendingWithdrawals", b.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.lock.RLock()
|
||||||
|
defer b.lock.RUnlock()
|
||||||
|
|
||||||
|
return b.builderPendingWithdrawalsVal(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builders returns a copy of the builders registry.
|
||||||
|
func (b *BeaconState) Builders() ([]*ethpb.Builder, error) {
|
||||||
|
if b.version < version.Gloas {
|
||||||
|
return nil, errNotSupported("Builders", b.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.lock.RLock()
|
||||||
|
defer b.lock.RUnlock()
|
||||||
|
|
||||||
|
return b.buildersVal(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextWithdrawalBuilderIndex returns the next withdrawal builder index.
|
||||||
|
func (b *BeaconState) NextWithdrawalBuilderIndex() (primitives.BuilderIndex, error) {
|
||||||
|
if b.version < version.Gloas {
|
||||||
|
return 0, errNotSupported("NextWithdrawalBuilderIndex", b.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.lock.RLock()
|
||||||
|
defer b.lock.RUnlock()
|
||||||
|
|
||||||
|
return b.nextWithdrawalBuilderIndex, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecutionPayloadAvailability returns a copy of the execution payload availability.
|
||||||
|
func (b *BeaconState) ExecutionPayloadAvailability() ([]byte, error) {
|
||||||
|
if b.version < version.Gloas {
|
||||||
|
return nil, errNotSupported("ExecutionPayloadAvailability", b.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.lock.RLock()
|
||||||
|
defer b.lock.RUnlock()
|
||||||
|
|
||||||
|
return b.executionPayloadAvailabilityVal(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PayloadExpectedWithdrawals returns a copy of the payload expected withdrawals.
|
||||||
|
func (b *BeaconState) PayloadExpectedWithdrawals() ([]*enginev1.Withdrawal, error) {
|
||||||
|
if b.version < version.Gloas {
|
||||||
|
return nil, errNotSupported("PayloadExpectedWithdrawals", b.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.lock.RLock()
|
||||||
|
defer b.lock.RUnlock()
|
||||||
|
|
||||||
|
return b.payloadExpectedWithdrawalsVal(), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,279 @@ func TestBuilderPendingPayments_UnsupportedVersion(t *testing.T) {
|
|||||||
_, err = st.BuilderPendingPayments()
|
_, err = st.BuilderPendingPayments()
|
||||||
require.ErrorContains(t, "BuilderPendingPayments", err)
|
require.ErrorContains(t, "BuilderPendingPayments", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilderPendingWithdrawals(t *testing.T) {
|
||||||
|
t.Run("returns error before gloas", func(t *testing.T) {
|
||||||
|
stIface, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
st := stIface.(*state_native.BeaconState)
|
||||||
|
|
||||||
|
_, err = st.BuilderPendingWithdrawals()
|
||||||
|
require.ErrorContains(t, "BuilderPendingWithdrawals", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns copy", func(t *testing.T) {
|
||||||
|
original := []*ethpb.BuilderPendingWithdrawal{
|
||||||
|
{Amount: 10, BuilderIndex: 1},
|
||||||
|
}
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
BuilderPendingWithdrawals: original,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got1, err := st.BuilderPendingWithdrawals()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.DeepEqual(t, original, got1)
|
||||||
|
|
||||||
|
got1[0].Amount = 99
|
||||||
|
got2, err := st.BuilderPendingWithdrawals()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.DeepEqual(t, original, got2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildersGetter(t *testing.T) {
|
||||||
|
t.Run("returns error before gloas", func(t *testing.T) {
|
||||||
|
stIface, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
st := stIface.(*state_native.BeaconState)
|
||||||
|
|
||||||
|
_, err = st.Builders()
|
||||||
|
require.ErrorContains(t, "Builders", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns copy", func(t *testing.T) {
|
||||||
|
pubkey := bytes.Repeat([]byte{0xAB}, fieldparams.BLSPubkeyLength)
|
||||||
|
buildr := ðpb.Builder{
|
||||||
|
Pubkey: pubkey,
|
||||||
|
Balance: 42,
|
||||||
|
DepositEpoch: 3,
|
||||||
|
WithdrawableEpoch: 4,
|
||||||
|
}
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
Builders: []*ethpb.Builder{buildr},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got1, err := st.Builders()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.DeepEqual(t, buildr, got1[0])
|
||||||
|
|
||||||
|
got1[0].Pubkey[0] = 0xFF
|
||||||
|
got2, err := st.Builders()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.DeepEqual(t, buildr, got2[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextWithdrawalBuilderIndex(t *testing.T) {
|
||||||
|
t.Run("returns error before gloas", func(t *testing.T) {
|
||||||
|
stIface, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
st := stIface.(*state_native.BeaconState)
|
||||||
|
|
||||||
|
_, err = st.NextWithdrawalBuilderIndex()
|
||||||
|
require.ErrorContains(t, "NextWithdrawalBuilderIndex", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns configured value", func(t *testing.T) {
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
NextWithdrawalBuilderIndex: 2,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got, err := st.NextWithdrawalBuilderIndex()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, primitives.BuilderIndex(2), got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecutionPayloadAvailability(t *testing.T) {
|
||||||
|
t.Run("returns error before gloas", func(t *testing.T) {
|
||||||
|
stIface, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
st := stIface.(*state_native.BeaconState)
|
||||||
|
|
||||||
|
_, err = st.ExecutionPayloadAvailability()
|
||||||
|
require.ErrorContains(t, "ExecutionPayloadAvailability", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns copy", func(t *testing.T) {
|
||||||
|
availability := []byte{0x01, 0x00, 0x01}
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
ExecutionPayloadAvailability: availability,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got1, err := st.ExecutionPayloadAvailability()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.DeepEqual(t, availability, got1)
|
||||||
|
|
||||||
|
got1[0] = 0xFF
|
||||||
|
got2, err := st.ExecutionPayloadAvailability()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.DeepEqual(t, availability, got2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPayloadExpectedWithdrawals(t *testing.T) {
|
||||||
|
t.Run("returns error before gloas", func(t *testing.T) {
|
||||||
|
stIface, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
st := stIface.(*state_native.BeaconState)
|
||||||
|
|
||||||
|
_, err = st.PayloadExpectedWithdrawals()
|
||||||
|
require.ErrorContains(t, "PayloadExpectedWithdrawals", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns copy", func(t *testing.T) {
|
||||||
|
original := enginev1.Withdrawal{
|
||||||
|
Index: 1,
|
||||||
|
ValidatorIndex: 2,
|
||||||
|
Address: bytes.Repeat([]byte{0x01}, 20),
|
||||||
|
Amount: 10,
|
||||||
|
}
|
||||||
|
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||||
|
PayloadExpectedWithdrawals: []*enginev1.Withdrawal{&original},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got1, err := st.PayloadExpectedWithdrawals()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.DeepEqual(t, &original, got1[0])
|
||||||
|
|
||||||
|
got1[0].Amount = 99
|
||||||
|
got2, err := st.PayloadExpectedWithdrawals()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.DeepEqual(t, &original, got2[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix Bazel build failure on macOS x86_64 (darwin_amd64) (adds missing assembly stub to hashtree patch).
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
### Added
|
|
||||||
|
|
||||||
- Added new proofCollector type to ssz-query
|
|
||||||
|
|
||||||
### Ignored
|
|
||||||
- Added testing covering the production of Merkle proof from Phase0 beacon state and benchmarked against real Hoodi beacon state (Fulu version)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
### Changed
|
|
||||||
|
|
||||||
- gRPC health endpoint will now return an error on syncing or optimistic status showing that it's unavailable.
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
### Added
|
||||||
|
- Add process execution payload for gloas
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
### Changed
|
|
||||||
- Sample PTC per committee to reduce allocations.
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -163,18 +163,3 @@ func Uint256ToSSZBytes(num string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return PadTo(ReverseByteOrder(uint256.Bytes()), 32), nil
|
return PadTo(ReverseByteOrder(uint256.Bytes()), 32), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutLittleEndian writes an unsigned integer value in little-endian format.
|
|
||||||
// Supports sizes 1, 2, 4, or 8 bytes for uint8/16/32/64 respectively.
|
|
||||||
func PutLittleEndian(dst []byte, val uint64, size int) {
|
|
||||||
switch size {
|
|
||||||
case 1:
|
|
||||||
dst[0] = byte(val)
|
|
||||||
case 2:
|
|
||||||
binary.LittleEndian.PutUint16(dst, uint16(val))
|
|
||||||
case 4:
|
|
||||||
binary.LittleEndian.PutUint32(dst, uint32(val))
|
|
||||||
case 8:
|
|
||||||
binary.LittleEndian.PutUint64(dst, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ go_library(
|
|||||||
"container.go",
|
"container.go",
|
||||||
"generalized_index.go",
|
"generalized_index.go",
|
||||||
"list.go",
|
"list.go",
|
||||||
"merkle_proof.go",
|
|
||||||
"path.go",
|
"path.go",
|
||||||
"proof_collector.go",
|
|
||||||
"query.go",
|
"query.go",
|
||||||
"ssz_info.go",
|
"ssz_info.go",
|
||||||
"ssz_object.go",
|
"ssz_object.go",
|
||||||
@@ -22,12 +20,7 @@ go_library(
|
|||||||
importpath = "github.com/OffchainLabs/prysm/v7/encoding/ssz/query",
|
importpath = "github.com/OffchainLabs/prysm/v7/encoding/ssz/query",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
"//container/trie:go_default_library",
|
|
||||||
"//crypto/hash/htr:go_default_library",
|
|
||||||
"//encoding/bytesutil:go_default_library",
|
|
||||||
"//encoding/ssz:go_default_library",
|
"//encoding/ssz:go_default_library",
|
||||||
"//math:go_default_library",
|
|
||||||
"@com_github_prysmaticlabs_fastssz//:go_default_library",
|
|
||||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -36,24 +29,15 @@ go_test(
|
|||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
"generalized_index_test.go",
|
"generalized_index_test.go",
|
||||||
"merkle_proof_test.go",
|
|
||||||
"path_test.go",
|
"path_test.go",
|
||||||
"proof_collector_test.go",
|
|
||||||
"query_test.go",
|
"query_test.go",
|
||||||
"tag_parser_test.go",
|
"tag_parser_test.go",
|
||||||
],
|
],
|
||||||
embed = [":go_default_library"],
|
|
||||||
deps = [
|
deps = [
|
||||||
"//beacon-chain/state/stateutil:go_default_library",
|
":go_default_library",
|
||||||
"//consensus-types/blocks:go_default_library",
|
|
||||||
"//consensus-types/primitives:go_default_library",
|
|
||||||
"//encoding/ssz:go_default_library",
|
|
||||||
"//encoding/ssz/query/testutil:go_default_library",
|
"//encoding/ssz/query/testutil:go_default_library",
|
||||||
"//proto/prysm/v1alpha1:go_default_library",
|
|
||||||
"//proto/ssz_query/testing:go_default_library",
|
"//proto/ssz_query/testing:go_default_library",
|
||||||
"//testing/require:go_default_library",
|
"//testing/require:go_default_library",
|
||||||
"//testing/util:go_default_library",
|
|
||||||
"@com_github_prysmaticlabs_fastssz//:go_default_library",
|
|
||||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package query
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
fastssz "github.com/prysmaticlabs/fastssz"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Prove is the entrypoint to generate an SSZ Merkle proof for the given generalized index.
|
|
||||||
// Parameters:
|
|
||||||
// - gindex: the generalized index of the node to prove inclusion for.
|
|
||||||
// Returns:
|
|
||||||
// - fastssz.Proof: the Merkle proof containing the leaf, index, and sibling hashes.
|
|
||||||
// - error: any error encountered during proof generation.
|
|
||||||
func (info *SszInfo) Prove(gindex uint64) (*fastssz.Proof, error) {
|
|
||||||
if info == nil {
|
|
||||||
return nil, fmt.Errorf("nil SszInfo")
|
|
||||||
}
|
|
||||||
|
|
||||||
collector := newProofCollector()
|
|
||||||
collector.addTarget(gindex)
|
|
||||||
|
|
||||||
// info.source is guaranteed to be valid and dereferenced by AnalyzeObject
|
|
||||||
v := reflect.ValueOf(info.source).Elem()
|
|
||||||
|
|
||||||
// Start the merkleization and proof collection process.
|
|
||||||
// In SSZ generalized indices, the root is always at index 1.
|
|
||||||
if _, err := collector.merkleize(info, v, 1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return collector.toProof()
|
|
||||||
}
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
package query_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/OffchainLabs/go-bitfield"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/encoding/ssz/query"
|
|
||||||
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
|
||||||
ssz "github.com/prysmaticlabs/fastssz"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestProve_FixedTestContainer(t *testing.T) {
|
|
||||||
obj := createFixedTestContainer()
|
|
||||||
|
|
||||||
tests := []string{
|
|
||||||
".field_uint32",
|
|
||||||
".nested.value2",
|
|
||||||
".vector_field[3]",
|
|
||||||
".bitvector64_field",
|
|
||||||
".trailing_field",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc, func(t *testing.T) {
|
|
||||||
proveAndVerify(t, obj, tc)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProve_VariableTestContainer(t *testing.T) {
|
|
||||||
obj := createVariableTestContainer()
|
|
||||||
|
|
||||||
tests := []string{
|
|
||||||
".leading_field",
|
|
||||||
".field_list_uint64[2]",
|
|
||||||
"len(field_list_uint64)",
|
|
||||||
".nested.nested_list_field[1]",
|
|
||||||
".variable_container_list[0].inner_1.field_list_uint64[1]",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc, func(t *testing.T) {
|
|
||||||
proveAndVerify(t, obj, tc)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProve_BeaconBlock(t *testing.T) {
|
|
||||||
randaoReveal := make([]byte, 96)
|
|
||||||
for i := range randaoReveal {
|
|
||||||
randaoReveal[i] = 0x42
|
|
||||||
}
|
|
||||||
root32 := make([]byte, 32)
|
|
||||||
for i := range root32 {
|
|
||||||
root32[i] = 0x24
|
|
||||||
}
|
|
||||||
sig := make([]byte, 96)
|
|
||||||
for i := range sig {
|
|
||||||
sig[i] = 0x99
|
|
||||||
}
|
|
||||||
|
|
||||||
att := ð.Attestation{
|
|
||||||
AggregationBits: bitfield.Bitlist{0x01},
|
|
||||||
Data: ð.AttestationData{
|
|
||||||
Slot: 1,
|
|
||||||
CommitteeIndex: 1,
|
|
||||||
BeaconBlockRoot: root32,
|
|
||||||
Source: ð.Checkpoint{
|
|
||||||
Epoch: 1,
|
|
||||||
Root: root32,
|
|
||||||
},
|
|
||||||
Target: ð.Checkpoint{
|
|
||||||
Epoch: 1,
|
|
||||||
Root: root32,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Signature: sig,
|
|
||||||
}
|
|
||||||
|
|
||||||
b := util.NewBeaconBlock()
|
|
||||||
b.Block.Slot = 123
|
|
||||||
b.Block.Body.RandaoReveal = randaoReveal
|
|
||||||
b.Block.Body.Attestations = []*eth.Attestation{att}
|
|
||||||
|
|
||||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
protoBlock, err := sb.Block().Proto()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
obj, ok := protoBlock.(query.SSZObject)
|
|
||||||
require.Equal(t, true, ok, "block proto does not implement query.SSZObject")
|
|
||||||
|
|
||||||
tests := []string{
|
|
||||||
".slot",
|
|
||||||
".body.randao_reveal",
|
|
||||||
".body.attestations[0].data.slot",
|
|
||||||
"len(body.attestations)",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc, func(t *testing.T) {
|
|
||||||
proveAndVerify(t, obj, tc)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProve_BeaconState(t *testing.T) {
|
|
||||||
st, _ := util.DeterministicGenesisState(t, 16)
|
|
||||||
require.NoError(t, st.SetSlot(primitives.Slot(42)))
|
|
||||||
|
|
||||||
sszObj, ok := st.ToProtoUnsafe().(query.SSZObject)
|
|
||||||
require.Equal(t, true, ok, "state proto does not implement query.SSZObject")
|
|
||||||
|
|
||||||
tests := []string{
|
|
||||||
".slot",
|
|
||||||
".latest_block_header",
|
|
||||||
".validators[0].effective_balance",
|
|
||||||
"len(validators)",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc, func(t *testing.T) {
|
|
||||||
proveAndVerify(t, sszObj, tc)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// proveAndVerify helper to analyze an object, generate a merkle proof for the given path,
|
|
||||||
// and verify the proof against the object's root.
|
|
||||||
func proveAndVerify(t *testing.T, obj query.SSZObject, pathStr string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
info, err := query.AnalyzeObject(obj)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
path, err := query.ParsePath(pathStr)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gi, err := query.GetGeneralizedIndexFromPath(info, path)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
proof, err := info.Prove(gi)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, int(gi), proof.Index)
|
|
||||||
|
|
||||||
root, err := obj.HashTreeRoot()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ok, err := ssz.VerifyProof(root[:], proof)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, true, ok, "merkle proof verification failed")
|
|
||||||
|
|
||||||
require.Equal(t, 32, len(proof.Leaf))
|
|
||||||
for i, h := range proof.Hashes {
|
|
||||||
require.Equal(t, 32, len(h), "proof hash %d is not 32 bytes", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,672 +0,0 @@
|
|||||||
package query
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/bits"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"slices"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/OffchainLabs/go-bitfield"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/container/trie"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/crypto/hash/htr"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
|
||||||
ssz "github.com/OffchainLabs/prysm/v7/encoding/ssz"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/math"
|
|
||||||
fastssz "github.com/prysmaticlabs/fastssz"
|
|
||||||
)
|
|
||||||
|
|
||||||
// proofCollector collects sibling hashes and leaves needed for Merkle proofs.
|
|
||||||
//
|
|
||||||
// Multiproof-ready design:
|
|
||||||
// - requiredSiblings/requiredLeaves store which gindices we want to collect (registered before merkleization).
|
|
||||||
// - siblings/leaves store the actual collected hashes.
|
|
||||||
//
|
|
||||||
// Concurrency:
|
|
||||||
// - required* maps are read-only during merkleization.
|
|
||||||
// - siblings/leaves writes are protected by mutex.
|
|
||||||
type proofCollector struct {
|
|
||||||
sync.Mutex
|
|
||||||
|
|
||||||
// Required gindices (registered before merkleization)
|
|
||||||
requiredSiblings map[uint64]struct{}
|
|
||||||
requiredLeaves map[uint64]struct{}
|
|
||||||
|
|
||||||
// Collected hashes
|
|
||||||
siblings map[uint64][32]byte
|
|
||||||
leaves map[uint64][32]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newProofCollector() *proofCollector {
|
|
||||||
return &proofCollector{
|
|
||||||
requiredSiblings: make(map[uint64]struct{}),
|
|
||||||
requiredLeaves: make(map[uint64]struct{}),
|
|
||||||
siblings: make(map[uint64][32]byte),
|
|
||||||
leaves: make(map[uint64][32]byte),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *proofCollector) reset() {
|
|
||||||
pc.Lock()
|
|
||||||
defer pc.Unlock()
|
|
||||||
|
|
||||||
pc.requiredSiblings = make(map[uint64]struct{})
|
|
||||||
pc.requiredLeaves = make(map[uint64]struct{})
|
|
||||||
pc.siblings = make(map[uint64][32]byte)
|
|
||||||
pc.leaves = make(map[uint64][32]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addTarget register the target leaf and its required sibling nodes for proof construction.
|
|
||||||
// Registration should happen before merkleization begins.
|
|
||||||
func (pc *proofCollector) addTarget(gindex uint64) {
|
|
||||||
pc.Lock()
|
|
||||||
defer pc.Unlock()
|
|
||||||
|
|
||||||
pc.requiredLeaves[gindex] = struct{}{}
|
|
||||||
|
|
||||||
// Walk from the target leaf up to (but not including) the root (gindex=1).
|
|
||||||
// At each step, register the sibling node required to prove inclusion.
|
|
||||||
nodeGindex := gindex
|
|
||||||
for nodeGindex > 1 {
|
|
||||||
siblingGindex := nodeGindex ^ 1 // flip the last bit: left<->right sibling
|
|
||||||
pc.requiredSiblings[siblingGindex] = struct{}{}
|
|
||||||
|
|
||||||
// Move to parent
|
|
||||||
nodeGindex /= 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// toProof converts the collected siblings and leaves into a fastssz.Proof structure.
|
|
||||||
// Current behavior expects a single target leaf (single proof).
|
|
||||||
func (pc *proofCollector) toProof() (*fastssz.Proof, error) {
|
|
||||||
pc.Lock()
|
|
||||||
defer pc.Unlock()
|
|
||||||
|
|
||||||
proof := &fastssz.Proof{}
|
|
||||||
if len(pc.leaves) == 0 {
|
|
||||||
return nil, errors.New("no leaves collected: add target leaves before merkleization")
|
|
||||||
}
|
|
||||||
|
|
||||||
leafGindices := make([]uint64, 0, len(pc.leaves))
|
|
||||||
for g := range pc.leaves {
|
|
||||||
leafGindices = append(leafGindices, g)
|
|
||||||
}
|
|
||||||
slices.Sort(leafGindices)
|
|
||||||
|
|
||||||
// single proof resides in leafGindices[0]
|
|
||||||
targetGindex := leafGindices[0]
|
|
||||||
proofIndex, err := math.Int(targetGindex)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("gindex %d overflows int: %w", targetGindex, err)
|
|
||||||
}
|
|
||||||
proof.Index = proofIndex
|
|
||||||
|
|
||||||
// store the leaf
|
|
||||||
leaf := pc.leaves[targetGindex]
|
|
||||||
leafBuf := make([]byte, 32)
|
|
||||||
copy(leafBuf, leaf[:])
|
|
||||||
proof.Leaf = leafBuf
|
|
||||||
|
|
||||||
// Walk from target up to root, collecting siblings.
|
|
||||||
steps := bits.Len64(targetGindex) - 1
|
|
||||||
proof.Hashes = make([][]byte, 0, steps)
|
|
||||||
|
|
||||||
for targetGindex > 1 {
|
|
||||||
sib := targetGindex ^ 1
|
|
||||||
h, ok := pc.siblings[sib]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("missing sibling hash for gindex %d", sib)
|
|
||||||
}
|
|
||||||
proof.Hashes = append(proof.Hashes, h[:])
|
|
||||||
targetGindex /= 2
|
|
||||||
}
|
|
||||||
|
|
||||||
return proof, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// collectLeaf checks if the given gindex is a required leaf for the proof,
|
|
||||||
// and if so, stores the provided leaf hash in the collector.
|
|
||||||
func (pc *proofCollector) collectLeaf(gindex uint64, leaf [32]byte) {
|
|
||||||
if _, ok := pc.requiredLeaves[gindex]; !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pc.Lock()
|
|
||||||
pc.leaves[gindex] = leaf
|
|
||||||
pc.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// collectSibling stores the hash for a sibling node identified by gindex.
|
|
||||||
// It only stores the hash if gindex was pre-registered via addTarget (present in requiredSiblings).
|
|
||||||
// Writes to the collected siblings map are protected by the collector mutex.
|
|
||||||
func (pc *proofCollector) collectSibling(gindex uint64, hash [32]byte) {
|
|
||||||
if _, ok := pc.requiredSiblings[gindex]; !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pc.Lock()
|
|
||||||
pc.siblings[gindex] = hash
|
|
||||||
pc.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merkleizers and proof collection methods
|
|
||||||
|
|
||||||
// merkleize recursively traverses an SSZ info and computes the Merkle root of the subtree.
|
|
||||||
//
|
|
||||||
// Proof collection:
|
|
||||||
// - During traversal it calls collectLeaf/collectSibling with the SSZ generalized indices (gindices)
|
|
||||||
// of visited nodes.
|
|
||||||
// - The collector only stores hashes for gindices that were pre-registered via addTarget
|
|
||||||
// (requiredLeaves/requiredSiblings). This makes the traversal multiproof-ready: you can register
|
|
||||||
// multiple targets before calling merkleize.
|
|
||||||
//
|
|
||||||
// SSZ types handled: basic types, containers, lists, vectors, bitlists, and bitvectors.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - info: SSZ type metadata for the current value.
|
|
||||||
// - v: reflect.Value of the current value.
|
|
||||||
// - currentGindex: generalized index of the current subtree root.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - [32]byte: Merkle root of the current subtree.
|
|
||||||
// - error: any error encountered during traversal/merkleization.
|
|
||||||
func (pc *proofCollector) merkleize(info *SszInfo, v reflect.Value, currentGindex uint64) ([32]byte, error) {
|
|
||||||
if info.sszType.isBasic() {
|
|
||||||
return pc.merkleizeBasicType(info.sszType, v, currentGindex)
|
|
||||||
}
|
|
||||||
switch info.sszType {
|
|
||||||
case Container:
|
|
||||||
return pc.merkleizeContainer(info, v, currentGindex)
|
|
||||||
case List:
|
|
||||||
return pc.merkleizeList(info, v, currentGindex)
|
|
||||||
case Vector:
|
|
||||||
return pc.merkleizeVector(info, v, currentGindex)
|
|
||||||
case Bitlist:
|
|
||||||
return pc.merkleizeBitlist(info, v, currentGindex)
|
|
||||||
case Bitvector:
|
|
||||||
return pc.merkleizeBitvector(info, v, currentGindex)
|
|
||||||
default:
|
|
||||||
return [32]byte{}, fmt.Errorf("unsupported SSZ type: %v", info.sszType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// merkleizeBasicType serializes a basic SSZ value into a 32-byte leaf chunk (little-endian, zero-padded).
|
|
||||||
//
|
|
||||||
// Proof collection:
|
|
||||||
// - It calls collectLeaf(currentGindex, leaf) and stores the leaf if currentGindex was pre-registered via addTarget.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - t: the SSZType (basic).
|
|
||||||
// - v: the reflect.Value of the basic value.
|
|
||||||
// - currentGindex: the generalized index (gindex) of this leaf.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - [32]byte: the 32-byte SSZ leaf chunk.
|
|
||||||
// - error: if the SSZType is not a supported basic type.
|
|
||||||
func (pc *proofCollector) merkleizeBasicType(t SSZType, v reflect.Value, currentGindex uint64) ([32]byte, error) {
|
|
||||||
var leaf [32]byte
|
|
||||||
|
|
||||||
// Serialize the value into a 32-byte chunk (little-endian, zero-padded)
|
|
||||||
switch t {
|
|
||||||
case Uint8:
|
|
||||||
leaf[0] = uint8(v.Uint())
|
|
||||||
case Uint16:
|
|
||||||
binary.LittleEndian.PutUint16(leaf[:2], uint16(v.Uint()))
|
|
||||||
case Uint32:
|
|
||||||
binary.LittleEndian.PutUint32(leaf[:4], uint32(v.Uint()))
|
|
||||||
case Uint64:
|
|
||||||
binary.LittleEndian.PutUint64(leaf[:8], v.Uint())
|
|
||||||
case Boolean:
|
|
||||||
if v.Bool() {
|
|
||||||
leaf[0] = 1
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return [32]byte{}, fmt.Errorf("unexpected basic type: %v", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
pc.collectLeaf(currentGindex, leaf)
|
|
||||||
|
|
||||||
return leaf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// merkleizeContainer computes the Merkle root of an SSZ container by:
|
|
||||||
// 1. Merkleizing each field into a 32-byte subtree root
|
|
||||||
// 2. Merkleizing the field roots into the container root (padding to the next power-of-2)
|
|
||||||
//
|
|
||||||
// Generalized indices (gindices): depth = ssz.Depth(uint64(N)) and field i has gindex = (currentGindex << depth) + uint64(i).
|
|
||||||
// Proof collection: merkleize() computes each field root, merkleizeVectorAndCollect collects required siblings, and collectLeaf stores the container root if registered.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - info: SSZ type metadata for the container.
|
|
||||||
// - v: reflect.Value of the container value.
|
|
||||||
// - currentGindex: generalized index (gindex) of the container root.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - [32]byte: Merkle root of the container.
|
|
||||||
// - error: any error encountered while merkleizing fields.
|
|
||||||
func (pc *proofCollector) merkleizeContainer(info *SszInfo, v reflect.Value, currentGindex uint64) ([32]byte, error) {
|
|
||||||
// If the container root itself is the target, compute directly and return early.
|
|
||||||
// This avoids full subtree merkleization when we only need the root.
|
|
||||||
if _, ok := pc.requiredLeaves[currentGindex]; ok {
|
|
||||||
root, err := info.HashTreeRoot()
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
pc.collectLeaf(currentGindex, root)
|
|
||||||
return root, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ci, err := info.ContainerInfo()
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
v = dereferencePointer(v)
|
|
||||||
|
|
||||||
// Calculate depth: how many levels from container root to field leaves
|
|
||||||
numFields := len(ci.order)
|
|
||||||
depth := ssz.Depth(uint64(numFields))
|
|
||||||
|
|
||||||
// Step 1: Compute HTR for each subtree (field)
|
|
||||||
fieldRoots := make([][32]byte, numFields)
|
|
||||||
|
|
||||||
for i, name := range ci.order {
|
|
||||||
fieldInfo := ci.fields[name]
|
|
||||||
fieldVal := v.FieldByName(fieldInfo.goFieldName)
|
|
||||||
|
|
||||||
// Field i's gindex: shift currentGindex left by depth, then OR with field index
|
|
||||||
fieldGindex := currentGindex<<depth + uint64(i)
|
|
||||||
|
|
||||||
htr, err := pc.merkleize(fieldInfo.sszInfo, fieldVal, fieldGindex)
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, fmt.Errorf("field %s: %w", name, err)
|
|
||||||
}
|
|
||||||
fieldRoots[i] = htr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Merkleize the field hashes into the container root,
|
|
||||||
// collecting sibling hashes if target is within this subtree
|
|
||||||
root := pc.merkleizeVectorAndCollect(fieldRoots, currentGindex, uint64(depth))
|
|
||||||
|
|
||||||
return root, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// merkleizeVectorBody computes the Merkle root of the "data" subtree for vector-like SSZ types
|
|
||||||
// (vectors and the data-part of lists/bitlists).
|
|
||||||
//
|
|
||||||
// Generalized indices (gindices): depth = ssz.Depth(limit); leafBase = subtreeRootGindex << depth; element/chunk i gindex = leafBase + uint64(i).
|
|
||||||
// Proof collection: merkleize() is called for composite elements; merkleizeVectorAndCollect collects required siblings at this layer.
|
|
||||||
// Padding: merkleizeVectorAndCollect uses trie.ZeroHashes as needed.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - elemInfo: SSZ type metadata for the element.
|
|
||||||
// - v: reflect.Value of the vector/list data.
|
|
||||||
// - length: number of actual elements present.
|
|
||||||
// - limit: virtual leaf capacity used for padding/Depth (fixed length for vectors, limit for lists).
|
|
||||||
// - subtreeRootGindex: gindex of the data subtree root.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - [32]byte: Merkle root of the data subtree.
|
|
||||||
// - error: any error encountered while merkleizing composite elements.
|
|
||||||
func (pc *proofCollector) merkleizeVectorBody(elemInfo *SszInfo, v reflect.Value, length int, limit uint64, subtreeRootGindex uint64) ([32]byte, error) {
|
|
||||||
depth := uint64(ssz.Depth(limit))
|
|
||||||
|
|
||||||
var chunks [][32]byte
|
|
||||||
if elemInfo.sszType.isBasic() {
|
|
||||||
// Serialize basic elements and pack into 32-byte chunks using ssz.PackByChunk.
|
|
||||||
elemSize, err := math.Int(itemLength(elemInfo))
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, fmt.Errorf("element size %d overflows int: %w", itemLength(elemInfo), err)
|
|
||||||
}
|
|
||||||
serialized := make([][]byte, length)
|
|
||||||
// Single contiguous allocation for all element data
|
|
||||||
allData := make([]byte, length*elemSize)
|
|
||||||
for i := range length {
|
|
||||||
buf := allData[i*elemSize : (i+1)*elemSize]
|
|
||||||
elem := v.Index(i)
|
|
||||||
if elemInfo.sszType == Boolean && elem.Bool() {
|
|
||||||
buf[0] = 1
|
|
||||||
} else {
|
|
||||||
bytesutil.PutLittleEndian(buf, elem.Uint(), elemSize)
|
|
||||||
}
|
|
||||||
serialized[i] = buf
|
|
||||||
}
|
|
||||||
chunks, err = ssz.PackByChunk(serialized)
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Composite elements: compute each element root (no padding here; merkleizeVectorAndCollect pads).
|
|
||||||
chunks = make([][32]byte, length)
|
|
||||||
|
|
||||||
// Fall back to per-element merkleization with proper gindices for proof collection.
|
|
||||||
// Parallel execution
|
|
||||||
workerCount := min(runtime.GOMAXPROCS(0), length)
|
|
||||||
|
|
||||||
jobs := make(chan int, workerCount*16)
|
|
||||||
errCh := make(chan error, 1) // only need the first error
|
|
||||||
stopCh := make(chan struct{})
|
|
||||||
var stopOnce sync.Once
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
worker := func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for idx := range jobs {
|
|
||||||
select {
|
|
||||||
case <-stopCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
elemGindex := subtreeRootGindex<<depth + uint64(idx)
|
|
||||||
htr, err := pc.merkleize(elemInfo, v.Index(idx), elemGindex)
|
|
||||||
if err != nil {
|
|
||||||
stopOnce.Do(func() { close(stopCh) })
|
|
||||||
select {
|
|
||||||
case errCh <- fmt.Errorf("index %d: %w", idx, err):
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
chunks[idx] = htr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(workerCount)
|
|
||||||
for range workerCount {
|
|
||||||
go worker()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enqueue jobs; stop early if any worker reports an error.
|
|
||||||
enqueue:
|
|
||||||
for i := range length {
|
|
||||||
select {
|
|
||||||
case <-stopCh:
|
|
||||||
break enqueue
|
|
||||||
case jobs <- i:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(jobs)
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-errCh:
|
|
||||||
return [32]byte{}, err
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
root := pc.merkleizeVectorAndCollect(chunks, subtreeRootGindex, depth)
|
|
||||||
return root, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// merkleizeVector computes the Merkle root of an SSZ vector (fixed-length).
|
|
||||||
//
|
|
||||||
// Generalized indices (gindices): currentGindex is the gindex of the vector root; element/chunk gindices are derived
|
|
||||||
// inside merkleizeVectorBody using leafBase = currentGindex << ssz.Depth(leaves).
|
|
||||||
//
|
|
||||||
// Proof collection: merkleizeVectorBody performs element/chunk merkleization and collects required siblings at the
|
|
||||||
// vector layer; collectLeaf stores the vector root if currentGindex was registered via addTarget.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - info: SSZ type metadata for the vector.
|
|
||||||
// - v: reflect.Value of the vector value.
|
|
||||||
// - currentGindex: generalized index (gindex) of the vector root.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - [32]byte: Merkle root of the vector.
|
|
||||||
// - error: any error encountered while merkleizing composite elements.
|
|
||||||
func (pc *proofCollector) merkleizeVector(info *SszInfo, v reflect.Value, currentGindex uint64) ([32]byte, error) {
|
|
||||||
vi, err := info.VectorInfo()
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
length, err := math.Int(vi.Length())
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, fmt.Errorf("vector length %d overflows int: %w", vi.Length(), err)
|
|
||||||
}
|
|
||||||
elemInfo := vi.element
|
|
||||||
|
|
||||||
// Determine the virtual leaf capacity for the vector.
|
|
||||||
leaves, err := getChunkCount(info)
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
root, err := pc.merkleizeVectorBody(elemInfo, v, length, leaves, currentGindex)
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the vector root itself is the target
|
|
||||||
pc.collectLeaf(currentGindex, root)
|
|
||||||
|
|
||||||
return root, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// merkleizeList computes the Merkle root of an SSZ list by merkleizing its data subtree and mixing in the length.
|
|
||||||
//
|
|
||||||
// Generalized indices (gindices): dataRoot is the left child of the list root (dataRootGindex = currentGindex*2); the length mixin is the right child (currentGindex*2+1).
|
|
||||||
// Proof collection: merkleizeVectorBody computes the data root (collecting required siblings in the data subtree), and mixinLengthAndCollect collects required siblings at the length-mixin level; collectLeaf stores the list root if registered.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - info: SSZ type metadata for the list.
|
|
||||||
// - v: reflect.Value of the list value.
|
|
||||||
// - currentGindex: generalized index (gindex) of the list root.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - [32]byte: Merkle root of the list.
|
|
||||||
// - error: any error encountered while merkleizing the data subtree.
|
|
||||||
func (pc *proofCollector) merkleizeList(info *SszInfo, v reflect.Value, currentGindex uint64) ([32]byte, error) {
|
|
||||||
li, err := info.ListInfo()
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
length := v.Len()
|
|
||||||
elemInfo := li.element
|
|
||||||
|
|
||||||
chunks := make([][32]byte, 2)
|
|
||||||
// Compute the length hash (little-endian uint256)
|
|
||||||
binary.LittleEndian.PutUint64(chunks[1][:8], uint64(length))
|
|
||||||
|
|
||||||
// Data subtree root is the left child of the list root.
|
|
||||||
dataRootGindex := currentGindex * 2
|
|
||||||
|
|
||||||
// Compute virtual leaf capacity for the data subtree.
|
|
||||||
leaves, err := getChunkCount(info)
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
chunks[0], err = pc.merkleizeVectorBody(elemInfo, v, length, leaves, dataRootGindex)
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the length mixin level (and proof bookkeeping at this level).
|
|
||||||
// Compute the final list root: hash(dataRoot || lengthHash)
|
|
||||||
root := pc.mixinLengthAndCollect(currentGindex, chunks)
|
|
||||||
|
|
||||||
// If the list root itself is the target
|
|
||||||
pc.collectLeaf(currentGindex, root)
|
|
||||||
|
|
||||||
return root, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// merkleizeBitvectorBody computes the Merkle root of a bitvector-like byte sequence by packing it into 32-byte chunks
|
|
||||||
// and merkleizing those chunks as a fixed-capacity vector (padding with trie.ZeroHashes as needed).
|
|
||||||
//
|
|
||||||
// Generalized indices (gindices): depth = ssz.Depth(chunkLimit); leafBase = subtreeRootGindex << depth; chunk i uses gindex = leafBase + uint64(i).
|
|
||||||
// Proof collection: merkleizeVectorAndCollect collects required sibling hashes at the chunk-merkleization layer.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - data: raw byte sequence representing the bitvector payload.
|
|
||||||
// - chunkLimit: fixed/limit number of 32-byte chunks (used for padding/Depth).
|
|
||||||
// - subtreeRootGindex: gindex of the bitvector data subtree root.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - [32]byte: Merkle root of the bitvector data subtree.
|
|
||||||
// - error: any error encountered while packing data into chunks.
|
|
||||||
func (pc *proofCollector) merkleizeBitvectorBody(data []byte, chunkLimit uint64, subtreeRootGindex uint64) ([32]byte, error) {
|
|
||||||
depth := ssz.Depth(chunkLimit)
|
|
||||||
chunks, err := ssz.PackByChunk([][]byte{data})
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
root := pc.merkleizeVectorAndCollect(chunks, subtreeRootGindex, uint64(depth))
|
|
||||||
return root, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// merkleizeBitvector computes the Merkle root of a fixed-length SSZ bitvector and collects proof nodes for targets.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - info: SSZ type metadata for the bitvector.
|
|
||||||
// - v: reflect.Value of the bitvector value.
|
|
||||||
// - currentGindex: generalized index (gindex) of the bitvector root.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - [32]byte: Merkle root of the bitvector.
|
|
||||||
// - error: any error encountered during packing or merkleization.
|
|
||||||
func (pc *proofCollector) merkleizeBitvector(info *SszInfo, v reflect.Value, currentGindex uint64) ([32]byte, error) {
|
|
||||||
bitvectorBytes := v.Bytes()
|
|
||||||
if len(bitvectorBytes) == 0 {
|
|
||||||
return [32]byte{}, fmt.Errorf("bitvector field is uninitialized (nil or empty slice)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute virtual leaf capacity for the bitvector.
|
|
||||||
numChunks, err := getChunkCount(info)
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
root, err := pc.merkleizeBitvectorBody(bitvectorBytes, numChunks, currentGindex)
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pc.collectLeaf(currentGindex, root)
|
|
||||||
|
|
||||||
return root, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// merkleizeBitlist computes the Merkle root of an SSZ bitlist by merkleizing its data chunks and mixing in the bit length.
|
|
||||||
//
|
|
||||||
// Generalized indices (gindices): dataRoot is the left child (dataRootGindex = currentGindex*2) and the length mixin is the right child (currentGindex*2+1).
|
|
||||||
// Proof collection: merkleizeBitvectorBody computes the data root (collecting required siblings under dataRootGindex), and mixinLengthAndCollect collects required siblings at the length-mixin level; collectLeaf stores the bitlist root if registered.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - info: SSZ type metadata for the bitlist.
|
|
||||||
// - v: reflect.Value of the bitlist value.
|
|
||||||
// - currentGindex: generalized index (gindex) of the bitlist root.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - [32]byte: Merkle root of the bitlist.
|
|
||||||
// - error: any error encountered while merkleizing the data subtree.
|
|
||||||
func (pc *proofCollector) merkleizeBitlist(info *SszInfo, v reflect.Value, currentGindex uint64) ([32]byte, error) {
|
|
||||||
bi, err := info.BitlistInfo()
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bitlistBytes := v.Bytes()
|
|
||||||
|
|
||||||
// Use go-bitfield to get bytes with termination bit cleared
|
|
||||||
bl := bitfield.Bitlist(bitlistBytes)
|
|
||||||
data := bl.BytesNoTrim()
|
|
||||||
|
|
||||||
// Get the bit length from bitlistInfo
|
|
||||||
bitLength := bi.Length()
|
|
||||||
|
|
||||||
// Get the chunk limit from getChunkCount
|
|
||||||
limitChunks, err := getChunkCount(info)
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
chunks := make([][32]byte, 2)
|
|
||||||
// Compute the length hash (little-endian uint256)
|
|
||||||
binary.LittleEndian.PutUint64(chunks[1][:8], uint64(bitLength))
|
|
||||||
|
|
||||||
dataRootGindex := currentGindex * 2
|
|
||||||
chunks[0], err = pc.merkleizeBitvectorBody(data, limitChunks, dataRootGindex)
|
|
||||||
if err != nil {
|
|
||||||
return [32]byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the length mixin level (and proof bookkeeping at this level).
|
|
||||||
root := pc.mixinLengthAndCollect(currentGindex, chunks)
|
|
||||||
|
|
||||||
pc.collectLeaf(currentGindex, root)
|
|
||||||
|
|
||||||
return root, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// merkleizeVectorAndCollect merkleizes a slice of 32-byte leaf nodes into a subtree root, padding to a virtual size of 2^depth.
|
|
||||||
//
|
|
||||||
// Generalized indices (gindices): at layer i (0-based), nodes have gindices levelBase = subtreeGeneralizedIndex << (depth-i) and node gindex = levelBase + idx.
|
|
||||||
// Proof collection: for each layer it calls collectSibling(nodeGindex, nodeHash) and stores only those gindices registered via addTarget.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - elements: leaf-level hashes (may be shorter than 2^depth; padding is applied with trie.ZeroHashes).
|
|
||||||
// - subtreeGeneralizedIndex: gindex of the subtree root.
|
|
||||||
// - depth: number of merkleization layers from subtree root to leaves.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - [32]byte: Merkle root of the subtree.
|
|
||||||
func (pc *proofCollector) merkleizeVectorAndCollect(elements [][32]byte, subtreeGeneralizedIndex uint64, depth uint64) [32]byte {
|
|
||||||
// Return zerohash at depth
|
|
||||||
if len(elements) == 0 {
|
|
||||||
return trie.ZeroHashes[depth]
|
|
||||||
}
|
|
||||||
for i := range depth {
|
|
||||||
layerLen := len(elements)
|
|
||||||
oddNodeLength := layerLen%2 == 1
|
|
||||||
if oddNodeLength {
|
|
||||||
zerohash := trie.ZeroHashes[i]
|
|
||||||
elements = append(elements, zerohash)
|
|
||||||
}
|
|
||||||
|
|
||||||
levelBaseGindex := subtreeGeneralizedIndex << (depth - i)
|
|
||||||
for idx := range elements {
|
|
||||||
gindex := levelBaseGindex + uint64(idx)
|
|
||||||
pc.collectSibling(gindex, elements[idx])
|
|
||||||
pc.collectLeaf(gindex, elements[idx])
|
|
||||||
}
|
|
||||||
|
|
||||||
elements = htr.VectorizedSha256(elements)
|
|
||||||
}
|
|
||||||
return elements[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// mixinLengthAndCollect computes the final mix-in root for list/bitlist values:
|
|
||||||
//
|
|
||||||
// root = hash(dataRoot, lengthHash)
|
|
||||||
//
|
|
||||||
// where chunks[0] is dataRoot and chunks[1] is the 32-byte length hash.
|
|
||||||
//
|
|
||||||
// Generalized indices (gindices): dataRoot is the left child (dataRootGindex = currentGindex*2) and lengthHash is the right child (lengthHashGindex = currentGindex*2+1).
|
|
||||||
// Proof collection: it calls collectSibling/collectLeaf for both child gindices; the collector stores them only if they were registered via addTarget.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// - currentGindex: gindex of the parent node (list/bitlist root).
|
|
||||||
// - chunks: two 32-byte nodes: [dataRoot, lengthHash].
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - [32]byte: mixed-in Merkle root (or zero value on hashing error).
|
|
||||||
// - error: any error encountered during hashing.
|
|
||||||
func (pc *proofCollector) mixinLengthAndCollect(currentGindex uint64, chunks [][32]byte) [32]byte {
|
|
||||||
dataRoot, lengthHash := chunks[0], chunks[1]
|
|
||||||
dataRootGindex, lengthHashGindex := currentGindex*2, currentGindex*2+1
|
|
||||||
|
|
||||||
pc.collectSibling(dataRootGindex, dataRoot)
|
|
||||||
pc.collectSibling(lengthHashGindex, lengthHash)
|
|
||||||
|
|
||||||
pc.collectLeaf(dataRootGindex, dataRoot)
|
|
||||||
pc.collectLeaf(lengthHashGindex, lengthHash)
|
|
||||||
|
|
||||||
return ssz.MixInLength(dataRoot, lengthHash[:])
|
|
||||||
}
|
|
||||||
@@ -1,531 +0,0 @@
|
|||||||
package query
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/binary"
|
|
||||||
"reflect"
|
|
||||||
"slices"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/OffchainLabs/go-bitfield"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/stateutil"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
|
||||||
ssz "github.com/OffchainLabs/prysm/v7/encoding/ssz"
|
|
||||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
|
||||||
sszquerypb "github.com/OffchainLabs/prysm/v7/proto/ssz_query/testing"
|
|
||||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestProofCollector_New(t *testing.T) {
|
|
||||||
pc := newProofCollector()
|
|
||||||
|
|
||||||
require.NotNil(t, pc)
|
|
||||||
require.Equal(t, 0, len(pc.requiredSiblings))
|
|
||||||
require.Equal(t, 0, len(pc.requiredLeaves))
|
|
||||||
require.Equal(t, 0, len(pc.siblings))
|
|
||||||
require.Equal(t, 0, len(pc.leaves))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_Reset(t *testing.T) {
|
|
||||||
pc := newProofCollector()
|
|
||||||
pc.requiredSiblings[3] = struct{}{}
|
|
||||||
pc.requiredLeaves[5] = struct{}{}
|
|
||||||
pc.siblings[3] = [32]byte{1}
|
|
||||||
pc.leaves[5] = [32]byte{2}
|
|
||||||
|
|
||||||
pc.reset()
|
|
||||||
|
|
||||||
require.Equal(t, 0, len(pc.requiredSiblings))
|
|
||||||
require.Equal(t, 0, len(pc.requiredLeaves))
|
|
||||||
require.Equal(t, 0, len(pc.siblings))
|
|
||||||
require.Equal(t, 0, len(pc.leaves))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_AddTarget(t *testing.T) {
|
|
||||||
pc := newProofCollector()
|
|
||||||
pc.addTarget(5)
|
|
||||||
|
|
||||||
_, hasLeaf := pc.requiredLeaves[5]
|
|
||||||
_, hasSibling4 := pc.requiredSiblings[4]
|
|
||||||
_, hasSibling3 := pc.requiredSiblings[3]
|
|
||||||
_, hasSibling1 := pc.requiredSiblings[1] // GI 1 is the root
|
|
||||||
|
|
||||||
require.Equal(t, true, hasLeaf)
|
|
||||||
require.Equal(t, true, hasSibling4)
|
|
||||||
require.Equal(t, true, hasSibling3)
|
|
||||||
require.Equal(t, false, hasSibling1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_ToProof(t *testing.T) {
|
|
||||||
pc := newProofCollector()
|
|
||||||
pc.addTarget(5)
|
|
||||||
|
|
||||||
leaf := [32]byte{9}
|
|
||||||
sibling4 := [32]byte{4}
|
|
||||||
sibling3 := [32]byte{3}
|
|
||||||
|
|
||||||
pc.collectLeaf(5, leaf)
|
|
||||||
pc.collectSibling(4, sibling4)
|
|
||||||
pc.collectSibling(3, sibling3)
|
|
||||||
|
|
||||||
proof, err := pc.toProof()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, 5, proof.Index)
|
|
||||||
require.DeepEqual(t, leaf[:], proof.Leaf)
|
|
||||||
require.Equal(t, 2, len(proof.Hashes))
|
|
||||||
require.DeepEqual(t, sibling4[:], proof.Hashes[0])
|
|
||||||
require.DeepEqual(t, sibling3[:], proof.Hashes[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_ToProof_NoLeaves(t *testing.T) {
|
|
||||||
pc := newProofCollector()
|
|
||||||
_, err := pc.toProof()
|
|
||||||
require.NotNil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_CollectLeaf(t *testing.T) {
|
|
||||||
pc := newProofCollector()
|
|
||||||
leaf := [32]byte{7}
|
|
||||||
|
|
||||||
pc.collectLeaf(10, leaf)
|
|
||||||
require.Equal(t, 0, len(pc.leaves))
|
|
||||||
|
|
||||||
pc.addTarget(10)
|
|
||||||
pc.collectLeaf(10, leaf)
|
|
||||||
stored, ok := pc.leaves[10]
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
require.Equal(t, leaf, stored)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_CollectSibling(t *testing.T) {
|
|
||||||
pc := newProofCollector()
|
|
||||||
hash := [32]byte{5}
|
|
||||||
|
|
||||||
pc.collectSibling(4, hash)
|
|
||||||
require.Equal(t, 0, len(pc.siblings))
|
|
||||||
|
|
||||||
pc.addTarget(5)
|
|
||||||
pc.collectSibling(4, hash)
|
|
||||||
stored, ok := pc.siblings[4]
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
require.Equal(t, hash, stored)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_Merkleize_BasicTypes(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
sszType SSZType
|
|
||||||
value any
|
|
||||||
expected [32]byte
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "uint8",
|
|
||||||
sszType: Uint8,
|
|
||||||
value: uint8(0x11),
|
|
||||||
expected: func() [32]byte {
|
|
||||||
var leaf [32]byte
|
|
||||||
leaf[0] = 0x11
|
|
||||||
return leaf
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "uint16",
|
|
||||||
sszType: Uint16,
|
|
||||||
value: uint16(0x2211),
|
|
||||||
expected: func() [32]byte {
|
|
||||||
var leaf [32]byte
|
|
||||||
binary.LittleEndian.PutUint16(leaf[:2], 0x2211)
|
|
||||||
return leaf
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "uint32",
|
|
||||||
sszType: Uint32,
|
|
||||||
value: uint32(0x44332211),
|
|
||||||
expected: func() [32]byte {
|
|
||||||
var leaf [32]byte
|
|
||||||
binary.LittleEndian.PutUint32(leaf[:4], 0x44332211)
|
|
||||||
return leaf
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "uint64",
|
|
||||||
sszType: Uint64,
|
|
||||||
value: uint64(0x8877665544332211),
|
|
||||||
expected: func() [32]byte {
|
|
||||||
var leaf [32]byte
|
|
||||||
binary.LittleEndian.PutUint64(leaf[:8], 0x8877665544332211)
|
|
||||||
return leaf
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bool",
|
|
||||||
sszType: Boolean,
|
|
||||||
value: true,
|
|
||||||
expected: func() [32]byte {
|
|
||||||
var leaf [32]byte
|
|
||||||
leaf[0] = 1
|
|
||||||
return leaf
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
pc := newProofCollector()
|
|
||||||
gindex := uint64(3)
|
|
||||||
pc.addTarget(gindex)
|
|
||||||
|
|
||||||
leaf, err := pc.merkleizeBasicType(tc.sszType, reflect.ValueOf(tc.value), gindex)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, tc.expected, leaf)
|
|
||||||
|
|
||||||
stored, ok := pc.leaves[gindex]
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
require.Equal(t, tc.expected, stored)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_Merkleize_Container(t *testing.T) {
|
|
||||||
container := makeFixedTestContainer()
|
|
||||||
|
|
||||||
info, err := AnalyzeObject(container)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
pc := newProofCollector()
|
|
||||||
pc.addTarget(1)
|
|
||||||
|
|
||||||
root, err := pc.merkleize(info, reflect.ValueOf(container), 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected, err := container.HashTreeRoot()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expected, root)
|
|
||||||
|
|
||||||
stored, ok := pc.leaves[1]
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
require.Equal(t, expected, stored)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_Merkleize_Vector(t *testing.T) {
|
|
||||||
container := makeFixedTestContainer()
|
|
||||||
info, err := AnalyzeObject(container)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ci, err := info.ContainerInfo()
|
|
||||||
require.NoError(t, err)
|
|
||||||
field := ci.fields["vector_field"]
|
|
||||||
|
|
||||||
pc := newProofCollector()
|
|
||||||
root, err := pc.merkleizeVector(field.sszInfo, reflect.ValueOf(container.VectorField), 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
serialized := make([][]byte, len(container.VectorField))
|
|
||||||
for i, v := range container.VectorField {
|
|
||||||
buf := make([]byte, 8)
|
|
||||||
binary.LittleEndian.PutUint64(buf, v)
|
|
||||||
serialized[i] = buf
|
|
||||||
}
|
|
||||||
chunks, err := ssz.PackByChunk(serialized)
|
|
||||||
require.NoError(t, err)
|
|
||||||
limit, err := getChunkCount(field.sszInfo)
|
|
||||||
require.NoError(t, err)
|
|
||||||
expected := ssz.MerkleizeVector(chunks, limit)
|
|
||||||
|
|
||||||
require.Equal(t, expected, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_Merkleize_List(t *testing.T) {
|
|
||||||
list := []*sszquerypb.FixedNestedContainer{
|
|
||||||
makeFixedNestedContainer(1),
|
|
||||||
makeFixedNestedContainer(2),
|
|
||||||
}
|
|
||||||
container := makeVariableTestContainer(list, bitfield.NewBitlist(1))
|
|
||||||
info, err := AnalyzeObject(container)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ci, err := info.ContainerInfo()
|
|
||||||
require.NoError(t, err)
|
|
||||||
field := ci.fields["field_list_container"]
|
|
||||||
|
|
||||||
pc := newProofCollector()
|
|
||||||
root, err := pc.merkleizeList(field.sszInfo, reflect.ValueOf(list), 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
listInfo, err := field.sszInfo.ListInfo()
|
|
||||||
require.NoError(t, err)
|
|
||||||
expected, err := ssz.MerkleizeListSSZ(list, listInfo.Limit())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, expected, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_Merkleize_Bitvector(t *testing.T) {
|
|
||||||
container := makeFixedTestContainer()
|
|
||||||
info, err := AnalyzeObject(container)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ci, err := info.ContainerInfo()
|
|
||||||
require.NoError(t, err)
|
|
||||||
field := ci.fields["bitvector64_field"]
|
|
||||||
|
|
||||||
pc := newProofCollector()
|
|
||||||
root, err := pc.merkleizeBitvector(field.sszInfo, reflect.ValueOf(container.Bitvector64Field), 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected, err := ssz.MerkleizeByteSliceSSZ([]byte(container.Bitvector64Field))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expected, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_Merkleize_Bitlist(t *testing.T) {
|
|
||||||
bitlist := bitfield.NewBitlist(16)
|
|
||||||
bitlist.SetBitAt(3, true)
|
|
||||||
bitlist.SetBitAt(8, true)
|
|
||||||
|
|
||||||
container := makeVariableTestContainer(nil, bitlist)
|
|
||||||
info, err := AnalyzeObject(container)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ci, err := info.ContainerInfo()
|
|
||||||
require.NoError(t, err)
|
|
||||||
field := ci.fields["bitlist_field"]
|
|
||||||
|
|
||||||
pc := newProofCollector()
|
|
||||||
root, err := pc.merkleizeBitlist(field.sszInfo, reflect.ValueOf(container.BitlistField), 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
bitlistInfo, err := field.sszInfo.BitlistInfo()
|
|
||||||
require.NoError(t, err)
|
|
||||||
expected, err := ssz.BitlistRoot(bitfield.Bitlist(bitlist), bitlistInfo.Limit())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expected, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_MerkleizeVectorBody_Basic(t *testing.T) {
|
|
||||||
container := makeFixedTestContainer()
|
|
||||||
info, err := AnalyzeObject(container)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ci, err := info.ContainerInfo()
|
|
||||||
require.NoError(t, err)
|
|
||||||
field := ci.fields["vector_field"]
|
|
||||||
vectorInfo, err := field.sszInfo.VectorInfo()
|
|
||||||
require.NoError(t, err)
|
|
||||||
length := len(container.VectorField)
|
|
||||||
limit, err := getChunkCount(field.sszInfo)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
pc := newProofCollector()
|
|
||||||
root, err := pc.merkleizeVectorBody(vectorInfo.element, reflect.ValueOf(container.VectorField), length, limit, 2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
serialized := make([][]byte, len(container.VectorField))
|
|
||||||
for i, v := range container.VectorField {
|
|
||||||
buf := make([]byte, 8)
|
|
||||||
binary.LittleEndian.PutUint64(buf, v)
|
|
||||||
serialized[i] = buf
|
|
||||||
}
|
|
||||||
chunks, err := ssz.PackByChunk(serialized)
|
|
||||||
require.NoError(t, err)
|
|
||||||
expected := ssz.MerkleizeVector(chunks, limit)
|
|
||||||
|
|
||||||
require.Equal(t, expected, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_MerkleizeVectorAndCollect(t *testing.T) {
|
|
||||||
pc := newProofCollector()
|
|
||||||
pc.addTarget(6)
|
|
||||||
|
|
||||||
elements := [][32]byte{{1}, {2}}
|
|
||||||
expected := ssz.MerkleizeVector(slices.Clone(elements), 2)
|
|
||||||
root := pc.merkleizeVectorAndCollect(elements, 3, 1)
|
|
||||||
|
|
||||||
storedLeaf, hasLeaf := pc.leaves[6]
|
|
||||||
storedSibling, hasSibling := pc.siblings[7]
|
|
||||||
|
|
||||||
require.Equal(t, true, hasLeaf)
|
|
||||||
require.Equal(t, true, hasSibling)
|
|
||||||
require.Equal(t, elements[0], storedLeaf)
|
|
||||||
require.Equal(t, elements[1], storedSibling)
|
|
||||||
|
|
||||||
require.Equal(t, expected, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProofCollector_MixinLengthAndCollect(t *testing.T) {
|
|
||||||
list := []*sszquerypb.FixedNestedContainer{
|
|
||||||
makeFixedNestedContainer(1),
|
|
||||||
makeFixedNestedContainer(2),
|
|
||||||
}
|
|
||||||
container := makeVariableTestContainer(list, bitfield.NewBitlist(1))
|
|
||||||
info, err := AnalyzeObject(container)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ci, err := info.ContainerInfo()
|
|
||||||
require.NoError(t, err)
|
|
||||||
field := ci.fields["field_list_container"]
|
|
||||||
|
|
||||||
// Target gindex 2 (data root) - sibling at gindex 3 (length hash) should be collected
|
|
||||||
pc := newProofCollector()
|
|
||||||
pc.addTarget(2)
|
|
||||||
root, err := pc.merkleizeList(field.sszInfo, reflect.ValueOf(list), 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
listInfo, err := field.sszInfo.ListInfo()
|
|
||||||
require.NoError(t, err)
|
|
||||||
expected, err := ssz.MerkleizeListSSZ(list, listInfo.Limit())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expected, root)
|
|
||||||
|
|
||||||
// Verify data root is collected as leaf at gindex 2
|
|
||||||
storedLeaf, hasLeaf := pc.leaves[2]
|
|
||||||
require.Equal(t, true, hasLeaf)
|
|
||||||
|
|
||||||
// Verify length hash is collected as sibling at gindex 3
|
|
||||||
storedSibling, hasSibling := pc.siblings[3]
|
|
||||||
require.Equal(t, true, hasSibling)
|
|
||||||
|
|
||||||
// Verify the root is hash(dataRoot || lengthHash)
|
|
||||||
expectedBuf := append(storedLeaf[:], storedSibling[:]...)
|
|
||||||
expectedRoot := sha256.Sum256(expectedBuf)
|
|
||||||
require.Equal(t, expectedRoot, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkOptimizedValidatorRoots(b *testing.B) {
|
|
||||||
validators := make([]*ethpb.Validator, 1000)
|
|
||||||
for i := range validators {
|
|
||||||
validators[i] = makeTestValidator(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
for b.Loop() {
|
|
||||||
_, err := stateutil.OptimizedValidatorRoots(validators)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkProofCollectorMerkleize(b *testing.B) {
|
|
||||||
validators := make([]*ethpb.Validator, 1000)
|
|
||||||
for i := range validators {
|
|
||||||
validators[i] = makeTestValidator(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := AnalyzeObject(validators[0])
|
|
||||||
require.NoError(b, err)
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
for b.Loop() {
|
|
||||||
for _, val := range validators {
|
|
||||||
pc := newProofCollector()
|
|
||||||
v := reflect.ValueOf(val)
|
|
||||||
_, err := pc.merkleize(info, v, 1)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTestValidator(i int) *ethpb.Validator {
|
|
||||||
pubkey := make([]byte, 48)
|
|
||||||
for j := range pubkey {
|
|
||||||
pubkey[j] = byte(i + j)
|
|
||||||
}
|
|
||||||
|
|
||||||
withdrawalCredentials := make([]byte, 32)
|
|
||||||
for j := range withdrawalCredentials {
|
|
||||||
withdrawalCredentials[j] = byte(255 - ((i + j) % 256))
|
|
||||||
}
|
|
||||||
|
|
||||||
return ðpb.Validator{
|
|
||||||
PublicKey: pubkey,
|
|
||||||
WithdrawalCredentials: withdrawalCredentials,
|
|
||||||
EffectiveBalance: uint64(32000000000 + i),
|
|
||||||
Slashed: i%2 == 0,
|
|
||||||
ActivationEligibilityEpoch: primitives.Epoch(i),
|
|
||||||
ActivationEpoch: primitives.Epoch(i + 1),
|
|
||||||
ExitEpoch: primitives.Epoch(i + 2),
|
|
||||||
WithdrawableEpoch: primitives.Epoch(i + 3),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeFixedNestedContainer(value uint64) *sszquerypb.FixedNestedContainer {
|
|
||||||
value2 := make([]byte, 32)
|
|
||||||
for i := range value2 {
|
|
||||||
value2[i] = byte(i)
|
|
||||||
}
|
|
||||||
return &sszquerypb.FixedNestedContainer{
|
|
||||||
Value1: value,
|
|
||||||
Value2: value2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeFixedTestContainer() *sszquerypb.FixedTestContainer {
|
|
||||||
fieldBytes32 := make([]byte, 32)
|
|
||||||
for i := range fieldBytes32 {
|
|
||||||
fieldBytes32[i] = byte(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
vectorField := make([]uint64, 24)
|
|
||||||
for i := range vectorField {
|
|
||||||
vectorField[i] = uint64(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows := make([][]byte, 5)
|
|
||||||
for i := range rows {
|
|
||||||
row := make([]byte, 32)
|
|
||||||
for j := range row {
|
|
||||||
row[j] = byte(i) + byte(j)
|
|
||||||
}
|
|
||||||
rows[i] = row
|
|
||||||
}
|
|
||||||
|
|
||||||
bitvector64 := bitfield.NewBitvector64()
|
|
||||||
bitvector64.SetBitAt(1, true)
|
|
||||||
bitvector512 := bitfield.NewBitvector512()
|
|
||||||
bitvector512.SetBitAt(10, true)
|
|
||||||
|
|
||||||
trailing := make([]byte, 56)
|
|
||||||
for i := range trailing {
|
|
||||||
trailing[i] = byte(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &sszquerypb.FixedTestContainer{
|
|
||||||
FieldUint32: 1,
|
|
||||||
FieldUint64: 2,
|
|
||||||
FieldBool: true,
|
|
||||||
FieldBytes32: fieldBytes32,
|
|
||||||
Nested: makeFixedNestedContainer(3),
|
|
||||||
VectorField: vectorField,
|
|
||||||
TwoDimensionBytesField: rows,
|
|
||||||
Bitvector64Field: bitvector64,
|
|
||||||
Bitvector512Field: bitvector512,
|
|
||||||
TrailingField: trailing,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeVariableTestContainer(list []*sszquerypb.FixedNestedContainer, bitlist bitfield.Bitlist) *sszquerypb.VariableTestContainer {
|
|
||||||
leading := make([]byte, 32)
|
|
||||||
for i := range leading {
|
|
||||||
leading[i] = byte(i)
|
|
||||||
}
|
|
||||||
trailing := make([]byte, 56)
|
|
||||||
for i := range trailing {
|
|
||||||
trailing[i] = byte(255 - i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bitlist == nil {
|
|
||||||
bitlist = bitfield.NewBitlist(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &sszquerypb.VariableTestContainer{
|
|
||||||
LeadingField: leading,
|
|
||||||
FieldListContainer: list,
|
|
||||||
BitlistField: bitlist,
|
|
||||||
TrailingField: trailing,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -389,7 +389,6 @@ func TestHashTreeRoot(t *testing.T) {
|
|||||||
require.NoError(t, err, "HashTreeRoot should not return an error")
|
require.NoError(t, err, "HashTreeRoot should not return an error")
|
||||||
expectedHashTreeRoot, err := tt.obj.HashTreeRoot()
|
expectedHashTreeRoot, err := tt.obj.HashTreeRoot()
|
||||||
require.NoError(t, err, "HashTreeRoot on original object should not return an error")
|
require.NoError(t, err, "HashTreeRoot on original object should not return an error")
|
||||||
// Verify the Merkle tree root matches with the SSZ generated HashTreeRoot
|
|
||||||
require.Equal(t, expectedHashTreeRoot, hashTreeRoot, "HashTreeRoot from sszInfo should match original object's HashTreeRoot")
|
require.Equal(t, expectedHashTreeRoot, hashTreeRoot, "HashTreeRoot from sszInfo should match original object's HashTreeRoot")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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