From 3e7ec9a4c022ef715b25d1e98caf4de9681a8d18 Mon Sep 17 00:00:00 2001 From: gheorghestrimtu Date: Thu, 19 Feb 2026 10:14:21 +0200 Subject: [PATCH] fix(cosmos): avoid "codec sealed" panic in injective proposal registration - remove duplicate proposal `amino.RegisterConcrete` calls from `proposal.go` init - keep proposal amino registration centralized in `RegisterLegacyAminoCodec` - add regression tests for proposal registration and sealed-codec panic behavior - document why proposal init must not register on sealed module amino --- .../adapters/injective/types/proposal.go | 5 +- .../types/proposal_regression_test.go | 51 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 pkg/cosmos/adapters/injective/types/proposal_regression_test.go diff --git a/pkg/cosmos/adapters/injective/types/proposal.go b/pkg/cosmos/adapters/injective/types/proposal.go index a460d29c..85deb6b4 100644 --- a/pkg/cosmos/adapters/injective/types/proposal.go +++ b/pkg/cosmos/adapters/injective/types/proposal.go @@ -13,8 +13,9 @@ const ( func init() { gov.RegisterProposalType(ProposalTypeOcrSetConfig) - amino.RegisterConcrete(&SetConfigProposal{}, "injective/OcrSetConfigProposal", nil) - amino.RegisterConcrete(&SetBatchConfigProposal{}, "injective/OcrSetBatchConfigProposal", nil) + // Keep Amino concrete registration out of this init. The package-level + // LegacyAmino in codec.go is sealed during init, and registering here can + // trigger "panic: codec sealed" depending on init order. } // Implements Proposal Interface diff --git a/pkg/cosmos/adapters/injective/types/proposal_regression_test.go b/pkg/cosmos/adapters/injective/types/proposal_regression_test.go new file mode 100644 index 00000000..cdfcea61 --- /dev/null +++ b/pkg/cosmos/adapters/injective/types/proposal_regression_test.go @@ -0,0 +1,51 @@ +package types + +import ( + "fmt" + "strings" + "testing" + + gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" +) + +func TestProposalTypeRegistered(t *testing.T) { + t.Parallel() + + if !gov.IsValidProposalType(ProposalTypeOcrSetConfig) { + t.Fatalf("proposal type %q is not registered", ProposalTypeOcrSetConfig) + } +} + +func TestSetConfigProposalAminoRegistration(t *testing.T) { + t.Parallel() + + var content gov.Content = &SetConfigProposal{ + Title: "title", + Description: "description", + } + + bz, err := amino.MarshalJSON(content) + if err != nil { + t.Fatalf("marshal gov.Content via amino: %v", err) + } + + if !strings.Contains(string(bz), "ocr/SetConfigProposal") { + t.Fatalf("expected amino type tag to include ocr/SetConfigProposal, got: %s", string(bz)) + } +} + +func TestLegacyAminoRegistrationPanicsAfterSeal(t *testing.T) { + t.Parallel() + + defer func() { + r := recover() + if r == nil { + t.Fatal("expected panic when registering on sealed legacy amino codec") + } + if !strings.Contains(fmt.Sprint(r), "codec sealed") { + t.Fatalf("expected panic to contain %q, got: %v", "codec sealed", r) + } + }() + + amino.RegisterConcrete(&SetConfigProposal{}, "injective/OcrSetConfigProposal", nil) +}