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
31 changes: 30 additions & 1 deletion db/builder_deposit_request_txs.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,34 @@ func GetBuilderDepositTxsByDequeueRange(ctx context.Context, dequeueFirst uint64
return depositTxs
}

// GetBuilderDepositTxsUpToBlock returns all builder deposit request txs up to the given el
// block number, in queue (block number, log index) order.
func GetBuilderDepositTxsUpToBlock(ctx context.Context, maxBlockNumber uint64) []*dbtypes.BuilderDepositTx {
depositTxs := []*dbtypes.BuilderDepositTx{}

err := ReaderDb.SelectContext(ctx, &depositTxs, `SELECT builder_deposit_request_txs.*
FROM builder_deposit_request_txs
WHERE block_number <= $1
ORDER BY block_number ASC, block_index ASC
`, maxBlockNumber)
if err != nil {
logger.Errorf("Error while fetching builder deposit txs: %v", err)
return nil
}

return depositTxs
}

// UpdateBuilderDepositTxDequeueBlock updates the dequeue block of a builder deposit request tx.
func UpdateBuilderDepositTxDequeueBlock(ctx context.Context, tx *sqlx.Tx, blockRoot []byte, blockIndex uint64, dequeueBlock uint64) error {
_, err := tx.ExecContext(ctx, `UPDATE builder_deposit_request_txs
SET dequeue_block = $1
WHERE block_root = $2 AND block_index = $3
`, dequeueBlock, blockRoot, blockIndex)

return err
}

