Compare commits

..

2 Commits

Author SHA1 Message Date
potuz
4340b61a78 init 2026-02-04 07:42:24 -03:00
Preston Van Loon
d1b9281677 golangci-lint: Remove test exclusion from formatting (#16318)
**What type of PR is this?**

> Other

**What does this PR do? Why is it needed?**

**Which issues(s) does this PR fix?**

Follow up to #16311

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-02-02 17:42:05 +00:00
19 changed files with 84 additions and 483 deletions

View File

@@ -2,7 +2,7 @@ name: Go
on:
push:
branches: [ master ]
branches: [ master, develop ]
pull_request:
branches: [ '*' ]
merge_group:

View File

@@ -33,9 +33,8 @@ formatters:
generated: lax
paths:
- validator/web/site_data.go
- .*_test.go
- proto
- tools/analyzers
- third_party$
- builtin$
- examples$
- examples$

View File

@@ -8,7 +8,6 @@ go_library(
"deposit.go",
"engine_client.go",
"errors.go",
"graffiti_info.go",
"log.go",
"log_processing.go",
"metrics.go",
@@ -90,7 +89,6 @@ go_test(
"engine_client_fuzz_test.go",
"engine_client_test.go",
"execution_chain_test.go",
"graffiti_info_test.go",
"init_test.go",
"log_processing_test.go",
"mock_test.go",

View File

@@ -61,17 +61,7 @@ var (
}
)
// ClientVersionV1 represents the response from engine_getClientVersionV1.
type ClientVersionV1 struct {
Code string `json:"code"`
Name string `json:"name"`
Version string `json:"version"`
Commit string `json:"commit"`
}
const (
// GetClientVersionMethod is the engine_getClientVersionV1 method for JSON-RPC.
GetClientVersionMethod = "engine_getClientVersionV1"
// NewPayloadMethod v1 request string for JSON-RPC.
NewPayloadMethod = "engine_newPayloadV1"
// NewPayloadMethodV2 v2 request string for JSON-RPC.
@@ -360,27 +350,6 @@ func (s *Service) ExchangeCapabilities(ctx context.Context) ([]string, error) {
return elSupportedEndpointsSlice, nil
}
// GetClientVersion calls engine_getClientVersionV1 to retrieve EL client information.
func (s *Service) GetClientVersion(ctx context.Context) ([]ClientVersionV1, error) {
ctx, span := trace.StartSpan(ctx, "powchain.engine-api-client.GetClientVersion")
defer span.End()
// Per spec, we send our own client info as the parameter
clVersion := ClientVersionV1{
Code: CLCode,
Name: "Prysm",
Version: version.SemanticVersion(),
Commit: version.GetCommitPrefix(),
}
var result []ClientVersionV1
err := s.rpcClient.CallContext(ctx, &result, GetClientVersionMethod, clVersion)
if err != nil {
return nil, handleRPCError(err)
}
return result, nil
}
// GetTerminalBlockHash returns the valid terminal block hash based on total difficulty.
//
// Spec code:

View File

@@ -1,126 +0,0 @@
package execution
import (
"sync"
"github.com/OffchainLabs/prysm/v7/runtime/version"
)
const (
// CLCode is the two-letter client code for Prysm.
CLCode = "PR"
)
// GraffitiInfo holds version information for generating block graffiti.
// It is thread-safe and can be updated by the execution service and read by the validator server.
type GraffitiInfo struct {
mu sync.RWMutex
elCode string // From engine_getClientVersionV1
elCommit string // From engine_getClientVersionV1
logOnce sync.Once
}
// NewGraffitiInfo creates a new GraffitiInfo.
func NewGraffitiInfo() *GraffitiInfo {
return &GraffitiInfo{}
}
// UpdateFromEngine updates the EL client information.
func (g *GraffitiInfo) UpdateFromEngine(code, commit string) {
g.mu.Lock()
defer g.mu.Unlock()
g.elCode = code
g.elCommit = commit
}
// GenerateGraffiti generates graffiti using the flexible standard
// with the provided user graffiti from the validator client request.
// It places user graffiti first, then appends as much client info as space allows.
//
// Available Space | Format
// ≥12 bytes | user+EL(2)+commit(4)+CL(2)+commit(4) e.g. "SushiGEabcdPRxxxx"
// 8-11 bytes | user+EL(2)+commit(2)+CL(2)+commit(2) e.g. "SushiGEabPRxx"
// 4-7 bytes | user+EL(2)+CL(2) e.g. "SushiGEPR"
// 2-3 bytes | user+code(2) e.g. "SushiGE" or "SushiPR"
// <2 bytes | user only e.g. "Sushi"
func (g *GraffitiInfo) GenerateGraffiti(userGraffiti []byte) [32]byte {
g.mu.RLock()
defer g.mu.RUnlock()
var result [32]byte
userStr := string(userGraffiti)
// Trim trailing null bytes
for len(userStr) > 0 && userStr[len(userStr)-1] == 0 {
userStr = userStr[:len(userStr)-1]
}
available := 32 - len(userStr)
g.logOnce.Do(func() {
logGraffitiInfo(userStr, available)
})
clCommit := version.GetCommitPrefix()
clCommit4 := truncateCommit(clCommit, 4)
clCommit2 := truncateCommit(clCommit, 2)
// If no EL info, clear EL commits but still include CL info
var elCommit4, elCommit2 string
if g.elCode != "" {
elCommit4 = truncateCommit(g.elCommit, 4)
elCommit2 = truncateCommit(g.elCommit, 2)
}
var graffiti string
switch {
case available >= 12:
// Full: user+EL(2)+commit(4)+CL(2)+commit(4)
graffiti = userStr + g.elCode + elCommit4 + CLCode + clCommit4
case available >= 8:
// Reduced commits: user+EL(2)+commit(2)+CL(2)+commit(2)
graffiti = userStr + g.elCode + elCommit2 + CLCode + clCommit2
case available >= 4:
// Codes only: user+EL(2)+CL(2)
graffiti = userStr + g.elCode + CLCode
case available >= 2:
// Single code: user+code(2)
if g.elCode != "" {
graffiti = userStr + g.elCode
} else {
graffiti = userStr + CLCode
}
default:
// User graffiti only
graffiti = userStr
}
copy(result[:], graffiti)
return result
}
// logGraffitiInfo logs the graffiti format that will be used based on available space.
func logGraffitiInfo(userStr string, available int) {
userLen := len(userStr)
fields := log.WithField("userGraffiti", userStr).WithField("userGraffitiLength", userLen)
switch {
case available >= 12:
fields.Info("Graffiti: full client version format (EL code + 4-char commit + CL code + 4-char commit)")
case available >= 8:
fields.Warn("Graffiti: user graffiti reduces client version to 2-char commits")
case available >= 4:
fields.Warn("Graffiti: user graffiti reduces client version to codes only (no commits)")
case available >= 2:
fields.Warn("Graffiti: user graffiti reduces client version to single code only")
default:
fields.Warn("Graffiti: user graffiti consumes all 32 bytes, no client version info will be included")
}
}
// truncateCommit returns the first n characters of the commit string.
func truncateCommit(commit string, n int) string {
if len(commit) <= n {
return commit
}
return commit[:n]
}

View File

@@ -1,203 +0,0 @@
package execution
import (
"testing"
"github.com/OffchainLabs/prysm/v7/testing/require"
)
func TestGraffitiInfo_GenerateGraffiti(t *testing.T) {
tests := []struct {
name string
elCode string
elCommit string
userGraffiti []byte
wantPrefix string // user graffiti appears first
wantSuffix string // client version info appended after
}{
// No EL info cases (CL info "PR" + commit still included when space allows)
{
name: "No EL - empty user graffiti",
elCode: "",
elCommit: "",
userGraffiti: []byte{},
wantPrefix: "PR", // Only CL code + commit (no user graffiti to prefix)
},
{
name: "No EL - short user graffiti",
elCode: "",
elCommit: "",
userGraffiti: []byte("my validator"),
wantPrefix: "my validator",
wantSuffix: "PR", // CL code appended (within suffix that includes commit)
},
{
name: "No EL - 28 char user graffiti (4 bytes available)",
elCode: "",
elCommit: "",
userGraffiti: []byte("1234567890123456789012345678"), // 28 chars, 4 bytes available = codes only
wantPrefix: "1234567890123456789012345678",
wantSuffix: "PR", // CL code (no EL, so just PR)
},
{
name: "No EL - 30 char user graffiti (2 bytes available)",
elCode: "",
elCommit: "",
userGraffiti: []byte("123456789012345678901234567890"), // 30 chars, 2 bytes available = fits PR
wantPrefix: "123456789012345678901234567890",
wantSuffix: "PR",
},
{
name: "No EL - 31 char user graffiti (1 byte available)",
elCode: "",
elCommit: "",
userGraffiti: []byte("1234567890123456789012345678901"), // 31 chars, 1 byte available = not enough for code
wantPrefix: "1234567890123456789012345678901", // User only
},
{
name: "No EL - 32 char user graffiti (0 bytes available)",
elCode: "",
elCommit: "",
userGraffiti: []byte("12345678901234567890123456789012"),
wantPrefix: "12345678901234567890123456789012", // User only
},
// With EL info - flexible standard format cases
{
name: "With EL - full format (empty user graffiti)",
elCode: "GE",
elCommit: "abcd1234",
userGraffiti: []byte{},
wantPrefix: "GEabcdPR", // No user graffiti, starts with client info
},
{
name: "With EL - full format (short user graffiti)",
elCode: "GE",
elCommit: "abcd1234",
userGraffiti: []byte("Bob"),
wantPrefix: "Bob",
wantSuffix: "GEabcdPR", // EL(2)+commit(4)+CL(2)+commit(4)
},
{
name: "With EL - full format (20 char user, 12 bytes available)",
elCode: "GE",
elCommit: "abcd1234",
userGraffiti: []byte("12345678901234567890"), // 20 chars, leaves 12 bytes = full format
wantPrefix: "12345678901234567890",
wantSuffix: "GEabcdPR", // Full format fits (12 bytes)
},
{
name: "With EL - reduced commits (24 char user, 8 bytes available)",
elCode: "GE",
elCommit: "abcd1234",
userGraffiti: []byte("123456789012345678901234"), // 24 chars, leaves 8 bytes = reduced format
wantPrefix: "123456789012345678901234",
wantSuffix: "GEabPR", // Reduced format (8 bytes)
},
{
name: "With EL - codes only (28 char user, 4 bytes available)",
elCode: "GE",
elCommit: "abcd1234",
userGraffiti: []byte("1234567890123456789012345678"), // 28 chars, leaves 4 bytes = codes only
wantPrefix: "1234567890123456789012345678",
wantSuffix: "GEPR", // Codes only (4 bytes)
},
{
name: "With EL - EL code only (30 char user, 2 bytes available)",
elCode: "GE",
elCommit: "abcd1234",
userGraffiti: []byte("123456789012345678901234567890"), // 30 chars, leaves 2 bytes = EL code only
wantPrefix: "123456789012345678901234567890",
wantSuffix: "GE", // EL code (2 bytes)
},
{
name: "With EL - user only (31 char user, 1 byte available)",
elCode: "GE",
elCommit: "abcd1234",
userGraffiti: []byte("1234567890123456789012345678901"), // 31 chars, leaves 1 byte = not enough for code
wantPrefix: "1234567890123456789012345678901", // User only
},
{
name: "With EL - user only (32 char user, 0 bytes available)",
elCode: "GE",
elCommit: "abcd1234",
userGraffiti: []byte("12345678901234567890123456789012"),
wantPrefix: "12345678901234567890123456789012",
},
// Null byte handling
{
name: "Null bytes - input with trailing nulls",
elCode: "GE",
elCommit: "abcd1234",
userGraffiti: append([]byte("test"), 0, 0, 0),
wantPrefix: "test",
wantSuffix: "GEabcdPR",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewGraffitiInfo()
if tt.elCode != "" {
g.UpdateFromEngine(tt.elCode, tt.elCommit)
}
result := g.GenerateGraffiti(tt.userGraffiti)
resultStr := string(result[:])
trimmed := trimNullBytes(resultStr)
// Check prefix (user graffiti comes first)
require.Equal(t, true, len(trimmed) >= len(tt.wantPrefix), "Result too short for prefix check")
require.Equal(t, tt.wantPrefix, trimmed[:len(tt.wantPrefix)], "Prefix mismatch")
// Check suffix if specified (client version info appended)
if tt.wantSuffix != "" {
require.Equal(t, true, len(trimmed) >= len(tt.wantSuffix), "Result too short for suffix check")
// The suffix should appear somewhere after the prefix
afterPrefix := trimmed[len(tt.wantPrefix):]
require.Equal(t, true, len(afterPrefix) >= len(tt.wantSuffix), "Not enough room for suffix after prefix")
require.Equal(t, tt.wantSuffix, afterPrefix[:len(tt.wantSuffix)], "Suffix mismatch")
}
})
}
}
func TestGraffitiInfo_UpdateFromEngine(t *testing.T) {
g := NewGraffitiInfo()
// Initially no EL info - should still have CL info (PR + commit)
result := g.GenerateGraffiti([]byte{})
resultStr := trimNullBytes(string(result[:]))
require.Equal(t, "PR", resultStr[:2], "Expected CL info before update")
// Update with EL info
g.UpdateFromEngine("GE", "1234abcd")
result = g.GenerateGraffiti([]byte{})
resultStr = trimNullBytes(string(result[:]))
require.Equal(t, "GE1234PR", resultStr[:8], "Expected EL+CL info after update")
}
func TestTruncateCommit(t *testing.T) {
tests := []struct {
commit string
n int
want string
}{
{"abcd1234", 4, "abcd"},
{"ab", 4, "ab"},
{"", 4, ""},
{"abcdef", 2, "ab"},
}
for _, tt := range tests {
got := truncateCommit(tt.commit, tt.n)
require.Equal(t, tt.want, got)
}
}
func trimNullBytes(s string) string {
for len(s) > 0 && s[len(s)-1] == 0 {
s = s[:len(s)-1]
}
return s
}

View File

@@ -124,11 +124,3 @@ func WithVerifierWaiter(v *verification.InitializerWaiter) Option {
return nil
}
}
// WithGraffitiInfo sets the GraffitiInfo for client version tracking.
func WithGraffitiInfo(g *GraffitiInfo) Option {
return func(s *Service) error {
s.graffitiInfo = g
return nil
}
}

View File

@@ -162,7 +162,6 @@ type Service struct {
verifierWaiter *verification.InitializerWaiter
blobVerifier verification.NewBlobVerifier
capabilityCache *capabilityCache
graffitiInfo *GraffitiInfo
}
// NewService sets up a new instance with an ethclient when given a web3 endpoint as a string in the config.
@@ -319,28 +318,6 @@ func (s *Service) updateConnectedETH1(state bool) {
s.updateBeaconNodeStats()
}
// GraffitiInfo returns the GraffitiInfo struct for graffiti generation.
func (s *Service) GraffitiInfo() *GraffitiInfo {
return s.graffitiInfo
}
// updateGraffitiInfo fetches EL client version and updates the graffiti info.
func (s *Service) updateGraffitiInfo() {
if s.graffitiInfo == nil {
return
}
ctx, cancel := context.WithTimeout(s.ctx, time.Second)
defer cancel()
versions, err := s.GetClientVersion(ctx)
if err != nil {
log.WithError(err).Debug("Could not get execution client version for graffiti")
return
}
if len(versions) >= 1 {
s.graffitiInfo.UpdateFromEngine(versions[0].Code, versions[0].Commit)
}
}
// refers to the latest eth1 block which follows the condition: eth1_timestamp +
// SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= current_unix_time
func (s *Service) followedBlockHeight(ctx context.Context) (uint64, error) {
@@ -621,12 +598,6 @@ func (s *Service) run(done <-chan struct{}) {
chainstartTicker := time.NewTicker(logPeriod)
defer chainstartTicker.Stop()
// Update graffiti info 4 times per epoch (~96 seconds with 12s slots and 32 slots/epoch)
graffitiTicker := time.NewTicker(96 * time.Second)
defer graffitiTicker.Stop()
// Initial update
s.updateGraffitiInfo()
for {
select {
case <-done:
@@ -651,8 +622,6 @@ func (s *Service) run(done <-chan struct{}) {
continue
}
s.logTillChainStart(context.Background())
case <-graffitiTicker.C:
s.updateGraffitiInfo()
}
}
}

View File

@@ -66,16 +66,30 @@ func (s *Store) head(ctx context.Context) ([32]byte, error) {
// It then updates the new node's parent with the best child and descendant node.
func (s *Store) insert(ctx context.Context,
roblock consensus_blocks.ROBlock,
justifiedEpoch, finalizedEpoch primitives.Epoch) (*Node, error) {
justifiedEpoch, finalizedEpoch primitives.Epoch,
) (*PayloadNode, error) {
ctx, span := trace.StartSpan(ctx, "doublyLinkedForkchoice.insert")
defer span.End()
root := roblock.Root()
// Return if the block has been inserted into Store before.
if n, ok := s.nodeByRoot[root]; ok {
return n, nil
}
block := roblock.Block()
slot := block.Slot()
parentRoot := block.ParentRoot()
var payloadHash [32]byte
if block.Version() >= version.Bellatrix {
parent := s.nodeByRoot[parentRoot]
var (
payloadHash *[32]byte
ok bool
)
if block.Version() >= version.Gloas {
if err := s.getNodeInformation(block, parent, payloadHash); err != nil {
return nil, err
}
} else if block.Version() >= version.Bellatrix {
execution, err := block.Body().Execution()
if err != nil {
return nil, err
@@ -83,12 +97,6 @@ func (s *Store) insert(ctx context.Context,
copy(payloadHash[:], execution.BlockHash())
}
// Return if the block has been inserted into Store before.
if n, ok := s.nodeByRoot[root]; ok {
return n, nil
}
parent := s.nodeByRoot[parentRoot]
n := &Node{
slot: slot,
root: root,
@@ -97,24 +105,39 @@ func (s *Store) insert(ctx context.Context,
unrealizedJustifiedEpoch: justifiedEpoch,
finalizedEpoch: finalizedEpoch,
unrealizedFinalizedEpoch: finalizedEpoch,
optimistic: true,
payloadHash: payloadHash,
timestamp: time.Now(),
payloadHash: *payloadHash,
}
// Set the node's target checkpoint
if slot%params.BeaconConfig().SlotsPerEpoch == 0 {
n.target = n
} else if parent != nil {
if slots.ToEpoch(slot) == slots.ToEpoch(parent.slot) {
n.target = parent.target
if slots.ToEpoch(slot) == slots.ToEpoch(parent.node.slot) {
n.target = parent.node.target
} else {
n.target = parent
n.target = parent.node
}
}
var ret *PayloadNode
// Make the empty node.
pn := &PayloadNode{
node: n,
optimistic: true,
timestamp: time.Now(),
}
s.nodeByRoot[root] = pn
ret = pn
if block.Version() < version.Gloas {
// Make also the full node
fn := &PayloadNode{
node: n,
optimistic: true,
timestamp: time.Now(),
full: true,
}
ret = fn
s.nodeByPayload[*payloadHash] = fn
}
s.nodeByPayload[payloadHash] = n
s.nodeByRoot[root] = n
if parent == nil {
if s.treeRootNode == nil {
s.treeRootNode = n
@@ -122,7 +145,7 @@ func (s *Store) insert(ctx context.Context,
s.highestReceivedNode = n
} else {
delete(s.nodeByRoot, root)
delete(s.nodeByPayload, payloadHash)
delete(s.nodeByPayload, *payloadHash)
return nil, errInvalidParentRoot
}
} else {
@@ -130,7 +153,7 @@ func (s *Store) insert(ctx context.Context,
// Apply proposer boost
now := time.Now()
if now.Before(s.genesisTime) {
return n, nil
return ret, nil
}
currentSlot := slots.CurrentSlot(s.genesisTime)
sss, err := slots.SinceSlotStart(currentSlot, s.genesisTime, now)
@@ -167,7 +190,7 @@ func (s *Store) insert(ctx context.Context,
s.highestReceivedNode = n
}
return n, nil
return ret, nil
}
// pruneFinalizedNodeByRootMap prunes the `nodeByRoot` map

View File

@@ -23,22 +23,22 @@ type ForkChoice struct {
// Store defines the fork choice store which includes block nodes and the last view of checkpoint information.
type Store struct {
justifiedCheckpoint *forkchoicetypes.Checkpoint // latest justified epoch in store.
unrealizedJustifiedCheckpoint *forkchoicetypes.Checkpoint // best unrealized justified checkpoint in store.
unrealizedFinalizedCheckpoint *forkchoicetypes.Checkpoint // best unrealized finalized checkpoint in store.
prevJustifiedCheckpoint *forkchoicetypes.Checkpoint // previous justified checkpoint in store.
finalizedCheckpoint *forkchoicetypes.Checkpoint // latest finalized epoch in store.
proposerBoostRoot [fieldparams.RootLength]byte // latest block root that was boosted after being received in a timely manner.
previousProposerBoostRoot [fieldparams.RootLength]byte // previous block root that was boosted after being received in a timely manner.
previousProposerBoostScore uint64 // previous proposer boosted root score.
finalizedDependentRoot [fieldparams.RootLength]byte // dependent root at finalized checkpoint.
committeeWeight uint64 // tracks the total active validator balance divided by the number of slots per Epoch.
treeRootNode *Node // the root node of the store tree.
headNode *Node // last head Node
nodeByRoot map[[fieldparams.RootLength]byte]*Node // nodes indexed by roots.
nodeByPayload map[[fieldparams.RootLength]byte]*Node // nodes indexed by payload Hash
slashedIndices map[primitives.ValidatorIndex]bool // the list of equivocating validator indices
originRoot [fieldparams.RootLength]byte // The genesis block root
justifiedCheckpoint *forkchoicetypes.Checkpoint // latest justified epoch in store.
unrealizedJustifiedCheckpoint *forkchoicetypes.Checkpoint // best unrealized justified checkpoint in store.
unrealizedFinalizedCheckpoint *forkchoicetypes.Checkpoint // best unrealized finalized checkpoint in store.
prevJustifiedCheckpoint *forkchoicetypes.Checkpoint // previous justified checkpoint in store.
finalizedCheckpoint *forkchoicetypes.Checkpoint // latest finalized epoch in store.
proposerBoostRoot [fieldparams.RootLength]byte // latest block root that was boosted after being received in a timely manner.
previousProposerBoostRoot [fieldparams.RootLength]byte // previous block root that was boosted after being received in a timely manner.
previousProposerBoostScore uint64 // previous proposer boosted root score.
finalizedDependentRoot [fieldparams.RootLength]byte // dependent root at finalized checkpoint.
committeeWeight uint64 // tracks the total active validator balance divided by the number of slots per Epoch.
treeRootNode *Node // the root node of the store tree.
headNode *Node // last head Node
nodeByRoot map[[fieldparams.RootLength]byte]*PayloadNode // nodes indexed by roots.
nodeByPayload map[[fieldparams.RootLength]byte]*PayloadNode // nodes indexed by payload Hash
slashedIndices map[primitives.ValidatorIndex]bool // the list of equivocating validator indices
originRoot [fieldparams.RootLength]byte // The genesis block root
genesisTime time.Time
highestReceivedNode *Node // The highest slot node.
receivedBlocksLastEpoch [fieldparams.SlotsPerEpoch]primitives.Slot // Using `highestReceivedSlot`. The slot of blocks received in the last epoch.
@@ -51,18 +51,26 @@ type Node struct {
slot primitives.Slot // slot of the block converted to the node.
root [fieldparams.RootLength]byte // root of the block converted to the node.
payloadHash [fieldparams.RootLength]byte // payloadHash of the block converted to the node.
parent *Node // parent index of this node.
parent *PayloadNode // parent index of this node.
target *Node // target checkpoint for
children []*Node // the list of direct children of this Node
justifiedEpoch primitives.Epoch // justifiedEpoch of this node.
unrealizedJustifiedEpoch primitives.Epoch // the epoch that would be justified if the block would be advanced to the next epoch.
finalizedEpoch primitives.Epoch // finalizedEpoch of this node.
unrealizedFinalizedEpoch primitives.Epoch // the epoch that would be finalized if the block would be advanced to the next epoch.
balance uint64 // the balance that voted for this node directly
weight uint64 // weight of this node: the total balance including children
bestDescendant *Node // bestDescendant node of this node.
optimistic bool // whether the block has been fully validated or not
timestamp time.Time // The timestamp when the node was inserted.
}
// PayloadNode defines a full Forkchoice node after the Gloas fork, with the payload status either empty of full
type PayloadNode struct {
node *Node // the consensus part of this full forkchoice node
children []*Node // the list of direct children of this Node
bestDescendant *Node // bestDescendant node of this node.
full bool // whether this node represents a payload present or not
balance uint64 // the balance that voted for this node directly
weight uint64 // weight of this node: the total balance including children
optimistic bool // whether the block has been fully validated or not
timestamp time.Time // The timestamp when the node was inserted.
}
// Vote defines an individual validator's vote.

View File

@@ -775,9 +775,6 @@ func (b *BeaconNode) registerPOWChainService() error {
return err
}
// Create GraffitiInfo for client version tracking in block graffiti
graffitiInfo := execution.NewGraffitiInfo()
// skipcq: CRT-D0001
opts := append(
b.serviceFlagOpts.executionChainFlagOpts,
@@ -790,7 +787,6 @@ func (b *BeaconNode) registerPOWChainService() error {
execution.WithFinalizedStateAtStartup(b.finalizedStateAtStartUp),
execution.WithJwtId(b.cliCtx.String(flags.JwtId.Name)),
execution.WithVerifierWaiter(b.verifyInitWaiter),
execution.WithGraffitiInfo(graffitiInfo),
)
web3Service, err := execution.NewService(b.ctx, opts...)
if err != nil {
@@ -997,7 +993,6 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error {
TrackedValidatorsCache: b.trackedValidatorsCache,
PayloadIDCache: b.payloadIDCache,
LCStore: b.lcStore,
GraffitiInfo: web3Service.GraffitiInfo(),
})
return b.services.RegisterService(rpcService)

View File

@@ -89,13 +89,7 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
}
// Set slot, graffiti, randao reveal, and parent root.
sBlk.SetSlot(req.Slot)
// Generate graffiti with client version info using flexible standard
if vs.GraffitiInfo != nil {
graffiti := vs.GraffitiInfo.GenerateGraffiti(req.Graffiti)
sBlk.SetGraffiti(graffiti[:])
} else {
sBlk.SetGraffiti(req.Graffiti)
}
sBlk.SetGraffiti(req.Graffiti)
sBlk.SetRandaoReveal(req.RandaoReveal)
sBlk.SetParentRoot(parentRoot[:])

View File

@@ -83,7 +83,6 @@ type Server struct {
ClockWaiter startup.ClockWaiter
CoreService *core.Service
AttestationStateFetcher blockchain.AttestationStateFetcher
GraffitiInfo *execution.GraffitiInfo
}
// 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.

View File

@@ -125,7 +125,6 @@ type Config struct {
TrackedValidatorsCache *cache.TrackedValidatorsCache
PayloadIDCache *cache.PayloadIDCache
LCStore *lightClient.Store
GraffitiInfo *execution.GraffitiInfo
}
// NewService instantiates a new RPC service instance that will
@@ -257,7 +256,6 @@ func NewService(ctx context.Context, cfg *Config) *Service {
TrackedValidatorsCache: s.cfg.TrackedValidatorsCache,
PayloadIDCache: s.cfg.PayloadIDCache,
AttestationStateFetcher: s.cfg.AttestationReceiver,
GraffitiInfo: s.cfg.GraffitiInfo,
}
s.validatorServer = validatorServer
nodeServer := &nodev1alpha1.Server{

View File

@@ -0,0 +1,3 @@
### Ignored
- Updated golangci to run lint on tests too.

View File

@@ -1,3 +0,0 @@
### Added
- Graffiti implementation based on the design doc.

View File

@@ -47,13 +47,3 @@ func BuildData() string {
}
return fmt.Sprintf("Prysm/%s/%s", gitTag, gitCommit)
}
// GetCommitPrefix returns the first 4 characters of the git commit.
// This is used for graffiti generation per the client identification spec.
// Note: BuildData() must be called before this (happens at startup via Version()).
func GetCommitPrefix() string {
if len(gitCommit) < 4 {
return gitCommit
}
return gitCommit[:4]
}

View File

@@ -260,21 +260,17 @@ func verifyGraffitiInBlocks(_ *e2etypes.EvaluationContext, conns ...*grpc.Client
if err != nil {
return err
}
var found bool
var e bool
slot := blk.Block().Slot()
graffitiInBlock := blk.Block().Body().Graffiti()
// Trim trailing null bytes from graffiti.
// Example: "SushiGEabcdPRxxxx\x00\x00\x00..." becomes "SushiGEabcdPRxxxx"
graffitiTrimmed := bytes.TrimRight(graffitiInBlock[:], "\x00")
for _, graffiti := range helpers.Graffiti {
// Check prefix match since user graffiti comes first, with EL/CL version info appended after.
if bytes.HasPrefix(graffitiTrimmed, []byte(graffiti)) {
found = true
if bytes.Equal(bytesutil.PadTo([]byte(graffiti), 32), graffitiInBlock[:]) {
e = true
break
}
}
if !found && slot != 0 {
return fmt.Errorf("block at slot %d has graffiti %q which does not start with any expected graffiti", slot, string(graffitiTrimmed))
if !e && slot != 0 {
return errors.New("could not get graffiti from the list")
}
}