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
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ nogo(
"//tools/analyzers/featureconfig:go_default_library",
"//tools/analyzers/gocognit:go_default_library",
"//tools/analyzers/ineffassign:go_default_library",
"//tools/analyzers/httperror:go_default_library",
"//tools/analyzers/interfacechecker:go_default_library",
"//tools/analyzers/logcapitalization:go_default_library",
"//tools/analyzers/logruswitherror:go_default_library",
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/rpc/eth/beacon/handlers_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ func (s *Server) SubmitAttesterSlashingsV2(w http.ResponseWriter, r *http.Reques
versionHeader := r.Header.Get(api.VersionHeader)
if versionHeader == "" {
httputil.HandleError(w, api.VersionHeader+" header is required", http.StatusBadRequest)
return
}
v, err := version.FromString(versionHeader)
if err != nil {
Expand Down
27 changes: 27 additions & 0 deletions beacon-chain/rpc/eth/beacon/handlers_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2112,6 +2112,33 @@ func TestSubmitAttesterSlashingsV2(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.StringContains(t, "Invalid attester slashing", e.Message)
})

t.Run("missing-version-header", func(t *testing.T) {
bs, err := util.NewBeaconStateElectra()
require.NoError(t, err)

broadcaster := &p2pMock.MockBroadcaster{}
s := &Server{
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
SlashingsPool: &slashingsmock.PoolMock{},
Broadcaster: broadcaster,
}

var body bytes.Buffer
_, err = body.WriteString(invalidAttesterSlashing)
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body)
// Intentionally do not set api.VersionHeader to verify missing header handling.
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

s.SubmitAttesterSlashingsV2(writer, request)
require.Equal(t, http.StatusBadRequest, writer.Code)
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.StringContains(t, api.VersionHeader+" header is required", e.Message)
})
}

