From 93f2fea006269737cc433dc606151d02452d90c3 Mon Sep 17 00:00:00 2001 From: pk910 Date: Fri, 9 Feb 2024 14:44:46 +0100 Subject: [PATCH] added more block constraints to `check_consensus_block_proposals` task --- go.mod | 3 +- go.sum | 6 + .../check_consensus_block_proposals/README.md | 21 ++ .../check_consensus_block_proposals/config.go | 18 ++ .../check_consensus_block_proposals/task.go | 226 +++++++++++++++++- .../check_consensus_validator_status/task.go | 2 +- 6 files changed, 263 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 0908f5f..9413348 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,7 @@ require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/itchyny/gojq v0.12.14 // indirect github.com/itchyny/timefmt-go v0.1.5 // indirect + github.com/juliangruber/go-intersect v1.1.0 // indirect github.com/kilic/bls12-381 v0.1.0 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -90,4 +91,4 @@ require ( google.golang.org/protobuf v1.31.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect -) \ No newline at end of file +) diff --git a/go.sum b/go.sum index 128e122..73a07a6 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= @@ -142,14 +143,19 @@ github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/juliangruber/go-intersect v1.1.0 h1:sc+y5dCjMMx0pAdYk/N6KBm00tD/f3tq+Iox7dYDUrY= +github.com/juliangruber/go-intersect v1.1.0/go.mod h1:WMau+1kAmnlQnKiikekNJbtGtfmILU/mMU6H7AgKbWQ= github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= diff --git a/pkg/coordinator/tasks/check_consensus_block_proposals/README.md b/pkg/coordinator/tasks/check_consensus_block_proposals/README.md index 4c91b3d..cc4ef2f 100644 --- a/pkg/coordinator/tasks/check_consensus_block_proposals/README.md +++ b/pkg/coordinator/tasks/check_consensus_block_proposals/README.md @@ -44,6 +44,22 @@ The `check_consensus_block_proposals` task checks consensus block proposals to m - **`minBlobCount`**:\ The minimum number of blob sidecars (extra data packets) in a block. +- **`expectDeposits`**:\ + A list of validator public keys expected to have deposit operations included in the block. + +- **`expectExits`**:\ + A list of validator public keys expected to have exit operations included in the block. + +- **`expectSlashings`**:\ + A list of expected slashing operations in the block, each specified as an object with a `publicKey` and a `slashingType` ("attester" or "proposer"). If `slashingType` is omitted, any type of slashing is accepted. + +- **`expectBlsChanges`**:\ + A list of expected BLS change operations in the block, each as an object with a `publicKey` and the target `address` (optional). + +- **`expectWithdrawals`**:\ + A list of expected withdrawal operations in the block, each as an object with a `publicKey`, `address`, and a `minAmount` specifying the minimum amount expected for the withdrawal. + + ### Defaults These are the default settings for the `check_consensus_block_proposals` task: @@ -64,4 +80,9 @@ These are the default settings for the `check_consensus_block_proposals` task: minWithdrawalCount: 0 minTransactionCount: 0 minBlobCount: 0 + expectDeposits: [] + expectExits: [] + expectSlashings: [] + expectBlsChanges: [] + expectWithdrawals: [] ``` diff --git a/pkg/coordinator/tasks/check_consensus_block_proposals/config.go b/pkg/coordinator/tasks/check_consensus_block_proposals/config.go index 4be0a08..348e8b1 100644 --- a/pkg/coordinator/tasks/check_consensus_block_proposals/config.go +++ b/pkg/coordinator/tasks/check_consensus_block_proposals/config.go @@ -1,5 +1,7 @@ package checkconsensusblockproposals +import "math/big" + type Config struct { BlockCount int `yaml:"blockCount" json:"blockCount"` GraffitiPattern string `yaml:"graffitiPattern" json:"graffitiPattern"` @@ -14,6 +16,22 @@ type Config struct { MinWithdrawalCount int `yaml:"minWithdrawalCount" json:"minWithdrawalCount"` MinTransactionCount int `yaml:"minTransactionCount" json:"minTransactionCount"` MinBlobCount int `yaml:"minBlobCount" json:"minBlobCount"` + + ExpectDeposits []string `yaml:"expectDeposits" json:"expectDeposits"` + ExpectExits []string `yaml:"expectExits" json:"expectExits"` + ExpectSlashings []struct { + PublicKey string `yaml:"publicKey" json:"publicKey"` + SlashingType string `yaml:"slashingType" json:"slashingType"` + } `yaml:"expectSlashings" json:"expectSlashings"` + ExpectBlsChanges []struct { + PublicKey string `yaml:"publicKey" json:"publicKey"` + Address string `yaml:"address" json:"address"` + } `yaml:"expectBlsChanges" json:"expectBlsChanges"` + ExpectWithdrawals []struct { + PublicKey string `yaml:"publicKey" json:"publicKey"` + Address string `yaml:"address" json:"address"` + MinAmount *big.Int `yaml:"minAmount" json:"minAmount"` + } `yaml:"expectWithdrawals" json:"expectWithdrawals"` } func DefaultConfig() Config { diff --git a/pkg/coordinator/tasks/check_consensus_block_proposals/task.go b/pkg/coordinator/tasks/check_consensus_block_proposals/task.go index db65f4f..4e615d7 100644 --- a/pkg/coordinator/tasks/check_consensus_block_proposals/task.go +++ b/pkg/coordinator/tasks/check_consensus_block_proposals/task.go @@ -3,12 +3,15 @@ package checkconsensusblockproposals import ( "context" "fmt" + "math/big" "regexp" "time" + v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec" "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/juliangruber/go-intersect" "github.com/sirupsen/logrus" ) @@ -23,11 +26,12 @@ var ( ) type Task struct { - ctx *types.TaskContext - options *types.TaskOptions - config Config - logger logrus.FieldLogger - firstHeight map[uint16]uint64 + ctx *types.TaskContext + options *types.TaskOptions + config Config + logger logrus.FieldLogger + firstHeight map[uint16]uint64 + currentValidatorSet map[uint64]*v1.Validator } func NewTask(ctx *types.TaskContext, options *types.TaskOptions) (types.Task, error) { @@ -91,10 +95,16 @@ func (t *Task) LoadConfig() error { func (t *Task) Execute(ctx context.Context) error { consensusPool := t.ctx.Scheduler.GetCoordinator().ClientPool().GetConsensusPool() - blockSubscription := consensusPool.GetBlockCache().SubscribeBlockEvent(10) + blockSubscription := consensusPool.GetBlockCache().SubscribeBlockEvent(10) defer blockSubscription.Unsubscribe() + wallclockEpochSubscription := consensusPool.GetBlockCache().SubscribeWallclockEpochEvent(10) + defer wallclockEpochSubscription.Unsubscribe() + + // load current epoch duties + t.loadValidatorSet(ctx) + totalMatches := 0 for { @@ -118,12 +128,30 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.SetResult(types.TaskResultNone) } } + case <-wallclockEpochSubscription.Channel(): + t.loadValidatorSet(ctx) case <-ctx.Done(): return ctx.Err() } } } +func (t *Task) loadValidatorSet(ctx context.Context) { + client := t.ctx.Scheduler.GetCoordinator().ClientPool().GetConsensusPool().GetReadyEndpoint(consensus.UnspecifiedClient) + validatorSet, err := client.GetRPCClient().GetStateValidators(ctx, "head") + + if err != nil { + t.logger.Errorf("error while fetching validator set: %v", err.Error()) + return + } + + t.currentValidatorSet = make(map[uint64]*v1.Validator) + for _, val := range validatorSet { + t.currentValidatorSet[uint64(val.Index)] = val + } +} + +//nolint:gocyclo // ignore func (t *Task) checkBlock(ctx context.Context, block *consensus.Block) bool { blockData := block.AwaitBlock(ctx, 2*time.Second) if blockData == nil { @@ -147,17 +175,17 @@ func (t *Task) checkBlock(ctx context.Context, block *consensus.Block) bool { } // check deposit count - if t.config.MinDepositCount > 0 && !t.checkBlockDeposits(block, blockData) { + if (t.config.MinDepositCount > 0 || len(t.config.ExpectDeposits) > 0) && !t.checkBlockDeposits(block, blockData) { return false } // check exit count - if t.config.MinExitCount > 0 && !t.checkBlockExits(block, blockData) { + if (t.config.MinExitCount > 0 || len(t.config.ExpectExits) > 0) && !t.checkBlockExits(block, blockData) { return false } // check slashing count - if t.config.MinSlashingCount > 0 && !t.checkBlockSlashings(block, blockData) { + if (t.config.MinSlashingCount > 0 || len(t.config.ExpectSlashings) > 0) && !t.checkBlockSlashings(block, blockData) { return false } @@ -172,12 +200,12 @@ func (t *Task) checkBlock(ctx context.Context, block *consensus.Block) bool { } // check bls change count - if t.config.MinBlsChangeCount > 0 && !t.checkBlockBlsChanges(block, blockData) { + if (t.config.MinBlsChangeCount > 0 || len(t.config.ExpectBlsChanges) > 0) && !t.checkBlockBlsChanges(block, blockData) { return false } // check withdrawal count - if t.config.MinWithdrawalCount > 0 && !t.checkBlockWithdrawals(block, blockData) { + if (t.config.MinWithdrawalCount > 0 || len(t.config.ExpectWithdrawals) > 0) && !t.checkBlockWithdrawals(block, blockData) { return false } @@ -265,6 +293,24 @@ func (t *Task) checkBlockDeposits(block *consensus.Block, blockData *spec.Versio return false } + if len(t.config.ExpectDeposits) > 0 { + for _, pubkey := range t.config.ExpectDeposits { + found := false + + for _, deposit := range deposits { + if deposit.Data.PublicKey.String() == pubkey { + found = true + break + } + } + + if !found { + t.logger.Infof("check failed for block %v [0x%x]: expected deposit not found (pubkey: %v)", block.Slot, block.Root, pubkey) + return false + } + } + } + return true } @@ -280,6 +326,34 @@ func (t *Task) checkBlockExits(block *consensus.Block, blockData *spec.Versioned return false } + if len(t.config.ExpectExits) > 0 { + if t.currentValidatorSet == nil { + t.logger.Errorf("check failed: no validator set") + return false + } + + for _, pubkey := range t.config.ExpectExits { + found := false + + for _, exit := range exits { + validator := t.currentValidatorSet[uint64(exit.Message.ValidatorIndex)] + if validator == nil { + continue + } + + if validator.Validator.PublicKey.String() == pubkey { + found = true + break + } + } + + if !found { + t.logger.Infof("check failed for block %v [0x%x]: expected exit not found (pubkey: %v)", block.Slot, block.Root, pubkey) + return false + } + } + } + return true } @@ -302,6 +376,64 @@ func (t *Task) checkBlockSlashings(block *consensus.Block, blockData *spec.Versi return false } + if len(t.config.ExpectSlashings) > 0 { + if t.currentValidatorSet == nil { + t.logger.Errorf("check failed: no validator set") + return false + } + + for _, expectedSlashing := range t.config.ExpectSlashings { + found := false + + if !found && (expectedSlashing.SlashingType == "" || expectedSlashing.SlashingType == "attester") { + for _, slashing := range attSlashings { + inter := intersect.Simple(slashing.Attestation1.AttestingIndices, slashing.Attestation2.AttestingIndices) + for _, j := range inter { + valIdx, ok := j.(uint64) + if !ok { + continue + } + + validator := t.currentValidatorSet[valIdx] + if validator == nil { + continue + } + + if validator.Validator.PublicKey.String() == expectedSlashing.PublicKey { + found = true + break + } + } + + if found { + break + } + } + } + + if !found && (expectedSlashing.SlashingType == "" || expectedSlashing.SlashingType == "proposer") { + for _, slashing := range propSlashings { + valIdx := uint64(slashing.SignedHeader1.Message.ProposerIndex) + + validator := t.currentValidatorSet[valIdx] + if validator == nil { + continue + } + + if validator.Validator.PublicKey.String() == expectedSlashing.PublicKey { + found = true + break + } + } + } + + if !found { + t.logger.Infof("check failed for block %v [0x%x]: expected deposit not found (pubkey: %v)", block.Slot, block.Root, expectedSlashing.PublicKey) + return false + } + } + } + return true } @@ -349,6 +481,39 @@ func (t *Task) checkBlockBlsChanges(block *consensus.Block, blockData *spec.Vers return false } + if len(t.config.ExpectBlsChanges) > 0 { + if t.currentValidatorSet == nil { + t.logger.Errorf("check failed: no validator set") + return false + } + + for _, expectedBlsChange := range t.config.ExpectBlsChanges { + found := false + + for _, blsChange := range blsChanges { + validator := t.currentValidatorSet[uint64(blsChange.Message.ValidatorIndex)] + if validator == nil { + continue + } + + if validator.Validator.PublicKey.String() == expectedBlsChange.PublicKey { + if expectedBlsChange.Address != "" && expectedBlsChange.Address != blsChange.Message.ToExecutionAddress.String() { + t.logger.Warnf("check failed: bls change found, but execution address does not match (have: %v, want: %v)", blsChange.Message.ToExecutionAddress.String(), expectedBlsChange.Address) + } else { + found = true + } + + break + } + } + + if !found { + t.logger.Infof("check failed for block %v [0x%x]: expected bls change not found (pubkey: %v)", block.Slot, block.Root, expectedBlsChange.PublicKey) + return false + } + } + } + return true } @@ -364,6 +529,45 @@ func (t *Task) checkBlockWithdrawals(block *consensus.Block, blockData *spec.Ver return false } + if len(t.config.ExpectWithdrawals) > 0 { + if t.currentValidatorSet == nil { + t.logger.Errorf("check failed: no validator set") + return false + } + + for _, expectedWithdrawal := range t.config.ExpectWithdrawals { + found := false + + for _, withdrawal := range withdrawals { + validator := t.currentValidatorSet[uint64(withdrawal.ValidatorIndex)] + if validator == nil { + continue + } + + if validator.Validator.PublicKey.String() == expectedWithdrawal.PublicKey { + withdrawalAmount := big.NewInt(int64(withdrawal.Amount)) + withdrawalAmount = withdrawalAmount.Mul(withdrawalAmount, big.NewInt(1000000000)) + + switch { + case expectedWithdrawal.Address != "" && expectedWithdrawal.Address != withdrawal.Address.String(): + t.logger.Warnf("check failed: withdrawal found, but execution address does not match (have: %v, want: %v)", withdrawal.Address.String(), expectedWithdrawal.Address) + case expectedWithdrawal.MinAmount.Cmp(big.NewInt(0)) > 0 && expectedWithdrawal.MinAmount.Cmp(withdrawalAmount) < 0: + t.logger.Warnf("check failed: withdrawal found, but amount lower than minimum (have: %v, want >= %v)", withdrawalAmount, expectedWithdrawal.MinAmount) + default: + found = true + } + + break + } + } + + if !found { + t.logger.Infof("check failed for block %v [0x%x]: expected bls change not found (pubkey: %v)", block.Slot, block.Root, expectedWithdrawal.PublicKey) + return false + } + } + } + return true } diff --git a/pkg/coordinator/tasks/check_consensus_validator_status/task.go b/pkg/coordinator/tasks/check_consensus_validator_status/task.go index 84883b3..7aab89b 100644 --- a/pkg/coordinator/tasks/check_consensus_validator_status/task.go +++ b/pkg/coordinator/tasks/check_consensus_validator_status/task.go @@ -125,7 +125,7 @@ func (t *Task) loadValidatorSet(ctx context.Context) { validatorSet, err := client.GetRPCClient().GetStateValidators(ctx, "head") if err != nil { - t.logger.Errorf("error while fetching epoch duties: %v", err.Error()) + t.logger.Errorf("error while fetching validator set: %v", err.Error()) return }