Skip to content

Commit ca53477

Browse files
committed
queuedWithdrawalSlashingAdjustments
1 parent fe4de17 commit ca53477

File tree

11 files changed

+280
-50
lines changed

11 files changed

+280
-50
lines changed

cmd/database.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ var runDatabaseCmd = &cobra.Command{
114114
l.Sugar().Fatalw("Failed to load meta state models", zap.Error(err))
115115
}
116116

117-
precommitProcessors.LoadPrecommitProcessors(sm, grm, l)
117+
precommitProcessors.LoadPrecommitProcessors(sm, grm, l, cfg)
118118

119119
fetchr := fetcher.NewFetcher(client, &fetcher.FetcherConfig{UseGetBlockReceipts: cfg.EthereumRpcConfig.UseGetBlockReceipts}, contractStore, l)
120120

cmd/debugger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ var runDebuggerCmd = &cobra.Command{
116116
l.Sugar().Fatalw("Failed to load meta state models", zap.Error(err))
117117
}
118118

119-
precommitProcessors.LoadPrecommitProcessors(sm, grm, l)
119+
precommitProcessors.LoadPrecommitProcessors(sm, grm, l, cfg)
120120

121121
fetchr := fetcher.NewFetcher(client, &fetcher.FetcherConfig{UseGetBlockReceipts: cfg.EthereumRpcConfig.UseGetBlockReceipts}, contractStore, l)
122122

cmd/debugger/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func main() {
114114
l.Sugar().Fatalw("Failed to load meta state models", zap.Error(err))
115115
}
116116

117-
precommitProcessors.LoadPrecommitProcessors(sm, grm, l)
117+
precommitProcessors.LoadPrecommitProcessors(sm, grm, l, cfg)
118118

119119
fetchr := fetcher.NewFetcher(client, &fetcher.FetcherConfig{UseGetBlockReceipts: cfg.EthereumRpcConfig.UseGetBlockReceipts}, contractStore, l)
120120

cmd/operatorRestakedStrategies.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ var runOperatorRestakedStrategiesCmd = &cobra.Command{
8686
l.Sugar().Fatalw("Failed to load eigen state models", zap.Error(err))
8787
}
8888

89-
precommitProcessors.LoadPrecommitProcessors(sm, grm, l)
89+
precommitProcessors.LoadPrecommitProcessors(sm, grm, l, cfg)
9090

9191
fetchr := fetcher.NewFetcher(client, &fetcher.FetcherConfig{UseGetBlockReceipts: cfg.EthereumRpcConfig.UseGetBlockReceipts}, contractStore, l)
9292

cmd/rpc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ var rpcCmd = &cobra.Command{
119119
l.Sugar().Fatalw("Failed to load eigen state models", zap.Error(err))
120120
}
121121

122-
precommitProcessors.LoadPrecommitProcessors(sm, grm, l)
122+
precommitProcessors.LoadPrecommitProcessors(sm, grm, l, cfg)
123123

124124
sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg)
125125

cmd/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ var runCmd = &cobra.Command{
150150
l.Sugar().Fatalw("Failed to load meta state models", zap.Error(err))
151151
}
152152

153-
precommitProcessors.LoadPrecommitProcessors(sm, grm, l)
153+
precommitProcessors.LoadPrecommitProcessors(sm, grm, l, cfg)
154154

155155
fetchr := fetcher.NewFetcher(client, &fetcher.FetcherConfig{UseGetBlockReceipts: cfg.EthereumRpcConfig.UseGetBlockReceipts}, contractStore, l)
156156