func TestSubmitProposerSlashing_InvalidSlashing(t *testing.T) {
Expand Down
5 changes: 5 additions & 0 deletions beacon-chain/rpc/eth/light-client/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func (s *Server) GetLightClientUpdatesByRange(w http.ResponseWriter, req *http.R
for _, update := range updates {
if ctx.Err() != nil {
httputil.HandleError(w, "Context error: "+ctx.Err().Error(), http.StatusInternalServerError)
return
}

updateSlot := update.AttestedHeader().Beacon().Slot
Expand All @@ -131,12 +132,15 @@ func (s *Server) GetLightClientUpdatesByRange(w http.ResponseWriter, req *http.R
chunkLength = ssz.MarshalUint64(chunkLength, uint64(len(updateSSZ)+4))
if _, err := w.Write(chunkLength); err != nil {
httputil.HandleError(w, "Could not write chunk length: "+err.Error(), http.StatusInternalServerError)
return
}
if _, err := w.Write(updateEntry.ForkDigest[:]); err != nil {
httputil.HandleError(w, "Could not write fork digest: "+err.Error(), http.StatusInternalServerError)
return
}
if _, err := w.Write(updateSSZ); err != nil {
httputil.HandleError(w, "Could not write update SSZ: "+err.Error(), http.StatusInternalServerError)
return
}
}
} else {
Expand All @@ -145,6 +149,7 @@ func (s *Server) GetLightClientUpdatesByRange(w http.ResponseWriter, req *http.R
for _, update := range updates {
if ctx.Err() != nil {
httputil.HandleError(w, "Context error: "+ctx.Err().Error(), http.StatusInternalServerError)
return
}

updateJson, err := structs.LightClientUpdateFromConsensus(update)
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/rpc/eth/node/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func (s *Server) GetHealth(w http.ResponseWriter, r *http.Request) {
optimistic, err := s.OptimisticModeFetcher.IsOptimistic(ctx)
if err != nil {
httputil.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
return
}
if s.SyncChecker.Synced() && !optimistic {
return
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/rpc/eth/rewards/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ func (s *Server) attRewardsState(w http.ResponseWriter, r *http.Request) (state.
}
st, err := s.Stater.StateBySlot(r.Context(), nextEpochEnd)
if err != nil {
httputil.HandleError(w, "Could not get state for epoch's starting slot: "+err.Error(), http.StatusInternalServerError)
shared.WriteStateFetchError(w, err)
return nil, false
}
return st, true
Expand Down
31 changes: 15 additions & 16 deletions beacon-chain/rpc/eth/validator/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ func (s *Server) GetAttesterDuties(w http.ResponseWriter, r *http.Request) {

st, err := s.Stater.StateBySlot(ctx, startSlot)
if err != nil {
httputil.HandleError(w, "Could not get state: "+err.Error(), http.StatusInternalServerError)
shared.WriteStateFetchError(w, err)
return
}

Expand Down Expand Up @@ -1030,7 +1030,7 @@ func (s *Server) GetProposerDuties(w http.ResponseWriter, r *http.Request) {
if requestedEpoch < currentEpoch {
st, err = s.Stater.StateBySlot(ctx, epochStartSlot)
if err != nil {
httputil.HandleError(w, fmt.Sprintf("Could not get state for slot %d: %v ", epochStartSlot, err), http.StatusInternalServerError)
shared.WriteStateFetchError(w, err)
return
}
} else {
Expand Down Expand Up @@ -1103,7 +1103,8 @@ func (s *Server) GetProposerDuties(w http.ResponseWriter, r *http.Request) {
httputil.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
return
}
if !sortProposerDuties(w, duties) {
if err = sortProposerDuties(duties); err != nil {
httputil.HandleError(w, "Could not sort proposer duties: "+err.Error(), http.StatusInternalServerError)
return
}

Expand Down Expand Up @@ -1181,7 +1182,7 @@ func (s *Server) GetSyncCommitteeDuties(w http.ResponseWriter, r *http.Request)
}
st, err := s.Stater.State(ctx, []byte(strconv.FormatUint(uint64(slot), 10)))
if err != nil {
httputil.HandleError(w, "Could not get sync committee state: "+err.Error(), http.StatusInternalServerError)
shared.WriteStateFetchError(w, err)
return
}

Expand Down Expand Up @@ -1327,7 +1328,7 @@ func (s *Server) GetLiveness(w http.ResponseWriter, r *http.Request) {
}
st, err = s.Stater.StateBySlot(ctx, epochEnd)
if err != nil {
httputil.HandleError(w, "Could not get slot for requested epoch: "+err.Error(), http.StatusInternalServerError)
shared.WriteStateFetchError(w, err)
return
}
participation, err = st.CurrentEpochParticipation()
Expand Down Expand Up @@ -1447,22 +1448,20 @@ func syncCommitteeDutiesAndVals(
return duties, vals, nil
}

func sortProposerDuties(w http.ResponseWriter, duties []*structs.ProposerDuty) bool {
ok := true
func sortProposerDuties(duties []*structs.ProposerDuty) error {
var err error
sort.Slice(duties, func(i, j int) bool {
si, err := strconv.ParseUint(duties[i].Slot, 10, 64)
if err != nil {
httputil.HandleError(w, "Could not parse slot: "+err.Error(), http.StatusInternalServerError)
ok = false
si, parseErr := strconv.ParseUint(duties[i].Slot, 10, 64)
if parseErr != nil {
err = errors.Wrap(parseErr, "could not parse slot")
return false
}
sj, err := strconv.ParseUint(duties[j].Slot, 10, 64)
if err != nil {
httputil.HandleError(w, "Could not parse slot: "+err.Error(), http.StatusInternalServerError)
ok = false
sj, parseErr := strconv.ParseUint(duties[j].Slot, 10, 64)
if parseErr != nil {
err = errors.Wrap(parseErr, "could not parse slot")
return false
}
return si < sj
})
return ok
return err
}
2 changes: 1 addition & 1 deletion beacon-chain/sync/batch_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func (s *Service) validateWithKzgBatchVerifier(ctx context.Context, dataColumns

timeout := time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second

resChan := make(chan error)
resChan := make(chan error, 1)
verificationSet := &kzgVerifier{dataColumns: dataColumns, resChan: resChan}
s.kzgChan <- verificationSet

Expand Down
36 changes: 36 additions & 0 deletions beacon-chain/sync/kzg_batch_verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/testing/assert"
Expand Down Expand Up @@ -268,6 +269,41 @@ func TestKzgBatchVerifierFallback(t *testing.T) {
})
}

func TestValidateWithKzgBatchVerifier_DeadlockOnTimeout(t *testing.T) {
err := kzg.Start()
require.NoError(t, err)

params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.SecondsPerSlot = 0
params.OverrideBeaconConfig(cfg)

ctx, cancel := context.WithCancel(t.Context())
defer cancel()

service := &Service{
ctx: ctx,
kzgChan: make(chan *kzgVerifier),
}
go service.kzgVerifierRoutine()

result, err := service.validateWithKzgBatchVerifier(context.Background(), nil)
require.Equal(t, pubsub.ValidationIgnore, result)
require.ErrorIs(t, err, context.DeadlineExceeded)

done := make(chan struct{})
go func() {
_, _ = service.validateWithKzgBatchVerifier(context.Background(), nil)
close(done)
}()

select {
case <-done:
case <-time.After(500 * time.Millisecond):
t.Fatal("validateWithKzgBatchVerifier blocked")
}
}

func createValidTestDataColumns(t *testing.T, count int) []blocks.RODataColumn {
_, roSidecars, _ := util.GenerateTestFuluBlockWithSidecars(t, count)
if len(roSidecars) >= count {
Expand Down
3 changes: 3 additions & 0 deletions changelog/SashaMalysehko_fix-return-after-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Fixed

- Fix missing return after version header check in SubmitAttesterSlashingsV2.
3 changes: 3 additions & 0 deletions changelog/fix_kzg_batch_verifier_timeout_deadlock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Fix deadlock in data column gossip KZG batch verification when a caller times out preventing result delivery.
3 changes: 3 additions & 0 deletions changelog/radek_httperror-analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- Static analyzer that ensures each `httputil.HandleError` call is followed by a `return` statement.
3 changes: 3 additions & 0 deletions changelog/radek_use-statefetch-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Ignored

- Use `WriteStateFetchError` in API handlers whenever possible.
13 changes: 13 additions & 0 deletions tools/analyzers/httperror/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("@prysm//tools/go:def.bzl", "go_library")

go_library(
name = "go_default_library",
srcs = ["analyzer.go"],
importpath = "github.com/OffchainLabs/prysm/v7/tools/analyzers/httperror",
visibility = ["//visibility:public"],
deps = [
"@org_golang_x_tools//go/analysis:go_default_library",
"@org_golang_x_tools//go/analysis/passes/inspect:go_default_library",
"@org_golang_x_tools//go/ast/inspector:go_default_library",
],
)
Loading
Loading