diff --git a/core/vm/contracts.go b/core/vm/contracts.go index f8b2d0856d..1bdeb5a65d 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -350,6 +350,14 @@ var PrecompiledContractsOsaka = PrecompiledContracts{ common.BytesToAddress([]byte{0x1, 0x00}): &p256Verify{eip7951: true}, } +var PrecompiledContractsPasteur = func() PrecompiledContracts { + precompiles := maps.Clone(PrecompiledContractsOsaka) + // Note: 0x66 (blsSignatureVerify) is a generic BLS verification primitive + // and does not require unique pubkeys. Uniqueness is enforced by callers. + precompiles[common.BytesToAddress([]byte{0x67})] = &cometBFTLightBlockValidatePasteur{} + return precompiles +}() + // PrecompiledContractsP256Verify contains the precompiled Ethereum // contract specified in EIP-7212. This is exported for testing purposes. var PrecompiledContractsP256Verify = PrecompiledContracts{ @@ -357,6 +365,7 @@ var PrecompiledContractsP256Verify = PrecompiledContracts{ } var ( + PrecompiledAddressesPasteur []common.Address PrecompiledAddressesOsaka []common.Address PrecompiledAddressesPrague []common.Address PrecompiledAddressesHaber []common.Address @@ -417,6 +426,9 @@ func init() { for k := range PrecompiledContractsPrague { PrecompiledAddressesPrague = append(PrecompiledAddressesPrague, k) } + for k := range PrecompiledContractsPasteur { + PrecompiledAddressesPasteur = append(PrecompiledAddressesPasteur, k) + } for k := range PrecompiledContractsOsaka { PrecompiledAddressesOsaka = append(PrecompiledAddressesOsaka, k) } @@ -426,6 +438,8 @@ func activePrecompiledContracts(rules params.Rules) PrecompiledContracts { switch { case rules.IsVerkle: return PrecompiledContractsVerkle + case rules.IsPasteur: + return PrecompiledContractsPasteur case rules.IsOsaka: return PrecompiledContractsOsaka case rules.IsPrague: @@ -467,6 +481,8 @@ func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts { // ActivePrecompiles returns the precompile addresses enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { switch { + case rules.IsPasteur: + return PrecompiledAddressesPasteur case rules.IsOsaka: return PrecompiledAddressesOsaka case rules.IsPrague: @@ -1623,6 +1639,10 @@ func (c *blsSignatureVerify) RequiredGas(input []byte) uint64 { // Run input: // msg | signature | [{bls pubkey}] | // 32 bytes | 96 bytes | [{48 bytes}] | +// +// Note: as a generic BLS signature verification primitive, this precompile +// does not require public keys to be unique. Callers that need uniqueness +// (e.g. validator set decoding) must enforce it themselves. func (c *blsSignatureVerify) Run(input []byte) ([]byte, error) { msgAndSigLength := msgHashLength + signatureLength inputLen := uint64(len(input)) diff --git a/core/vm/contracts_lightclient.go b/core/vm/contracts_lightclient.go index d83c537644..4eefcf6731 100644 --- a/core/vm/contracts_lightclient.go +++ b/core/vm/contracts_lightclient.go @@ -7,6 +7,7 @@ import ( "net/url" "strings" + "github.com/cometbft/cometbft/types" "github.com/cosmos/iavl" "github.com/tendermint/tendermint/crypto/merkle" "github.com/tendermint/tendermint/crypto/secp256k1" @@ -391,14 +392,18 @@ func (c *cometBFTLightBlockValidate) RequiredGas(input []byte) uint64 { return params.CometBFTLightBlockValidateGas } -func (c *cometBFTLightBlockValidate) run(input []byte, isHertz bool) (result []byte, err error) { +func (c *cometBFTLightBlockValidate) run(input []byte, isHertz bool, requireUniqueValidators bool) (result []byte, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("internal error: %v\n", r) } }() - cs, block, err := v2.DecodeLightBlockValidationInput(input) + var ( + cs *v2.ConsensusState + block *types.LightBlock + ) + cs, block, err = v2.DecodeLightBlockValidationInput(input, requireUniqueValidators) if err != nil { return nil, err } @@ -418,7 +423,7 @@ func (c *cometBFTLightBlockValidate) run(input []byte, isHertz bool) (result []b } func (c *cometBFTLightBlockValidate) Run(input []byte) (result []byte, err error) { - return c.run(input, false) + return c.run(input, false, false) } func (c *cometBFTLightBlockValidate) Name() string { @@ -430,13 +435,25 @@ type cometBFTLightBlockValidateHertz struct { } func (c *cometBFTLightBlockValidateHertz) Run(input []byte) (result []byte, err error) { - return c.run(input, true) + return c.run(input, true, false) } func (c *cometBFTLightBlockValidateHertz) Name() string { return "COMET_BFT_LIGHT_BLOCK_VALIDATE_HERTZ" } +type cometBFTLightBlockValidatePasteur struct { + cometBFTLightBlockValidate +} + +func (c *cometBFTLightBlockValidatePasteur) Run(input []byte) (result []byte, err error) { + return c.run(input, true, true) +} + +func (c *cometBFTLightBlockValidatePasteur) Name() string { + return "COMET_BFT_LIGHT_BLOCK_VALIDATE_PASTEUR" +} + // secp256k1SignatureRecover implemented as a native contract. type secp256k1SignatureRecover struct{} diff --git a/core/vm/contracts_lightclient_test.go b/core/vm/contracts_lightclient_test.go index e3211b07da..ceb8bbe0c9 100644 --- a/core/vm/contracts_lightclient_test.go +++ b/core/vm/contracts_lightclient_test.go @@ -5,13 +5,17 @@ import ( "encoding/hex" "testing" + "github.com/cometbft/cometbft/types" "github.com/cosmos/iavl" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" v1 "github.com/ethereum/go-ethereum/core/vm/lightclient/v1" + v2 "github.com/ethereum/go-ethereum/core/vm/lightclient/v2" ) const ( @@ -374,6 +378,39 @@ func TestCometBFTLightBlockValidateHertz(t *testing.T) { require.Equal(t, expectOutputStr, hex.EncodeToString(res)) } +func TestCometBFTLightBlockValidateRejectsDuplicateTrustedValidatorsAtPasteur(t *testing.T) { + inputStr := "000000000000000000000000000000000000000000000000000000000000018c677265656e6669656c645f393030302d3132310000000000000000000000000000000000000000013c350cd55b99dc6c2b7da9bef5410fbfb869fede858e7b95bf7ca294e228bb40e33f6e876d63791ebd05ff617a1b4f4ad1aa2ce65e3c3a9cdfb33e0ffa7e8423000000000098968015154514f68ce65a0d9eecc578c0ab12da0a2a28a0805521b5b7ae56eb3fb24555efbfe59e1622bfe9f7be8c9022e9b3f2442739c1ce870b9adee169afe60f674edd7c86451c5363d89052fde8351895eeea166ce5373c36e31b518ed191d0c599aa0f5b0000000000989680432f6c4908a9aa5f3444421f466b11645235c99b831b2a2de9e504d7ea299e52a202ce529808618eb3bfc0addf13d8c5f2df821d81e18f9bc61583510b322d067d46323b0a572635c06a049c0a2a929e3c8184a50cf6a8b95708c25834ade456f399015a0000000000989680864cb9828254d712f8e59b164fc6a9402dc4e6c59065e38cff24f5323c8c5da888a0f97e5ee4ba1e11b0674b0a0d06204c1dfa247c370cd4be3e799fc4f6f48d977ac7ca0aeb060adb030a02080b1213677265656e6669656c645f393030302d3132311802220c08b2d7f3a10610e8d2adb3032a480a20ec6ecb5db4ffb17fabe40c60ca7b8441e9c5d77585d0831186f3c37aa16e9c15122408011220a2ab9e1eb9ea52812f413526e424b326aff2f258a56e00d690db9f805b60fe7e32200f40aeff672e8309b7b0aefbb9a1ae3d4299b5c445b7d54e8ff398488467f0053a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85542203c350cd55b99dc6c2b7da9bef5410fbfb869fede858e7b95bf7ca294e228bb404a203c350cd55b99dc6c2b7da9bef5410fbfb869fede858e7b95bf7ca294e228bb405220294d8fbd0b94b767a7eba9840f299a3586da7fe6b5dead3b7eecba193c400f935a20bc50557c12d7392b0d07d75df0b61232d48f86a74fdea6d1485d9be6317d268c6220e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8556a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85572146699336aa109d1beab3946198c8e59f3b2cbd92f7a4065e3cd89e315ca39d87dee92835b98f8b8ec0861d6d9bb2c60156df5d375b3ceb1fbe71af6a244907d62548a694165caa660fec7a9b4e7b9198191361c71be0b128a0308021a480a20726abd0fdbfb6f779b0483e6e4b4b6f12241f6ea2bf374233ab1a316692b6415122408011220159f10ff15a8b58fc67a92ffd7f33c8cd407d4ce81b04ca79177dfd00ca19a67226808021214050cff76cc632760ba9db796c046004c900967361a0c08b3d7f3a10610808cadba03224080713027ffb776a702d78fd0406205c629ba473e1f8d6af646190f6eb9262cd67d69be90d10e597b91e06d7298eb6fa4b8f1eb7752ebf352a1f51560294548042268080212146699336aa109d1beab3946198c8e59f3b2cbd92f1a0c08b3d7f3a10610b087c1c00322405e2ddb70acfe4904438be3d9f4206c0ace905ac4fc306a42cfc9e86268950a0fbfd6ec5f526d3e41a3ef52bf9f9f358e3cb4c3feac76c762fa3651c1244fe004226808021214c55765fd2d0570e869f6ac22e7f2916a35ea300d1a0c08b3d7f3a10610f0b3d492032240ca17898bd22232fc9374e1188636ee321a396444a5b1a79f7628e4a11f265734b2ab50caf21e8092c55d701248e82b2f011426cb35ba22043b497a6b4661930612a0050aa8010a14050cff76cc632760ba9db796c046004c9009673612220a20e33f6e876d63791ebd05ff617a1b4f4ad1aa2ce65e3c3a9cdfb33e0ffa7e84231880ade2042080a6bbf6ffffffffff012a30a0805521b5b7ae56eb3fb24555efbfe59e1622bfe9f7be8c9022e9b3f2442739c1ce870b9adee169afe60f674edd7c86321415154514f68ce65a0d9eecc578c0ab12da0a2a283a14ee7a2a6a44d427f6949eeb8f12ea9fbb2501da880aa2010a146699336aa109d1beab3946198c8e59f3b2cbd92f12220a20451c5363d89052fde8351895eeea166ce5373c36e31b518ed191d0c599aa0f5b1880ade2042080ade2042a30831b2a2de9e504d7ea299e52a202ce529808618eb3bfc0addf13d8c5f2df821d81e18f9bc61583510b322d067d46323b3214432f6c4908a9aa5f3444421f466b11645235c99b3a14a0a7769429468054e19059af4867da0a495567e50aa2010a14c55765fd2d0570e869f6ac22e7f2916a35ea300d12220a200a572635c06a049c0a2a929e3c8184a50cf6a8b95708c25834ade456f399015a1880ade2042080ade2042a309065e38cff24f5323c8c5da888a0f97e5ee4ba1e11b0674b0a0d06204c1dfa247c370cd4be3e799fc4f6f48d977ac7ca3214864cb9828254d712f8e59b164fc6a9402dc4e6c53a143139916d97df0c589312b89950b6ab9795f34d1a12a8010a14050cff76cc632760ba9db796c046004c9009673612220a20e33f6e876d63791ebd05ff617a1b4f4ad1aa2ce65e3c3a9cdfb33e0ffa7e84231880ade2042080a6bbf6ffffffffff012a30a0805521b5b7ae56eb3fb24555efbfe59e1622bfe9f7be8c9022e9b3f2442739c1ce870b9adee169afe60f674edd7c86321415154514f68ce65a0d9eecc578c0ab12da0a2a283a14ee7a2a6a44d427f6949eeb8f12ea9fbb2501da88" + + input, err := hex.DecodeString(inputStr) + require.NoError(t, err) + + cs, block, err := v2.DecodeLightBlockValidationInput(input, false) + require.NoError(t, err) + + duplicate := cs.ValidatorSet.Validators[0].Copy() + cs.ValidatorSet = &types.ValidatorSet{Validators: []*types.Validator{duplicate.Copy(), duplicate.Copy(), duplicate.Copy()}} + csBytes, err := cs.EncodeConsensusState() + require.NoError(t, err) + + blockProto, err := block.ToProto() + require.NoError(t, err) + blockBytes, err := blockProto.Marshal() + require.NoError(t, err) + + mutatedInput := make([]byte, 32) + binary.BigEndian.PutUint64(mutatedInput[24:], uint64(len(csBytes))) + mutatedInput = append(mutatedInput, csBytes...) + mutatedInput = append(mutatedInput, blockBytes...) + + pasteurPrecompile := ActivePrecompiledContracts(params.Rules{IsOsaka: true, IsMendel: true, IsPasteur: true})[common.BytesToAddress([]byte{0x67})] + _, err = pasteurPrecompile.Run(mutatedInput) + require.ErrorContains(t, err, "duplicate validator") + + legacyPrecompile := ActivePrecompiledContracts(params.Rules{IsOsaka: true})[common.BytesToAddress([]byte{0x67})] + _, err = legacyPrecompile.Run(mutatedInput) + require.NoError(t, err) +} + func TestSecp256k1SignatureRecover(t *testing.T) { // local key { diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 684a51080a..4e182425d1 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -25,6 +25,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" ) // precompiledTest defines the input/output pairs for precompiled contract tests. @@ -353,6 +354,25 @@ func TestPrecompiledBLS12381MapG2(t *testing.T) { testJson("blsMapG2", "f10 func TestPrecompiledBlsSignatureVerify(t *testing.T) { testJson("blsSignatureVerify", "66", t) } +func TestActivePrecompiledContractsUsesPasteurVariants(t *testing.T) { + rules := params.Rules{IsOsaka: true, IsMendel: true, IsPasteur: true} + precompiles := ActivePrecompiledContracts(rules) + + requirePrecompile := func(addr byte) PrecompiledContract { + t.Helper() + + precompile, ok := precompiles[common.BytesToAddress([]byte{addr})] + if !ok { + t.Fatalf("missing precompile 0x%02x", addr) + } + return precompile + } + + if got := requirePrecompile(0x67).Name(); got != "COMET_BFT_LIGHT_BLOCK_VALIDATE_PASTEUR" { + t.Fatalf("unexpected Pasteur 0x67 precompile: %s", got) + } +} + func TestPrecompiledPointEvaluation(t *testing.T) { testJson("pointEvaluation", "0a", t) } func BenchmarkPrecompiledPointEvaluation(b *testing.B) { benchJson("pointEvaluation", "0a", b) } diff --git a/core/vm/lightclient/v2/lightclient.go b/core/vm/lightclient/v2/lightclient.go index b531e29111..9ab4b7d0c5 100644 --- a/core/vm/lightclient/v2/lightclient.go +++ b/core/vm/lightclient/v2/lightclient.go @@ -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) { +func DecodeConsensusState(input []byte, requireUniqueValidators bool) (ConsensusState, error) { minimumLength := chainIDLength + heightLength + validatorSetHashLength inputLen := uint64(len(input)) if inputLen <= minimumLength || (inputLen-minimumLength)%singleValidatorBytesLength != 0 { @@ -192,6 +267,11 @@ func DecodeConsensusState(input []byte) (ConsensusState, error) { Validators: validatorSet, }, } + if requireUniqueValidators { + if err := validateUniqueValidatorSet(consensusState.ValidatorSet); err != nil { + return ConsensusState{}, err + } + } return consensusState, nil } @@ -199,7 +279,7 @@ func DecodeConsensusState(input []byte) (ConsensusState, error) { // input: // consensus state length | consensus state | light block | // 32 bytes | | | -func DecodeLightBlockValidationInput(input []byte) (*ConsensusState, *types.LightBlock, error) { +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 } diff --git a/core/vm/lightclient/v2/lightclient_test.go b/core/vm/lightclient/v2/lightclient_test.go index 0a7d43f525..9c7bfae833 100644 --- a/core/vm/lightclient/v2/lightclient_test.go +++ b/core/vm/lightclient/v2/lightclient_test.go @@ -3,6 +3,7 @@ package v2 import ( "bytes" + "encoding/binary" "encoding/hex" "testing" @@ -144,7 +145,7 @@ func TestDecodeConsensusState(t *testing.T) { csBytes, err := hex.DecodeString(testcase.consensusStateBytes) require.NoError(t, err) - cs, err := DecodeConsensusState(csBytes) + cs, err := DecodeConsensusState(csBytes, false) require.NoError(t, err) if cs.ChainID != testcase.chainID { @@ -182,7 +183,7 @@ func TestConsensusStateApplyLightBlock(t *testing.T) { block, err := types.LightBlockFromProto(&lbpb) require.NoError(t, err) - cs, err := DecodeConsensusState(csBytes) + cs, err := DecodeConsensusState(csBytes, false) require.NoError(t, err) validatorSetChanged, err := cs.ApplyLightBlock(block, true) require.NoError(t, err) @@ -196,3 +197,125 @@ func TestConsensusStateApplyLightBlock(t *testing.T) { } } } + +func TestDecodeConsensusStateWithValidationRejectsDuplicateValidators(t *testing.T) { + csBytes, err := hex.DecodeString(testcases[0].consensusStateBytes) + require.NoError(t, err) + + validatorBytes := csBytes[chainIDLength+heightLength+validatorSetHashLength:] + duplicateConsensusState := append([]byte{}, csBytes[:chainIDLength+heightLength+validatorSetHashLength]...) + duplicateConsensusState = append(duplicateConsensusState, validatorBytes...) + duplicateConsensusState = append(duplicateConsensusState, validatorBytes...) + + _, err = DecodeConsensusState(duplicateConsensusState, true) + require.ErrorContains(t, err, "duplicate validator") +} + +func TestValidateUniqueValidatorSetRejectsDuplicateBridgeKeys(t *testing.T) { + makeValidator := func(info validatorInfo) *types.Validator { + t.Helper() + + pubKeyBytes, err := hex.DecodeString(info.pubKey) + require.NoError(t, err) + relayerAddress, err := hex.DecodeString(info.relayerAddress) + require.NoError(t, err) + relayerBlsKey, err := hex.DecodeString(info.relayerBlsKey) + require.NoError(t, err) + + pubkey := ed25519.PubKey(make([]byte, ed25519.PubKeySize)) + copy(pubkey[:], pubKeyBytes) + validator := types.NewValidator(pubkey, info.votingPower) + validator.SetRelayerAddress(relayerAddress) + validator.SetBlsKey(relayerBlsKey) + return validator + } + + t.Run("duplicate bls key", func(t *testing.T) { + first := makeValidator(testcases[1].vals[0]) + second := makeValidator(testcases[1].vals[1]) + second.SetBlsKey(append([]byte{}, first.BlsKey...)) + + err := validateUniqueValidatorSet(&types.ValidatorSet{Validators: []*types.Validator{first, second}}) + require.ErrorContains(t, err, "duplicate validator bls key") + }) + + t.Run("duplicate relayer address", func(t *testing.T) { + first := makeValidator(testcases[1].vals[0]) + second := makeValidator(testcases[1].vals[1]) + second.SetRelayerAddress(append([]byte{}, first.RelayerAddress...)) + + err := validateUniqueValidatorSet(&types.ValidatorSet{Validators: []*types.Validator{first, second}}) + require.ErrorContains(t, err, "duplicate validator relayer address") + }) +} + +func TestDecodeConsensusStateWithValidationAllowsUnsetBridgeKeys(t *testing.T) { + makeValidator := func(seed byte) *types.Validator { + t.Helper() + + pubkey := ed25519.PubKey(make([]byte, ed25519.PubKeySize)) + for i := range pubkey { + pubkey[i] = seed + byte(i) + 1 + } + + return types.NewValidator(pubkey, 100) + } + + consensusState := ConsensusState{ + ChainID: "chain_9000-121", + Height: 1, + NextValidatorSetHash: bytes.Repeat([]byte{0xAB}, int(validatorSetHashLength)), + ValidatorSet: &types.ValidatorSet{ + Validators: []*types.Validator{ + makeValidator(0x01), + makeValidator(0x02), + }, + }, + } + + csBytes, err := consensusState.EncodeConsensusState() + require.NoError(t, err) + + _, err = DecodeConsensusState(csBytes, true) + require.NoError(t, err) +} + +func TestIsZeroBytesRequiresNonEmptyInput(t *testing.T) { + require.False(t, isZeroBytes(nil)) + require.False(t, isZeroBytes([]byte{})) + require.True(t, isZeroBytes([]byte{0x00, 0x00})) + require.False(t, isZeroBytes([]byte{0x00, 0x01})) +} + +func TestDecodeLightBlockValidationInputAllowsUnsetBlockBridgeKeys(t *testing.T) { + csBytes, err := hex.DecodeString(applyBlocksTestcases[0].consensusStateBytes) + require.NoError(t, err) + + blockBytes, err := hex.DecodeString(applyBlocksTestcases[0].lightBlockBytes) + require.NoError(t, err) + + var lbpb tmproto.LightBlock + err = lbpb.Unmarshal(blockBytes) + require.NoError(t, err) + + block, err := types.LightBlockFromProto(&lbpb) + require.NoError(t, err) + + for _, validator := range block.ValidatorSet.Validators { + validator.SetBlsKey(nil) + validator.SetRelayerAddress(nil) + } + + blockProto, err := block.ToProto() + require.NoError(t, err) + blockBytes, err = blockProto.Marshal() + require.NoError(t, err) + + input := make([]byte, consensusStateLengthBytesLength) + binary.BigEndian.PutUint64(input[consensusStateLengthBytesLength-uint64TypeLength:], uint64(len(csBytes))) + input = append(input, csBytes...) + input = append(input, blockBytes...) + + _, _, err = DecodeLightBlockValidationInput(input, true) + require.NoError(t, err) +}