pkg/eigenState/precommitProcessors/slashingProcessor/slashing.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,171 @@ func (sp *SlashingProcessor) Process(blockNumber uint64, models map[string]types
8080
precommitDelegations = append(precommitDelegations, delegation)
8181
}
8282
stakerSharesModel.PrecommitDelegatedStakers[blockNumber] = precommitDelegations
83+
84+
// Also handle slashing adjustments for queued withdrawals
85+
err := sp.processQueuedWithdrawalSlashing(blockNumber, models)
86+
if err != nil {
87+
sp.logger.Sugar().Errorw("Failed to process queued withdrawal slashing",
88+
zap.Error(err),
89+
zap.Uint64("blockNumber", blockNumber),
90+
)
91+
return err
92+
}
93+
94+
return nil
95+
}
96+
97+
// SlashingEvent represents a slashing event from the database
98+
type SlashingEvent struct {
99+
Operator string
100+
Strategy string
101+
WadSlashed string
102+
TransactionHash string
103+
LogIndex uint64
104+
}
105+
106+
// processQueuedWithdrawalSlashing creates adjustment records for queued withdrawals
107+
// when an operator is slashed, so that the effective withdrawal amount is reduced.
108+
func (sp *SlashingProcessor) processQueuedWithdrawalSlashing(blockNumber uint64, models map[string]types.IEigenStateModel) error {
109+
// Query slashed_operator_shares table directly for this block's slashing events
110+
var slashingEvents []SlashingEvent
111+
err := sp.grm.Table("slashed_operator_shares").
112+
Where("block_number = ?", blockNumber).
113+
Find(&slashingEvents).Error
114+
115+
if err != nil {
116+
sp.logger.Sugar().Errorw("Failed to query slashing events",
117+
zap.Error(err),
118+
zap.Uint64("blockNumber", blockNumber),
119+
)
120+
return err
121+
}
122+
123+
if len(slashingEvents) == 0 {
124+
sp.logger.Sugar().Debug("No slashing events found for block number", zap.Uint64("blockNumber", blockNumber))
125+
return nil
126+
}
127+
128+
// For each slashing event, find active queued withdrawals and create adjustment records
129+
for i := range slashingEvents {
130+
slashEvent := &slashingEvents[i]
131+
err := sp.createSlashingAdjustments(slashEvent, blockNumber)
132+
if err != nil {
133+
sp.logger.Sugar().Errorw("Failed to create slashing adjustments",
134+
zap.Error(err),
135+
zap.Uint64("blockNumber", blockNumber),
136+
zap.String("operator", slashEvent.Operator),
137+
zap.String("strategy", slashEvent.Strategy),
138+
)
139+
return err
140+
}
141+
}
142+
143+
return nil
144+
}
145+
146+
func (sp *SlashingProcessor) createSlashingAdjustments(slashEvent *SlashingEvent, blockNumber uint64) error {
147+
// Find all active queued withdrawals for this operator/strategy
148+
query := `
149+
SELECT
150+
qsw.staker,
151+
qsw.strategy,
152+
qsw.operator,
153+
qsw.block_number as withdrawal_block_number,
154+
? as slash_block_number,
155+
-- Calculate cumulative slash multiplier: previous multipliers * (1 - current_slash)
156+
COALESCE(
157+
(SELECT slash_multiplier
158+
FROM queued_withdrawal_slashing_adjustments adj
159+
WHERE adj.staker = qsw.staker
160+
AND adj.strategy = qsw.strategy
161+
AND adj.operator = qsw.operator
162+
AND adj.withdrawal_block_number = qsw.block_number
163+
ORDER BY adj.slash_block_number DESC
164+
LIMIT 1),
165+
1
166+
) * (1 - LEAST(? / 1e18, 0)) as slash_multiplier,
167+
? as block_number,
168+
? as transaction_hash,
169+
? as log_index
170+
FROM queued_slashing_withdrawals qsw
171+
INNER JOIN blocks b_queued ON qsw.block_number = b_queued.number
172+
WHERE qsw.operator = ?
173+
AND qsw.strategy = ?
174+
-- Withdrawal was queued before this slash
175+
AND qsw.block_number < ?
176+
-- Still within 14-day window (not yet completable)
177+
AND DATE(b_queued.block_time) + INTERVAL '14 days' > (
178+
SELECT block_time FROM blocks WHERE number = ?
179+
)
180+
-- Backwards compatibility: only process records with valid data
181+
AND qsw.staker IS NOT NULL
182+
AND qsw.strategy IS NOT NULL
183+
AND qsw.operator IS NOT NULL
184+
AND qsw.shares_to_withdraw IS NOT NULL
185+
AND b_queued.block_time IS NOT NULL
186+
`
187+
188+
type AdjustmentRecord struct {
189+
Staker string
190+
Strategy string
191+
Operator string
192+
WithdrawalBlockNumber uint64
193+
SlashBlockNumber uint64
194+
SlashMultiplier string
195+
BlockNumber uint64
196+
TransactionHash string
197+
LogIndex uint64
198+
}
199+
200+
var adjustments []AdjustmentRecord
201+
err := sp.grm.Raw(query,
202+
blockNumber, // slash_block_number
203+
slashEvent.WadSlashed, // slash percentage for calculation
204+
blockNumber, // block_number for record
205+
slashEvent.TransactionHash, // transaction_hash for record
206+
slashEvent.LogIndex, // log_index for record
207+
slashEvent.Operator, // operator filter
208+
slashEvent.Strategy, // strategy filter
209+
blockNumber, // queued before slash
210+
blockNumber, // current block for 14-day check
211+
).Scan(&adjustments).Error
212+
213+
if err != nil {
214+
return fmt.Errorf("failed to find active withdrawals for slashing: %w", err)
215+
}
216+
217+
if len(adjustments) == 0 {
218+
sp.logger.Sugar().Debugw("No active queued withdrawals found for slashing event",
219+
zap.String("operator", slashEvent.Operator),
220+
zap.String("strategy", slashEvent.Strategy),
221+
zap.Uint64("blockNumber", blockNumber),
222+
)
223+
return nil
224+
}
225+
226+
// Insert adjustment records
227+
for _, adj := range adjustments {
228+
err := sp.grm.Table("queued_withdrawal_slashing_adjustments").Create(&adj).Error
229+
if err != nil {
230+
sp.logger.Sugar().Errorw("Failed to create slashing adjustment record",
231+
zap.Error(err),
232+
zap.String("staker", adj.Staker),
233+
zap.String("strategy", adj.Strategy),
234+
zap.Uint64("withdrawalBlockNumber", adj.WithdrawalBlockNumber),
235+
)
236+
return err
237+
}
238+
239+
sp.logger.Sugar().Infow("Created queued withdrawal slashing adjustment",
240+
zap.String("staker", adj.Staker),
241+
zap.String("strategy", adj.Strategy),
242+
zap.String("operator", adj.Operator),
243+
zap.Uint64("withdrawalBlock", adj.WithdrawalBlockNumber),
244+
zap.Uint64("slashBlock", adj.SlashBlockNumber),
245+
zap.String("multiplier", adj.SlashMultiplier),
246+
)
247+
}
248+
83249
return nil
84250
}

