-
Notifications
You must be signed in to change notification settings - Fork 1.8k
core/vm: reject duplicate bridge validators at Pasteur #3623
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
ee3f3c6
bb0b2cf
fb42601
62ba5ac
141f190
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,81 @@ type ConsensusState struct { | |
| ValidatorSet *types.ValidatorSet | ||
| } | ||
|
|
||
| type validatorDuplicateTracker struct { | ||
| field string | ||
| seen map[string]int | ||
| ignoreZero bool | ||
| } | ||
|
|
||
| func newValidatorDuplicateTracker(field string, size int, ignoreZero bool) *validatorDuplicateTracker { | ||
| return &validatorDuplicateTracker{ | ||
| field: field, | ||
| seen: make(map[string]int, size), | ||
| ignoreZero: ignoreZero, | ||
| } | ||
| } | ||
|
|
||
| func (t *validatorDuplicateTracker) check(idx int, value []byte) error { | ||
| if t.ignoreZero { | ||
| // Optional bridge fields may be omitted in source validators or zero-filled by | ||
| // fixed-width decoding. Both forms mean "unset" and should not count as duplicates. | ||
| if len(value) == 0 || isZeroBytes(value) { | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| key := string(value) | ||
| if firstIdx, ok := t.seen[key]; ok { | ||
| return fmt.Errorf("duplicate validator %s #%d and #%d: %X", t.field, firstIdx, idx, value) | ||
| } | ||
| t.seen[key] = idx | ||
| return nil | ||
| } | ||
|
|
||
| func isZeroBytes(value []byte) bool { | ||
| if len(value) == 0 { | ||
| return false | ||
| } | ||
| for _, b := range value { | ||
| if b != 0 { | ||
| return false | ||
| } | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| func validateUniqueValidatorSet(validatorSet *types.ValidatorSet) error { | ||
| if validatorSet == nil { | ||
| return nil | ||
| } | ||
|
|
||
| size := len(validatorSet.Validators) | ||
| addresses := newValidatorDuplicateTracker("address", size, false) | ||
| pubKeys := newValidatorDuplicateTracker("pubkey", size, false) | ||
| blsKeys := newValidatorDuplicateTracker("bls key", size, true) | ||
| relayerAddresses := newValidatorDuplicateTracker("relayer address", size, true) | ||
|
|
||
| for idx, validator := range validatorSet.Validators { | ||
| if validator == nil || validator.PubKey == nil { | ||
| return fmt.Errorf("invalid validator #%d", idx) | ||
| } | ||
| if err := addresses.check(idx, validator.Address); err != nil { | ||
| return err | ||
| } | ||
| if err := pubKeys.check(idx, validator.PubKey.Bytes()); err != nil { | ||
| return err | ||
| } | ||
| if err := blsKeys.check(idx, validator.BlsKey); err != nil { | ||
| return err | ||
| } | ||
| if err := relayerAddresses.check(idx, validator.RelayerAddress); err != nil { | ||
| return err | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // output: | ||
| // | chainID | height | nextValidatorSetHash | [{validator pubkey, voting power, relayer address, relayer bls pubkey}] | | ||
| // | 32 bytes | 8 bytes | 32 bytes | [{32 bytes, 8 bytes, 20 bytes, 48 bytes}] | | ||
|
|
@@ -137,7 +212,7 @@ func (cs *ConsensusState) ApplyLightBlock(block *types.LightBlock, isHertz bool) | |
| // input: | ||
| // | chainID | height | nextValidatorSetHash | [{validator pubkey, voting power, relayer address, relayer bls pubkey}] | | ||
| // | 32 bytes | 8 bytes | 32 bytes | [{32 bytes, 8 bytes, 20 bytes, 48 bytes}] | | ||
| func DecodeConsensusState(input []byte) (ConsensusState, error) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how about
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
| func DecodeConsensusState(input []byte, requireUniqueValidators bool) (ConsensusState, error) { | ||
| minimumLength := chainIDLength + heightLength + validatorSetHashLength | ||
| inputLen := uint64(len(input)) | ||
| if inputLen <= minimumLength || (inputLen-minimumLength)%singleValidatorBytesLength != 0 { | ||
|
|
@@ -192,14 +267,19 @@ func DecodeConsensusState(input []byte) (ConsensusState, error) { | |
| Validators: validatorSet, | ||
| }, | ||
| } | ||
| if requireUniqueValidators { | ||
| if err := validateUniqueValidatorSet(consensusState.ValidatorSet); err != nil { | ||
| return ConsensusState{}, err | ||
| } | ||
| } | ||
|
|
||
| return consensusState, nil | ||
| } | ||
|
|
||
| // input: | ||
| // consensus state length | consensus state | light block | | ||
| // 32 bytes | | | | ||
| func DecodeLightBlockValidationInput(input []byte) (*ConsensusState, *types.LightBlock, error) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how about
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
| func DecodeLightBlockValidationInput(input []byte, requireUniqueValidators bool) (*ConsensusState, *types.LightBlock, error) { | ||
| if uint64(len(input)) <= consensusStateLengthBytesLength { | ||
| return nil, nil, errors.New("invalid input") | ||
| } | ||
|
|
@@ -214,7 +294,7 @@ func DecodeLightBlockValidationInput(input []byte) (*ConsensusState, *types.Ligh | |
| return nil, nil, fmt.Errorf("expected payload size %d, actual size: %d", consensusStateLengthBytesLength+csLen, len(input)) | ||
| } | ||
|
|
||
| cs, err := DecodeConsensusState(input[consensusStateLengthBytesLength : consensusStateLengthBytesLength+csLen]) | ||
| cs, err := DecodeConsensusState(input[consensusStateLengthBytesLength:consensusStateLengthBytesLength+csLen], requireUniqueValidators) | ||
| if err != nil { | ||
| return nil, nil, err | ||
| } | ||
|
|
@@ -228,6 +308,11 @@ func DecodeLightBlockValidationInput(input []byte) (*ConsensusState, *types.Ligh | |
| if err != nil { | ||
| return nil, nil, err | ||
| } | ||
| if requireUniqueValidators { | ||
| if err := validateUniqueValidatorSet(block.ValidatorSet); err != nil { | ||
| return nil, nil, err | ||
| } | ||
| } | ||
|
|
||
| return &cs, block, nil | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are bls key and RelayerAddress allowed to stay unset? represent by zero value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Those two fields are allowed to be unset upstream. In our fixed-width decode path, "unset" is represented as zero-filled bytes, so I treat empty/nil and all-zero as the same unset state and exclude only
that case from duplicate detection.