Skip to content

Commit 9417ed7

Browse files
authored
Merge branch 'master' into dependabot/github_actions/actions-e0d84faf3a
2 parents 2253110 + 955130f commit 9417ed7

16 files changed

Lines changed: 611 additions & 97 deletions

File tree

db/block_bids.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,25 @@ func InsertBids(bids []*dbtypes.BlockBid, tx *sqlx.Tx) error {
6464
return nil
6565
}
6666

67-
func GetBidsForBlockRoot(ctx context.Context, blockRoot []byte, slot uint64) []*dbtypes.BlockBid {
67+
// GetBidsForSlot returns all bids for a slot regardless of their parent root,
68+
// so bids targeting other forks or deeper ancestors (reorg bids) are included.
69+
func GetBidsForSlot(ctx context.Context, slot uint64) []*dbtypes.BlockBid {
6870
var sql strings.Builder
6971
args := []any{
70-
blockRoot,
7172
slot,
7273
}
7374
fmt.Fprint(&sql, `
7475
SELECT
7576
parent_root, parent_hash, block_hash, fee_recipient, gas_limit, builder_index, slot, value, el_payment
7677
FROM block_bids
77-
WHERE parent_root = $1 AND slot = $2
78+
WHERE slot = $1
7879
ORDER BY value DESC
7980
`)
8081

8182
bids := []*dbtypes.BlockBid{}
8283
err := ReaderDb.SelectContext(ctx, &bids, sql.String(), args...)
8384
if err != nil {
84-
logger.Errorf("Error while fetching bids for block root: %v", err)
85+
logger.Errorf("Error while fetching bids for slot: %v", err)
8586
return nil
8687
}
8788
return bids

docs/docs.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1256,7 +1256,7 @@ const docTemplate = `{
12561256
},
12571257
"/v1/slot/{slotOrHash}/bids": {
12581258
"get": {
1259-
"description": "Returns the execution payload bids submitted for a slot's parent root (ePBS, gloas+).",
1259+
"description": "Returns all execution payload bids submitted for a slot (ePBS, gloas+), including bids targeting other forks or deeper ancestors (reorg bids). Each bid is classified against the slot's actual block parent chain.",
12601260
"produces": [
12611261
"application/json"
12621262
],
@@ -4225,6 +4225,18 @@ const docTemplate = `{
42254225
"builder_name": {
42264226
"type": "string"
42274227
},
4228+
"candidate_key": {
4229+
"description": "CandidateKey is the buildoor-style candidate identifier (parent_full, parent_empty,\ngrandparent_full, grandparent_empty, ancestor-N_full/-empty); empty for orphaned-fork\nand unknown-parent bids.",
4230+
"type": "string"
4231+
},
4232+
"el_parent_slot": {
4233+
"description": "ElParentSlot is the slot whose payload block hash matches the bid's parent hash,\ni.e. the EL head the bid builds on (omitted if not resolvable).",
4234+
"type": "integer"
4235+
},
4236+
"el_parent_unrevealed": {
4237+
"description": "ElParentUnrevealed: the bid's parent hash matches a committed payload hash that was\nnever revealed - such a bid can never become valid.",
4238+
"type": "boolean"
4239+
},
42284240
"el_payment": {
42294241
"type": "integer"
42304242
},
@@ -4240,12 +4252,27 @@ const docTemplate = `{
42404252
"is_winning": {
42414253
"type": "boolean"
42424254
},
4255+
"parent_class": {
4256+
"description": "Bid target classification relative to the slot's actual block parent chain.\nParentClass: \"parent\" (targets the block's actual beacon parent), \"reorg\" (targets a\ndeeper ancestor, orphaning ReorgDepth blocks), \"orphaned\" (targets a block outside\nthe block's chain), \"unknown\" (parent root not resolvable).",
4257+
"type": "string"
4258+
},
4259+
"parent_full": {
4260+
"description": "ParentFull: the bid builds on the targeted block's own payload (full) rather than an\nearlier payload (empty).",
4261+
"type": "boolean"
4262+
},
42434263
"parent_hash": {
42444264
"type": "string"
42454265
},
42464266
"parent_root": {
42474267
"type": "string"
42484268
},
4269+
"parent_slot": {
4270+
"description": "ParentSlot is the slot of the targeted beacon parent block (0 if unknown).",
4271+
"type": "integer"
4272+
},
4273+
"reorg_depth": {
4274+
"type": "integer"
4275+
},
42494276
"slot": {
42504277
"type": "integer"
42514278
},

docs/swagger.json

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1253,7 +1253,7 @@
12531253
},
12541254
"/v1/slot/{slotOrHash}/bids": {
12551255
"get": {
1256-
"description": "Returns the execution payload bids submitted for a slot's parent root (ePBS, gloas+).",
1256+
"description": "Returns all execution payload bids submitted for a slot (ePBS, gloas+), including bids targeting other forks or deeper ancestors (reorg bids). Each bid is classified against the slot's actual block parent chain.",
12571257
"produces": [
12581258
"application/json"
12591259
],
@@ -4222,6 +4222,18 @@
42224222
"builder_name": {
42234223
"type": "string"
42244224
},
4225+
"candidate_key": {
4226+
"description": "CandidateKey is the buildoor-style candidate identifier (parent_full, parent_empty,\ngrandparent_full, grandparent_empty, ancestor-N_full/-empty); empty for orphaned-fork\nand unknown-parent bids.",
4227+
"type": "string"
4228+
},
4229+
"el_parent_slot": {
4230+
"description": "ElParentSlot is the slot whose payload block hash matches the bid's parent hash,\ni.e. the EL head the bid builds on (omitted if not resolvable).",
4231+
"type": "integer"
4232+
},
4233+
"el_parent_unrevealed": {
4234+
"description": "ElParentUnrevealed: the bid's parent hash matches a committed payload hash that was\nnever revealed - such a bid can never become valid.",
4235+
"type": "boolean"
4236+
},
42254237
"el_payment": {
42264238
"type": "integer"
42274239
},
@@ -4237,12 +4249,27 @@
42374249
"is_winning": {
42384250
"type": "boolean"
42394251
},
4252+
"parent_class": {
4253+
"description": "Bid target classification relative to the slot's actual block parent chain.\nParentClass: \"parent\" (targets the block's actual beacon parent), \"reorg\" (targets a\ndeeper ancestor, orphaning ReorgDepth blocks), \"orphaned\" (targets a block outside\nthe block's chain), \"unknown\" (parent root not resolvable).",
4254+
"type": "string"
4255+
},
4256+
"parent_full": {
4257+
"description": "ParentFull: the bid builds on the targeted block's own payload (full) rather than an\nearlier payload (empty).",
4258+
"type": "boolean"
4259+
},
42404260
"parent_hash": {
42414261
"type": "string"
42424262
},
42434263
"parent_root": {
42444264
"type": "string"
42454265
},
4266+
"parent_slot": {
4267+
"description": "ParentSlot is the slot of the targeted beacon parent block (0 if unknown).",
4268+
"type": "integer"
4269+
},
4270+
"reorg_depth": {
4271+
"type": "integer"
4272+
},
42464273
"slot": {
42474274
"type": "integer"
42484275
},

docs/swagger.yaml

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,22 @@ definitions:
11411141
type: integer
11421142
builder_name:
11431143
type: string
1144+
candidate_key:
1145+
description: |-
1146+
CandidateKey is the buildoor-style candidate identifier (parent_full, parent_empty,
1147+
grandparent_full, grandparent_empty, ancestor-N_full/-empty); empty for orphaned-fork
1148+
and unknown-parent bids.
1149+
type: string
1150+
el_parent_slot:
1151+
description: |-
1152+
ElParentSlot is the slot whose payload block hash matches the bid's parent hash,
1153+
i.e. the EL head the bid builds on (omitted if not resolvable).
1154+
type: integer
1155+
el_parent_unrevealed:
1156+
description: |-
1157+
ElParentUnrevealed: the bid's parent hash matches a committed payload hash that was
1158+
never revealed - such a bid can never become valid.
1159+
type: boolean
11441160
el_payment:
11451161
type: integer
11461162
fee_recipient:
@@ -1151,10 +1167,28 @@ definitions:
11511167
type: boolean
11521168
is_winning:
11531169
type: boolean
1170+
parent_class:
1171+
description: |-
1172+
Bid target classification relative to the slot's actual block parent chain.
1173+
ParentClass: "parent" (targets the block's actual beacon parent), "reorg" (targets a
1174+
deeper ancestor, orphaning ReorgDepth blocks), "orphaned" (targets a block outside
1175+
the block's chain), "unknown" (parent root not resolvable).
1176+
type: string
1177+
parent_full:
1178+
description: |-
1179+
ParentFull: the bid builds on the targeted block's own payload (full) rather than an
1180+
earlier payload (empty).
1181+
type: boolean
11541182
parent_hash:
11551183
type: string
11561184
parent_root:
11571185
type: string
1186+
parent_slot:
1187+
description: ParentSlot is the slot of the targeted beacon parent block (0
1188+
if unknown).
1189+
type: integer
1190+
reorg_depth:
1191+
type: integer
11581192
slot:
11591193
type: integer
11601194
total_value:
@@ -2839,8 +2873,9 @@ paths:
28392873
- Slot
28402874
/v1/slot/{slotOrHash}/bids:
28412875
get:
2842-
description: Returns the execution payload bids submitted for a slot's parent
2843-
root (ePBS, gloas+).
2876+
description: Returns all execution payload bids submitted for a slot (ePBS,
2877+
gloas+), including bids targeting other forks or deeper ancestors (reorg bids).
2878+
Each bid is classified against the slot's actual block parent chain.
28442879
operationId: getSlotBids
28452880
parameters:
28462881
- description: Slot number or block root (0x-prefixed hex)

handlers/api/slot_bids_v1.go

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,33 @@ type APISlotBid struct {
4242
ElPayment uint64 `json:"el_payment"`
4343
TotalValue uint64 `json:"total_value"`
4444
IsWinning bool `json:"is_winning"`
45+
46+
// Bid target classification relative to the slot's actual block parent chain.
47+
// ParentClass: "parent" (targets the block's actual beacon parent), "reorg" (targets a
48+
// deeper ancestor, orphaning ReorgDepth blocks), "orphaned" (targets a block outside
49+
// the block's chain), "unknown" (parent root not resolvable).
50+
ParentClass string `json:"parent_class"`
51+
// ParentFull: the bid builds on the targeted block's own payload (full) rather than an
52+
// earlier payload (empty).
53+
ParentFull bool `json:"parent_full"`
54+
ReorgDepth uint64 `json:"reorg_depth,omitempty"`
55+
// ParentSlot is the slot of the targeted beacon parent block (0 if unknown).
56+
ParentSlot uint64 `json:"parent_slot,omitempty"`
57+
// ElParentSlot is the slot whose payload block hash matches the bid's parent hash,
58+
// i.e. the EL head the bid builds on (omitted if not resolvable).
59+
ElParentSlot *uint64 `json:"el_parent_slot,omitempty"`
60+
// ElParentUnrevealed: the bid's parent hash matches a committed payload hash that was
61+
// never revealed - such a bid can never become valid.
62+
ElParentUnrevealed bool `json:"el_parent_unrevealed,omitempty"`
63+
// CandidateKey is the buildoor-style candidate identifier (parent_full, parent_empty,
64+
// grandparent_full, grandparent_empty, ancestor-N_full/-empty); empty for orphaned-fork
65+
// and unknown-parent bids.
66+
CandidateKey string `json:"candidate_key,omitempty"`
4567
}
4668

4769
// APISlotBidsV1 returns all execution payload bids submitted for a slot (ePBS, EIP-7732)
4870
// @Summary Get execution payload bids for a slot
49-
// @Description Returns the execution payload bids submitted for a slot's parent root (ePBS, gloas+).
71+
// @Description Returns all execution payload bids submitted for a slot (ePBS, gloas+), including bids targeting other forks or deeper ancestors (reorg bids). Each bid is classified against the slot's actual block parent chain.
5072
// @Tags Slot
5173
// @Produce json
5274
// @Param slotOrHash path string true "Slot number or block root (0x-prefixed hex)"
@@ -65,14 +87,18 @@ func APISlotBidsV1(w http.ResponseWriter, r *http.Request) {
6587
return
6688
}
6789

68-
indexer := services.GlobalBeaconService.GetBeaconIndexer()
6990
var parentRoot phase0.Root
7091
copy(parentRoot[:], dbSlot.ParentRoot)
71-
bids := indexer.GetBlockBids(parentRoot, phase0.Slot(dbSlot.Slot))
92+
bids := services.GlobalBeaconService.GetSlotBidsClassified(r.Context(), phase0.Slot(dbSlot.Slot), parentRoot)
7293

7394
apiBids := make([]*APISlotBid, 0, len(bids))
7495
for _, bid := range bids {
75-
isWinning := len(dbSlot.EthBlockHash) > 0 && bytes.Equal(bid.BlockHash, dbSlot.EthBlockHash)
96+
// The winner is the exact bid committed in the block: several builders may bid the
97+
// same block hash, so the builder index and parent root must match too.
98+
isWinning := len(dbSlot.EthBlockHash) > 0 &&
99+
bytes.Equal(bid.BlockHash, dbSlot.EthBlockHash) &&
100+
bid.BuilderIndex == dbSlot.BuilderIndex &&
101+
bytes.Equal(bid.ParentRoot, dbSlot.ParentRoot)
76102

77103
// Dora's bid indexer casts the on-chain uint64 BuilderIndex to int64 and
78104
// uses -1 (== MaxUint64 reinterpreted) as a "self-built" sentinel. Surface
@@ -84,24 +110,43 @@ func APISlotBidsV1(w http.ResponseWriter, r *http.Request) {
84110
builderName = services.GlobalBeaconService.GetValidatorName(builderIndexU | services.BuilderIndexFlag)
85111
}
86112

87-
apiBids = append(apiBids, &APISlotBid{
88-
ParentRoot: fmt.Sprintf("0x%x", bid.ParentRoot),
89-
ParentHash: fmt.Sprintf("0x%x", bid.ParentHash),
90-
BlockHash: fmt.Sprintf("0x%x", bid.BlockHash),
91-
FeeRecipient: fmt.Sprintf("0x%x", bid.FeeRecipient),
92-
GasLimit: bid.GasLimit,
93-
BuilderIndex: builderIndexU,
94-
BuilderName: builderName,
95-
IsSelfBuilt: isSelfBuilt,
96-
Slot: bid.Slot,
97-
Value: bid.Value,
98-
ElPayment: bid.ElPayment,
99-
TotalValue: bid.Value + bid.ElPayment,
100-
IsWinning: isWinning,
101-
})
113+
apiBid := &APISlotBid{
114+
ParentRoot: fmt.Sprintf("0x%x", bid.ParentRoot),
115+
ParentHash: fmt.Sprintf("0x%x", bid.ParentHash),
116+
BlockHash: fmt.Sprintf("0x%x", bid.BlockHash),
117+
FeeRecipient: fmt.Sprintf("0x%x", bid.FeeRecipient),
118+
GasLimit: bid.GasLimit,
119+
BuilderIndex: builderIndexU,
120+
BuilderName: builderName,
121+
IsSelfBuilt: isSelfBuilt,
122+
Slot: bid.Slot,
123+
Value: bid.Value,
124+
ElPayment: bid.ElPayment,
125+
TotalValue: bid.Value + bid.ElPayment,
126+
IsWinning: isWinning,
127+
ParentClass: bid.ParentClass.String(),
128+
ParentFull: bid.ParentFull,
129+
ReorgDepth: bid.ReorgDepth,
130+
ParentSlot: bid.ParentSlot,
131+
ElParentUnrevealed: bid.ElParentUnrevealed,
132+
CandidateKey: bidCandidateKey(bid),
133+
}
134+
if bid.ElParentKnown {
135+
elParentSlot := bid.ElParentSlot
136+
apiBid.ElParentSlot = &elParentSlot
137+
}
138+
139+
apiBids = append(apiBids, apiBid)
102140
}
103141

142+
// Bids for the block's actual parent root first, then bids targeting other parents
143+
// (reorg/fork bids); within each group sorted by total value descending.
104144
sort.SliceStable(apiBids, func(i, j int) bool {
145+
iParent := apiBids[i].ParentClass == "parent"
146+
jParent := apiBids[j].ParentClass == "parent"
147+
if iParent != jParent {
148+
return iParent
149+
}
105150
return apiBids[i].TotalValue > apiBids[j].TotalValue
106151
})
107152

@@ -120,3 +165,26 @@ func APISlotBidsV1(w http.ResponseWriter, r *http.Request) {
120165
http.Error(w, `{"status": "ERROR: failed to encode response"}`, http.StatusInternalServerError)
121166
}
122167
}
168+
169+
// bidCandidateKey maps a classified bid onto the candidate keys used by build tooling
170+
// (parent_full, parent_empty, grandparent_full, grandparent_empty, ancestor-N_full/-empty).
171+
func bidCandidateKey(bid *services.ClassifiedBlockBid) string {
172+
var position string
173+
switch bid.ParentClass {
174+
case services.BidParentClassParent:
175+
position = "parent"
176+
case services.BidParentClassReorg:
177+
if bid.ReorgDepth == 1 {
178+
position = "grandparent"
179+
} else {
180+
position = fmt.Sprintf("ancestor-%v", bid.ReorgDepth+1)
181+
}
182+
default:
183+
return ""
184+
}
185+
186+
if bid.ParentFull {
187+
return position + "_full"
188+
}
189+
return position + "_empty"
190+
}

0 commit comments

Comments
 (0)