pkg/eigenState/precommitProcessors/slashingProcessor/slashing_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func setup() (
4141
return dbname, grm, l, cfg, nil
4242
}
4343

44-
func withSlashingProcessor(esm *stateManager.EigenStateManager, grm *gorm.DB, l *zap.Logger) *SlashingProcessor {
44+
func withSlashingProcessor(esm *stateManager.EigenStateManager, grm *gorm.DB, l *zap.Logger, cfg *config.Config) *SlashingProcessor {
4545
return NewSlashingProcessor(esm, l, grm)
4646
}
4747

@@ -67,7 +67,7 @@ func Test_SlashingPrecommitProcessor(t *testing.T) {
6767

6868
t.Run("Should capture delegate, deposit, slash in same block", func(t *testing.T) {
6969
esm := stateManager.NewEigenStateManager(nil, l, grm)
70-
withSlashingProcessor(esm, grm, l)
70+
withSlashingProcessor(esm, grm, l, cfg)
7171

7272
blockNumber := uint64(200)
7373
err = createBlock(grm, blockNumber)
@@ -140,7 +140,7 @@ func Test_SlashingPrecommitProcessor(t *testing.T) {
140140

141141
t.Run("Should capture many deposits and slash in same block", func(t *testing.T) {
142142
esm := stateManager.NewEigenStateManager(nil, l, grm)
143-
withSlashingProcessor(esm, grm, l)
143+
withSlashingProcessor(esm, grm, l, cfg)
144144

145145
blockNumber := uint64(200)
146146
err = createBlock(grm, blockNumber)

pkg/pipeline/pipelineIntegration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func setup(ethConfig *ethereum.EthereumClientConfig) (
113113
l.Sugar().Fatalw("Failed to load meta state models", zap.Error(err))
114114
}
115115

116-
precommitProcessors.LoadPrecommitProcessors(sm, grm, l)
116+
precommitProcessors.LoadPrecommitProcessors(sm, grm, l, cfg)
117117

118118
sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg)
119119
rc, _ := rewards.NewRewardsCalculator(cfg, grm, mds, sog, sdc, l)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package _202512101500_queuedWithdrawalSlashingAdjustments
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
"github.com/Layr-Labs/sidecar/internal/config"
7+
"gorm.io/gorm"
8+
)
9+
10+
type Migration struct {
11+
}
12+
13+
func (m *Migration) Up(db *sql.DB, grm *gorm.DB, cfg *config.Config) error {
14+
queries := []string{
15+
`create table if not exists queued_withdrawal_slashing_adjustments (
16+
staker varchar not null,
17+
strategy varchar not null,
18+
operator varchar not null,
19+
withdrawal_block_number bigint not null,
20+
slash_block_number bigint not null,
21+
slash_multiplier numeric not null,
22+
block_number bigint not null,
23+
transaction_hash varchar not null,
24+
log_index bigint not null
25+
)`,
26+
`alter table queued_withdrawal_slashing_adjustments add constraint queued_withdrawal_slashing_adjustments_pk primary key (block_number, log_index, transaction_hash)`,
27+
`alter table queued_withdrawal_slashing_adjustments add constraint queued_withdrawal_slashing_adjustments_block_number_fk foreign key (block_number) references blocks (number) on delete cascade`,
28+
`alter table queued_withdrawal_slashing_adjustments add constraint uniq_queued_withdrawal_slashing_adjustments unique (staker, strategy, operator, withdrawal_block_number, slash_block_number)`,
29+
// Indexes following pattern from slashed_operator_shares
30+
`create index if not exists idx_queued_withdrawal_slashing_adjustments_staker_strategy on queued_withdrawal_slashing_adjustments(staker, strategy)`,
31+
`create index if not exists idx_queued_withdrawal_slashing_adjustments_withdrawal_block on queued_withdrawal_slashing_adjustments(withdrawal_block_number)`,
32+
}
33+
34+
for _, query := range queries {
35+
res := grm.Exec(query)
36+
if res.Error != nil {
37+
fmt.Printf("Error executing query: %s\n", query)
38+
return res.Error
39+
}
40+
}
41+
return nil
42+
}
43+
44+
func (m *Migration) GetName() string {
45+
return "202512101500_queuedWithdrawalSlashingAdjustments"
46+
}

0 commit comments

Comments
 (0)