Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions simulators/eth2/common/spoofing/beacon/beacon_verification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package beacon

import (
"fmt"

api "github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/hive/simulators/eth2/common/spoofing/proxy"
spoof "github.com/rauljordan/engine-proxy/proxy"
"golang.org/x/exp/slices"
)

// API call names
const (
EngineForkchoiceUpdatedV1 = "engine_forkchoiceUpdatedV1"
EngineForkchoiceUpdatedV2 = "engine_forkchoiceUpdatedV2"
EngineNewPayloadV1 = "engine_newPayloadV1"
EngineNewPayloadV2 = "engine_newPayloadV2"
)

var EngineNewPayload = []string{
EngineNewPayloadV1,
EngineNewPayloadV2,
}

var EngineForkchoiceUpdated = []string{
EngineForkchoiceUpdatedV1,
EngineForkchoiceUpdatedV2,
}

type FallibleLogger interface {
Fail()
Logf(format string, values ...interface{})
}

func GetTimestampFromNewPayload(
req []byte,
) (*uint64, error) {
var payload api.ExecutableData
if err := proxy.UnmarshalFromJsonRPCRequest(req, &payload); err != nil {
return nil, err
}
return &payload.Timestamp, nil
}

func GetTimestampFromFcU(
req []byte,
) (*uint64, error) {
var (
fcS api.ForkchoiceStateV1
pA *api.PayloadAttributes
)
if err := proxy.UnmarshalFromJsonRPCRequest(req, &fcS, &pA); err != nil {
return nil, err
}
if pA == nil {
return nil, nil
}
return &pA.Timestamp, nil
}

type EngineEndpointMaxTimestampVerify struct {
Endpoint string
ExpiringTimestamp uint64
GetTimestampFn func([]byte) (*uint64, error)
FallibleLogger FallibleLogger
}

func (v *EngineEndpointMaxTimestampVerify) Verify(
req []byte,
) *spoof.Spoof {
if v.GetTimestampFn == nil {
panic(fmt.Errorf("timestamp parse function not specified"))
}
timestamp, err := v.GetTimestampFn(req)
if err != nil {
panic(err)
}
if timestamp != nil && *timestamp >= v.ExpiringTimestamp {
if v.FallibleLogger == nil {
panic(fmt.Errorf("test is nil"))
}
v.FallibleLogger.Logf(
"FAIL: received directive using expired endpoint %s: timestamp %d >= %d",
v.Endpoint,
*timestamp,
v.ExpiringTimestamp,
)
v.FallibleLogger.Fail()
}
return nil
}

func (v *EngineEndpointMaxTimestampVerify) AddToProxy(p *proxy.Proxy) error {
if p == nil {
return fmt.Errorf("attempted to add to nil proxy")
}
if v.Endpoint == "" {
return fmt.Errorf("attempted to add to proxy with empty endpoint")
}
p.AddRequestCallback(v.Endpoint, v.Verify)
return nil
}

func NewEngineMaxTimestampVerifier(
t FallibleLogger,
endpoint string,
expiringTimestamp uint64,
) *EngineEndpointMaxTimestampVerify {
var getTimestampFn func([]byte) (*uint64, error)
if slices.Contains(EngineNewPayload, endpoint) {
getTimestampFn = GetTimestampFromNewPayload
} else if slices.Contains(EngineForkchoiceUpdated, endpoint) {
getTimestampFn = GetTimestampFromFcU
} else {
panic(fmt.Errorf("invalid endpoint for verification"))
}

return &EngineEndpointMaxTimestampVerify{
Endpoint: endpoint,
ExpiringTimestamp: expiringTimestamp,
FallibleLogger: t,
GetTimestampFn: getTimestampFn,
}
}
21 changes: 21 additions & 0 deletions simulators/eth2/withdrawals/scenarios.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/hive/hivesim"
"github.com/ethereum/hive/simulators/eth2/common/clients"

beacon_verification "github.com/ethereum/hive/simulators/eth2/common/spoofing/beacon"
tn "github.com/ethereum/hive/simulators/eth2/common/testnet"
"github.com/protolambda/eth2api"
beacon "github.com/protolambda/zrnt/eth2/beacon/common"
Expand All @@ -31,6 +33,25 @@ func (ts BaseWithdrawalsTestSpec) Execute(
testnet := tn.StartTestnet(ctx, t, env, config)
defer testnet.Stop()

// Add verification of Beacon->Execution Engine API calls to the proxies

// NewPayloadV1 expires at ShanghaiTime
newPayloadV1ExpireVerifier := beacon_verification.NewEngineMaxTimestampVerifier(
t,
beacon_verification.EngineNewPayloadV1,
testnet.ExecutionGenesis().Config.ShanghaiTime.Uint64(),
)
// ForkchoiceUpdatedV1 expires at ShanghaiTime
forkchoiceUpdatedV1ExpireVerifier := beacon_verification.NewEngineMaxTimestampVerifier(
t,
beacon_verification.EngineForkchoiceUpdatedV1,
testnet.ExecutionGenesis().Config.ShanghaiTime.Uint64(),
)
for _, e := range testnet.ExecutionClients() {
newPayloadV1ExpireVerifier.AddToProxy(e.Proxy())
forkchoiceUpdatedV1ExpireVerifier.AddToProxy(e.Proxy())
}

blsDomain := ComputeBLSToExecutionDomain(testnet)

// Get all validators info
Expand Down