func GetBuilderDepositTxsByTxHashes(ctx context.Context, txHashes [][]byte) []*dbtypes.BuilderDepositTx {
var sql strings.Builder
args := make([]any, len(txHashes))
Expand Down Expand Up @@ -117,8 +145,9 @@ func GetBuilderDepositTxsFiltered(ctx context.Context, offset uint64, limit uint

filterOp := "WHERE"
if filter.MinDequeue > 0 {
// dequeue block 0 = queued before dequeue activation, not determinable yet - still pending
args = append(args, filter.MinDequeue)
fmt.Fprintf(&sql, " %v dequeue_block >= $%v", filterOp, len(args))
fmt.Fprintf(&sql, " %v (dequeue_block >= $%v OR dequeue_block = 0)", filterOp, len(args))
filterOp = "AND"
}
if filter.MaxDequeue > 0 {
Expand Down
31 changes: 30 additions & 1 deletion db/builder_exit_request_txs.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,34 @@ func GetBuilderExitTxsByDequeueRange(ctx context.Context, dequeueFirst uint64, d
return exitTxs
}

// GetBuilderExitTxsUpToBlock returns all builder exit request txs up to the given el block
// number, in queue (block number, log index) order.
func GetBuilderExitTxsUpToBlock(ctx context.Context, maxBlockNumber uint64) []*dbtypes.BuilderExitTx {
exitTxs := []*dbtypes.BuilderExitTx{}

err := ReaderDb.SelectContext(ctx, &exitTxs, `SELECT builder_exit_request_txs.*
FROM builder_exit_request_txs
WHERE block_number <= $1
ORDER BY block_number ASC, block_index ASC
`, maxBlockNumber)
if err != nil {
logger.Errorf("Error while fetching builder exit txs: %v", err)
return nil
}

return exitTxs
}

// UpdateBuilderExitTxDequeueBlock updates the dequeue block of a builder exit request tx.
func UpdateBuilderExitTxDequeueBlock(ctx context.Context, tx *sqlx.Tx, blockRoot []byte, blockIndex uint64, dequeueBlock uint64) error {
_, err := tx.ExecContext(ctx, `UPDATE builder_exit_request_txs
SET dequeue_block = $1
WHERE block_root = $2 AND block_index = $3
`, dequeueBlock, blockRoot, blockIndex)

return err
}

func GetBuilderExitTxsByTxHashes(ctx context.Context, txHashes [][]byte) []*dbtypes.BuilderExitTx {
var sql strings.Builder
args := make([]any, len(txHashes))
Expand Down Expand Up @@ -115,8 +143,9 @@ func GetBuilderExitTxsFiltered(ctx context.Context, offset uint64, limit uint32,

filterOp := "WHERE"
if filter.MinDequeue > 0 {
// dequeue block 0 = queued before dequeue activation, not determinable yet - still pending
args = append(args, filter.MinDequeue)
fmt.Fprintf(&sql, " %v dequeue_block >= $%v", filterOp, len(args))
fmt.Fprintf(&sql, " %v (dequeue_block >= $%v OR dequeue_block = 0)", filterOp, len(args))
filterOp = "AND"
}
if filter.MaxDequeue > 0 {
Expand Down
20 changes: 20 additions & 0 deletions db/slots.go
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,26 @@ func GetHighestRootBeforeSlot(ctx context.Context, slot uint64, withOrphaned boo
return result
}

// GetFirstCanonicalElBlockNumber returns the slot and el block number of the first canonical
// block at or after the given slot that carries an execution payload.
func GetFirstCanonicalElBlockNumber(ctx context.Context, minSlot uint64) (uint64, uint64, bool) {
result := struct {
Slot uint64 `db:"slot"`
EthBlockNumber uint64 `db:"eth_block_number"`
}{}

err := ReaderDb.GetContext(ctx, &result, `
SELECT slot, eth_block_number FROM slots
WHERE slot >= $1 AND status = 1 AND eth_block_number IS NOT NULL
ORDER BY slot ASC LIMIT 1
`, minSlot)
if err != nil {
return 0, 0, false
}

return result.Slot, result.EthBlockNumber, true
}

func GetSlotAssignment(ctx context.Context, slot uint64) uint64 {
proposer := uint64(math.MaxInt64)
err := ReaderDb.GetContext(ctx, &proposer, `
Expand Down
74 changes: 65 additions & 9 deletions handlers/builder_deposits.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"net/url"
"strconv"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethpandaops/dora/dbtypes"
Expand Down Expand Up @@ -362,6 +363,49 @@ func buildBuilderDepositsProjectionPageData(ctx context.Context, pageIdx uint64,
pageData.NewDepositEstimateTime = projection.NewDepositEstimateTime
}

// Regular builder deposits already queued in the builder deposit contract: they can be
// submitted before the fork, but stay locked in the queue until dequeuing starts with the
// first Gloas payload, so no builder index is assigned yet (builders onboarded at the fork
// transition are registered first). Listed before the projected onboarding deposits, like
// pending request txs on the post-fork page.
pageOffset := (pageIdx - 1) * pageSize
queuedFilter := &dbtypes.BuilderDepositTxFilter{
PublicKey: common.FromHex(pubkey),
}
if minAmount != 0 {
queuedFilter.MinAmount = &minAmount
}
if maxAmount != 0 {
queuedFilter.MaxAmount = &maxAmount
}

queuedTxs, totalTxRows := services.GlobalBeaconService.GetQueuedBuilderDepositTxs(ctx, queuedFilter, pageOffset, uint32(pageSize))
pageData.QueuedRegularCount = totalTxRows

queuedRows := make([]*models.BuilderDepositsPageDataDeposit, 0, len(queuedTxs))
for _, queuedTx := range queuedTxs {
depositTx := queuedTx.Transaction
queuedRows = append(queuedRows, &models.BuilderDepositsPageDataDeposit{
IsQueuedRegular: true,
Time: time.Unix(int64(depositTx.BlockTime), 0),
PublicKey: depositTx.PublicKey,
WithdrawalCredentials: depositTx.WithdrawalCredentials,
Amount: depositTx.Amount,
BlockNumber: depositTx.BlockNumber,
HasTransaction: true,
TransactionHash: depositTx.TxHash,
TransactionOrphaned: queuedTx.TransactionOrphaned,
TransactionDetails: &models.BuilderPageDataDepositTxDetails{
BlockNumber: depositTx.BlockNumber,
BlockHash: fmt.Sprintf("%#x", depositTx.BlockRoot),
BlockTime: depositTx.BlockTime,
TxOrigin: common.Address(depositTx.TxSender).Hex(),
TxTarget: common.Address(depositTx.TxTarget).Hex(),
TxHash: fmt.Sprintf("%#x", depositTx.TxHash),
},
})
}

// Map and filter the projected deposits (slot / pubkey / amount; builder-index filter ignored).
pubkeyFilter := common.FromHex(pubkey)
matched := make([]*models.BuilderDepositsPageDataDeposit, 0)
Expand Down Expand Up @@ -441,16 +485,28 @@ func buildBuilderDepositsProjectionPageData(ctx context.Context, pageIdx uint64,
}
}

totalRows := uint64(len(matched))
start := (pageIdx - 1) * pageSize
end := start + pageSize
if start > totalRows {
start = totalRows
}
if end > totalRows {
end = totalRows
// combined pagination: queued request txs first, then the projected onboarding deposits
totalRows := totalTxRows + uint64(len(matched))

deposits := queuedRows
if uint64(len(deposits)) < pageSize {
matchedOffset := uint64(0)
if pageOffset > totalTxRows {
matchedOffset = pageOffset - totalTxRows
}
if matchedOffset > uint64(len(matched)) {
matchedOffset = uint64(len(matched))
}

matchedEnd := matchedOffset + pageSize - uint64(len(deposits))
if matchedEnd > uint64(len(matched)) {
matchedEnd = uint64(len(matched))
}

deposits = append(deposits, matched[matchedOffset:matchedEnd]...)
}
pageData.Deposits = matched[start:end]

pageData.Deposits = deposits
pageData.DepositCount = uint64(len(pageData.Deposits))

if pageData.DepositCount > 0 {
Expand Down
47 changes: 43 additions & 4 deletions indexer/execution/system_contracts/builder_deposit_indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/jmoiron/sqlx"
"github.com/sirupsen/logrus"

"github.com/ethpandaops/dora/clients/consensus"
"github.com/ethpandaops/dora/clients/execution/rpc"
"github.com/ethpandaops/dora/db"
"github.com/ethpandaops/dora/dbtypes"
Expand All @@ -21,10 +22,11 @@ import (

// BuilderDepositIndexer indexes the EIP-8282 builder deposit system contract.
type BuilderDepositIndexer struct {
indexerCtx *execution.IndexerCtx
logger logrus.FieldLogger
indexer *contractIndexer[dbtypes.BuilderDepositTx]
matcher *transactionMatcher[builderDepositMatch]
indexerCtx *execution.IndexerCtx
logger logrus.FieldLogger
indexer *contractIndexer[dbtypes.BuilderDepositTx]
matcher *transactionMatcher[builderDepositMatch]
activationResolver *forkActivationResolver
}

type builderDepositMatch struct {
Expand All @@ -43,6 +45,9 @@ func NewBuilderDepositIndexer(indexer *execution.IndexerCtx) *BuilderDepositInde
bi := &BuilderDepositIndexer{
indexerCtx: indexer,
logger: indexer.Logger.WithField("indexer", "builder_deposits"),
activationResolver: newForkActivationResolver(indexer, func(specs *consensus.ChainSpec) *uint64 {
return specs.GloasForkEpoch
}),
}

specs := indexer.ChainState.GetSpecs()
Expand All @@ -59,6 +64,10 @@ func NewBuilderDepositIndexer(indexer *execution.IndexerCtx) *BuilderDepositInde
deployBlock: uint64(utils.Config.ExecutionApi.GloasDeployBlock),
dequeueRate: specs.MaxBuilderDepositRequestsPerPayload,

queueActivationBlock: bi.activationResolver.resolveActivationBlock,
loadRebaseRows: bi.loadRebaseRows,
persistRebaseRows: bi.persistRebaseRows,

processFinalTx: bi.processFinalTx,
processRecentTx: bi.processRecentTx,
persistTxs: bi.persistBuilderDepositTxs,
Expand Down Expand Up @@ -180,6 +189,36 @@ func (bi *BuilderDepositIndexer) parseRequestLog(log *types.Log) *dbtypes.Builde
return requestTx
}

// loadRebaseRows loads persisted builder deposit request txs for the one-time dequeue rebase.
func (bi *BuilderDepositIndexer) loadRebaseRows(maxBlockNumber uint64) []*dequeueRebaseRow {
depositTxs := db.GetBuilderDepositTxsUpToBlock(bi.indexerCtx.Ctx, maxBlockNumber)

rows := make([]*dequeueRebaseRow, len(depositTxs))
for idx, depositTx := range depositTxs {
rows[idx] = &dequeueRebaseRow{
blockRoot: depositTx.BlockRoot,
blockNumber: depositTx.BlockNumber,
blockIndex: depositTx.BlockIndex,
forkId: depositTx.ForkId,
dequeueBlock: depositTx.DequeueBlock,
}
}

return rows
}

// persistRebaseRows persists rebased dequeue blocks of builder deposit request txs.
func (bi *BuilderDepositIndexer) persistRebaseRows(tx *sqlx.Tx, rows []*dequeueRebaseRow) error {
for _, row := range rows {
err := db.UpdateBuilderDepositTxDequeueBlock(bi.indexerCtx.Ctx, tx, row.blockRoot, row.blockIndex, row.dequeueBlock)
if err != nil {
return fmt.Errorf("error while updating builder deposit tx dequeue block: %w", err)
}
}

return nil
}

// persistBuilderDepositTxs persists builder deposit request txs to the database.
func (bi *BuilderDepositIndexer) persistBuilderDepositTxs(tx *sqlx.Tx, requests []*dbtypes.BuilderDepositTx) error {
requestCount := len(requests)
Expand Down
47 changes: 43 additions & 4 deletions indexer/execution/system_contracts/builder_exit_indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/jmoiron/sqlx"
"github.com/sirupsen/logrus"

"github.com/ethpandaops/dora/clients/consensus"
"github.com/ethpandaops/dora/clients/execution/rpc"
"github.com/ethpandaops/dora/db"
"github.com/ethpandaops/dora/dbtypes"
Expand All @@ -20,10 +21,11 @@ import (

// BuilderExitIndexer indexes the EIP-8282 builder exit system contract.
type BuilderExitIndexer struct {
indexerCtx *execution.IndexerCtx
logger logrus.FieldLogger
indexer *contractIndexer[dbtypes.BuilderExitTx]
matcher *transactionMatcher[builderExitMatch]
indexerCtx *execution.IndexerCtx
logger logrus.FieldLogger
indexer *contractIndexer[dbtypes.BuilderExitTx]
matcher *transactionMatcher[builderExitMatch]
activationResolver *forkActivationResolver
}

type builderExitMatch struct {
Expand All @@ -42,6 +44,9 @@ func NewBuilderExitIndexer(indexer *execution.IndexerCtx) *BuilderExitIndexer {
bi := &BuilderExitIndexer{
indexerCtx: indexer,
logger: indexer.Logger.WithField("indexer", "builder_exits"),
activationResolver: newForkActivationResolver(indexer, func(specs *consensus.ChainSpec) *uint64 {
return specs.GloasForkEpoch
}),
}

specs := indexer.ChainState.GetSpecs()
Expand All @@ -58,6 +63,10 @@ func NewBuilderExitIndexer(indexer *execution.IndexerCtx) *BuilderExitIndexer {
deployBlock: uint64(utils.Config.ExecutionApi.GloasDeployBlock),
dequeueRate: specs.MaxBuilderExitRequestsPerPayload,

queueActivationBlock: bi.activationResolver.resolveActivationBlock,
loadRebaseRows: bi.loadRebaseRows,
persistRebaseRows: bi.persistRebaseRows,

processFinalTx: bi.processFinalTx,
processRecentTx: bi.processRecentTx,
persistTxs: bi.persistBuilderExitTxs,
Expand Down Expand Up @@ -173,6 +182,36 @@ func (bi *BuilderExitIndexer) parseRequestLog(log *types.Log) *dbtypes.BuilderEx
return requestTx
}

// loadRebaseRows loads persisted builder exit request txs for the one-time dequeue rebase.
func (bi *BuilderExitIndexer) loadRebaseRows(maxBlockNumber uint64) []*dequeueRebaseRow {
exitTxs := db.GetBuilderExitTxsUpToBlock(bi.indexerCtx.Ctx, maxBlockNumber)

rows := make([]*dequeueRebaseRow, len(exitTxs))
for idx, exitTx := range exitTxs {
rows[idx] = &dequeueRebaseRow{
blockRoot: exitTx.BlockRoot,
blockNumber: exitTx.BlockNumber,
blockIndex: exitTx.BlockIndex,
forkId: exitTx.ForkId,
dequeueBlock: exitTx.DequeueBlock,
}
}

return rows
}

// persistRebaseRows persists rebased dequeue blocks of builder exit request txs.
func (bi *BuilderExitIndexer) persistRebaseRows(tx *sqlx.Tx, rows []*dequeueRebaseRow) error {
for _, row := range rows {
err := db.UpdateBuilderExitTxDequeueBlock(bi.indexerCtx.Ctx, tx, row.blockRoot, row.blockIndex, row.dequeueBlock)
if err != nil {
return fmt.Errorf("error while updating builder exit tx dequeue block: %w", err)
}
}

return nil
}

// persistBuilderExitTxs persists builder exit request txs to the database.
func (bi *BuilderExitIndexer) persistBuilderExitTxs(tx *sqlx.Tx, requests []*dbtypes.BuilderExitTx) error {
requestCount := len(requests)
Expand Down
Loading