Skip to content

Commit fbf5adc

Browse files
authored
Implement manual Bans/Unbans (#232)
1 parent d07347f commit fbf5adc

File tree

6 files changed

+774
-4
lines changed

6 files changed

+774
-4
lines changed

contract/abi.abi

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

contract/contract.go

Lines changed: 332 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

oracle/onchain.go

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,6 @@ func (o *Onchain) GetSetOfValidators(valIndices []phase0.ValidatorIndex, slot st
357357
return nil, errors.New("Error: no validators found onchain for the given indices")
358358
}
359359

360-
361360
// Sanity checks - Ensure all requested validators are found
362361
for _, idx := range valIndices {
363362
if _, found := validators.Data[idx]; !found {
@@ -368,7 +367,6 @@ func (o *Onchain) GetSetOfValidators(valIndices []phase0.ValidatorIndex, slot st
368367
return validators.Data, nil
369368
}
370369

371-
372370
func (o *Onchain) BlockByNumber(blockNumber *big.Int, opts ...retry.Option) (*types.Block, error) {
373371
var err error
374372
var block *types.Block
@@ -919,6 +917,16 @@ func (o *Onchain) FetchFullBlock(slot uint64, oracle *Oracle, opt ...bool) *Full
919917
log.Fatal("failed getting update subscription collateral events: ", err)
920918
}
921919

920+
banValidator, err := o.GetBanValidatorEvents(fullBlock.GetBlockNumber())
921+
if err != nil {
922+
log.Fatal("failed getting ban validator events: ", err)
923+
}
924+
925+
unbanValidator, err := o.GetUnbanValidatorEvents(fullBlock.GetBlockNumber())
926+
if err != nil {
927+
log.Fatal("failed getting unban validator events: ", err)
928+
}
929+
922930
// Not all events are fetched as they are not needed
923931
events := &Events{
924932
EtherReceived: etherReceived,
@@ -938,6 +946,8 @@ func (o *Onchain) FetchFullBlock(slot uint64, oracle *Oracle, opt ...bool) *Full
938946
//RemoveOracleMember: removeOracleMember,
939947
//TransferGovernance: transferGovernance,
940948
//AcceptGovernance: acceptGovernance,
949+
BanValidator: banValidator,
950+
UnbanValidator: unbanValidator,
941951
}
942952

943953
// Add the events to the block
@@ -1302,6 +1312,78 @@ func (o *Onchain) GetUnsubscribeValidatorEvents(
13021312
return events, nil
13031313
}
13041314

1315+
func (o *Onchain) GetBanValidatorEvents(
1316+
blockNumber uint64,
1317+
opts ...retry.Option) ([]*contract.ContractBanValidator, error) {
1318+
1319+
startBlock := uint64(blockNumber)
1320+
endBlock := uint64(blockNumber)
1321+
1322+
filterOpts := &bind.FilterOpts{Context: context.Background(), Start: startBlock, End: &endBlock}
1323+
1324+
var err error
1325+
var itr *contract.ContractBanValidatorIterator
1326+
1327+
err = retry.Do(func() error {
1328+
itr, err = o.Contract.FilterBanValidator(filterOpts)
1329+
if err != nil {
1330+
log.Warn("Failed attempt GetBanValidatorEvents for block ", strconv.FormatUint(blockNumber, 10), ": ", err.Error(), " Retrying...")
1331+
return err
1332+
}
1333+
return nil
1334+
}, o.GetRetryOpts(opts)...)
1335+
1336+
if err != nil {
1337+
return nil, errors.Wrap(err, "could not get BanValidator events")
1338+
}
1339+
1340+
var events []*contract.ContractBanValidator
1341+
for itr.Next() {
1342+
events = append(events, itr.Event)
1343+
}
1344+
err = itr.Close()
1345+
if err != nil {
1346+
return nil, errors.Wrap(err, "could not close BanValidator iterator")
1347+
}
1348+
return events, nil
1349+
}
1350+
1351+
func (o *Onchain) GetUnbanValidatorEvents(
1352+
blockNumber uint64,
1353+
opts ...retry.Option) ([]*contract.ContractUnbanValidator, error) {
1354+
1355+
startBlock := uint64(blockNumber)
1356+
endBlock := uint64(blockNumber)
1357+
1358+
filterOpts := &bind.FilterOpts{Context: context.Background(), Start: startBlock, End: &endBlock}
1359+
1360+
var err error
1361+
var itr *contract.ContractUnbanValidatorIterator
1362+
1363+
err = retry.Do(func() error {
1364+
itr, err = o.Contract.FilterUnbanValidator(filterOpts)
1365+
if err != nil {
1366+
log.Warn("Failed attempt GetUnbanValidatorEvents for block ", strconv.FormatUint(blockNumber, 10), ": ", err.Error(), " Retrying...")
1367+
return err
1368+
}
1369+
return nil
1370+
}, o.GetRetryOpts(opts)...)
1371+
1372+
if err != nil {
1373+
return nil, errors.Wrap(err, "could not get UnbanValidator events")
1374+
}
1375+
1376+
var events []*contract.ContractUnbanValidator
1377+
for itr.Next() {
1378+
events = append(events, itr.Event)
1379+
}
1380+
err = itr.Close()
1381+
if err != nil {
1382+
return nil, errors.Wrap(err, "could not close UnbanValidator iterator")
1383+
}
1384+
return events, nil
1385+
}
1386+
13051387
func (o *Onchain) GetInitSmoothingPoolEvents(
13061388
blockNumber uint64,
13071389
opts ...retry.Option) ([]*contract.ContractInitSmoothingPool, error) {

oracle/oracle.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,14 @@ func (or *Oracle) AdvanceStateToNextSlot(fullBlock *FullBlock) (uint64, error) {
206206
// Handle the donations from this block
207207
or.handleDonations(blockDonations)
208208

209+
// Manual bans/unbans should always be the last thing to be processed in each block, since
210+
// we want to ensure they persist to the next block
211+
// Handle manual bans
212+
or.handleManualBans(fullBlock.Events.BanValidator)
213+
214+
// Handle manual unbans
215+
or.handleManualUnbans(fullBlock.Events.UnbanValidator)
216+
209217
// Handle validator cleanup: redisitribute the pending rewards of validators subscribed to the pool
210218
// that are not in the beacon chain anymore (exited/slashed). We dont run this on every slot because
211219
// its expensive. Runs every 4 hours.
@@ -1211,6 +1219,98 @@ func (or *Oracle) handleManualUnsubscriptions(
12111219
}
12121220
}
12131221

1222+
func (or *Oracle) handleManualBans(
1223+
banEvents []*contract.ContractBanValidator) {
1224+
1225+
// Return immediately if there are no ban events. Nothing to process!
1226+
if len(banEvents) == 0 {
1227+
return
1228+
}
1229+
1230+
// FIRST: healthy checks, ensure the bans events are okay.
1231+
// Ensure the bans events are from the same block
1232+
if len(banEvents) > 0 {
1233+
blockReference := banEvents[0].Raw.BlockNumber
1234+
for _, ban := range banEvents {
1235+
if ban.Raw.BlockNumber != blockReference {
1236+
log.Fatal("Handling manual bans from different blocks is not possible: ",
1237+
ban.Raw.BlockNumber, " vs ", blockReference)
1238+
}
1239+
}
1240+
}
1241+
1242+
totalPending := big.NewInt(0)
1243+
// SECOND: iterate over the ban events.
1244+
// - Advance state machine of all banned validators (move them to Banned state).
1245+
// - Sum all the pending rewards of the banned validators and share them among the rest.
1246+
// - Reset the pending rewards of the banned validators (sets pending to 0).
1247+
for _, ban := range banEvents {
1248+
log.WithFields(log.Fields{
1249+
"BlockNumber": ban.Raw.BlockNumber,
1250+
"TxHash": ban.Raw.TxHash,
1251+
"ValidatorIndex": ban.ValidatorID,
1252+
}).Info("[Ban] Ban event received")
1253+
1254+
//Check if the validator is subscribed. If not, we log it and dont do anything
1255+
if !or.isSubscribed(ban.ValidatorID) {
1256+
log.Warn("Validator is not subscribed, skipping ban event")
1257+
continue
1258+
}
1259+
1260+
or.advanceStateMachine(ban.ValidatorID, ManualBan)
1261+
totalPending.Add(totalPending, or.state.Validators[ban.ValidatorID].PendingRewardsWei)
1262+
or.resetPendingRewards(ban.ValidatorID)
1263+
1264+
}
1265+
1266+
// THIRD: share the total pending rewards of the banned validators among the rest. This has to be done
1267+
// once all the bans have been processed. This should also be only done if banEvents is not empty, thats
1268+
// why we have the check at the beginning of the function.
1269+
1270+
// If totalPending is negative, log a fatal error. We should never have negative rewards to share.
1271+
if totalPending.Cmp(big.NewInt(0)) < 0 {
1272+
log.Fatal("Total pending rewards is negative. Aborting reward sharing.")
1273+
}
1274+
1275+
// Only share rewards if totalPending is greater than zero.
1276+
if totalPending.Cmp(big.NewInt(0)) > 0 {
1277+
or.increaseAllPendingRewards(totalPending)
1278+
}
1279+
}
1280+
1281+
func (or *Oracle) handleManualUnbans(
1282+
unbanEvents []*contract.ContractUnbanValidator) {
1283+
1284+
// FIRST: healthy checks, ensure the unbans events are okay.
1285+
if len(unbanEvents) > 0 {
1286+
blockReference := unbanEvents[0].Raw.BlockNumber
1287+
for _, ban := range unbanEvents {
1288+
if ban.Raw.BlockNumber != blockReference {
1289+
log.Fatal("Handling manual unbans from different blocks is not possible: ",
1290+
ban.Raw.BlockNumber, " vs ", blockReference)
1291+
}
1292+
}
1293+
}
1294+
1295+
// SECOND: iterate over the unban events.
1296+
// - Advance state machine of all unbanned validators (move them to Active state).
1297+
for _, unban := range unbanEvents {
1298+
log.WithFields(log.Fields{
1299+
"BlockNumber": unban.Raw.BlockNumber,
1300+
"TxHash": unban.Raw.TxHash,
1301+
"ValidatorIndex": unban.ValidatorID,
1302+
}).Info("[Unban] Unban event received")
1303+
1304+
// Check if the validator is banned. If not, we log it and dont do anything
1305+
if !or.isBanned(unban.ValidatorID) {
1306+
log.Warn("Validator is not banned, skipping unban event")
1307+
continue
1308+
}
1309+
1310+
or.advanceStateMachine(unban.ValidatorID, ManualUnban)
1311+
}
1312+
}
1313+
12141314
// Banning a validator implies sharing its pending rewards among the rest
12151315
// of the validators and setting its pending to zero.
12161316
func (or *Oracle) handleBanValidator(block SummarizedBlock) {
@@ -1490,6 +1590,18 @@ func GetWithdrawalAndType(validator *v1.Validator) (string, WithdrawalType) {
14901590
// See the spec for state diagram with states and transitions. This tracks all the different
14911591
// states and state transitions that a given validator can have from the oracle point of view
14921592
func (or *Oracle) advanceStateMachine(valIndex uint64, event Event) {
1593+
1594+
// Safety check, if the validator does not exist, we log it and return
1595+
validator, exists := or.state.Validators[valIndex]
1596+
if !exists || validator == nil {
1597+
// Handle the case where the validator does not exist or is nil
1598+
log.WithFields(log.Fields{
1599+
"ValidatorIndex": valIndex,
1600+
"Error": "Validator not found or is nil",
1601+
}).Warn("Called advanceStateMachine with a validator that does not exist or is nil")
1602+
return
1603+
}
1604+
14931605
switch or.state.Validators[valIndex].ValidatorStatus {
14941606
case Active:
14951607
switch event {
@@ -1525,6 +1637,15 @@ func (or *Oracle) advanceStateMachine(valIndex uint64, event Event) {
15251637
"Slot": or.state.NextSlotToProcess,
15261638
}).Info("Validator state change")
15271639
or.state.Validators[valIndex].ValidatorStatus = NotSubscribed
1640+
case ManualBan:
1641+
log.WithFields(log.Fields{
1642+
"Event": "ManualBan",
1643+
"StateChange": "Active -> Banned",
1644+
"ValidatorIndex": valIndex,
1645+
"Slot": or.state.NextSlotToProcess,
1646+
}).Info("Validator state change")
1647+
or.state.Validators[valIndex].ValidatorStatus = Banned
1648+
15281649
}
15291650
case YellowCard:
15301651
switch event {
@@ -1560,6 +1681,14 @@ func (or *Oracle) advanceStateMachine(valIndex uint64, event Event) {
15601681
"Slot": or.state.NextSlotToProcess,
15611682
}).Info("Validator state change")
15621683
or.state.Validators[valIndex].ValidatorStatus = NotSubscribed
1684+
case ManualBan:
1685+
log.WithFields(log.Fields{
1686+
"Event": "ManualBan",
1687+
"StateChange": "YellowCard -> Banned",
1688+
"ValidatorIndex": valIndex,
1689+
"Slot": or.state.NextSlotToProcess,
1690+
}).Info("Validator state change")
1691+
or.state.Validators[valIndex].ValidatorStatus = Banned
15631692
}
15641693
case RedCard:
15651694
switch event {
@@ -1595,6 +1724,14 @@ func (or *Oracle) advanceStateMachine(valIndex uint64, event Event) {
15951724
"Slot": or.state.NextSlotToProcess,
15961725
}).Info("Validator state change")
15971726
or.state.Validators[valIndex].ValidatorStatus = NotSubscribed
1727+
case ManualBan:
1728+
log.WithFields(log.Fields{
1729+
"Event": "ManualBan",
1730+
"StateChange": "RedCard -> Banned",
1731+
"ValidatorIndex": valIndex,
1732+
"Slot": or.state.NextSlotToProcess,
1733+
}).Info("Validator state change")
1734+
or.state.Validators[valIndex].ValidatorStatus = Banned
15981735
}
15991736
case NotSubscribed:
16001737
switch event {
@@ -1615,5 +1752,18 @@ func (or *Oracle) advanceStateMachine(valIndex uint64, event Event) {
16151752
}).Info("Validator state change")
16161753
or.state.Validators[valIndex].ValidatorStatus = Active
16171754
}
1755+
// A validator could return to the state it was after being banned, but we
1756+
// return it always to the Active state for the sake of simplicity.
1757+
case Banned:
1758+
switch event {
1759+
case ManualUnban:
1760+
log.WithFields(log.Fields{
1761+
"Event": "ManualUnban",
1762+
"StateChange": "Banned -> Active",
1763+
"ValidatorIndex": valIndex,
1764+
"Slot": or.state.NextSlotToProcess,
1765+
}).Info("Validator state change")
1766+
or.state.Validators[valIndex].ValidatorStatus = Active
1767+
}
16181768
}
16191769
}

0 commit comments

Comments
 (0)