diff --git a/app/ante/cosmos_checktx.go b/app/ante/cosmos_checktx.go index 111b6261b3..e184e4b276 100644 --- a/app/ante/cosmos_checktx.go +++ b/app/ante/cosmos_checktx.go @@ -400,17 +400,22 @@ func CheckSignatures(ctx sdk.Context, txConfig client.TxConfig, tx sdk.Tx, signe return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs)) } var events sdk.Events + // CheckTx and ReCheckTx discard these events (see CosmosCheckTxAnte); building them + // still runs SignatureDataToBz + base64 per signer — measurable CPU/alloc on hot path. + skipSigEvents := ctx.IsCheckTx() || ctx.IsReCheckTx() for i, sig := range sigs { - events = append(events, sdk.NewEvent(sdk.EventTypeTx, - sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signerAddrs[i], sig.Sequence)), - )) - if sigBzs, err := authante.SignatureDataToBz(sig.Data); err != nil { - return nil, err - } else { - for _, sigBz := range sigBzs { - events = append(events, sdk.NewEvent(sdk.EventTypeTx, - sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)), - )) + if !skipSigEvents { + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signerAddrs[i], sig.Sequence)), + )) + if sigBzs, err := authante.SignatureDataToBz(sig.Data); err != nil { + return nil, err + } else { + for _, sigBz := range sigBzs { + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)), + )) + } } } diff --git a/app/cosmos_checktx_test.go b/app/cosmos_checktx_test.go new file mode 100644 index 0000000000..a204b33ec2 --- /dev/null +++ b/app/cosmos_checktx_test.go @@ -0,0 +1,82 @@ +package app_test + +import ( + "testing" + "time" + + "github.com/sei-protocol/sei-chain/app" + anteante "github.com/sei-protocol/sei-chain/app/ante" + "github.com/sei-protocol/sei-chain/sei-cosmos/client/tx" + "github.com/sei-protocol/sei-chain/sei-cosmos/testutil/testdata" + sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" + "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx/signing" + authsigning "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/signing" + authtypes "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/types" + tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" + "github.com/stretchr/testify/require" +) + +// TestCheckSignaturesSkipsEventsOnCheckTx verifies that signature events +// (account sequence + signature bytes) are not built during CheckTx or +// ReCheckTx, but are built during DeliverTx. +func TestCheckSignaturesSkipsEventsOnCheckTx(t *testing.T) { + testApp := app.Setup(t, false, false, false) + ctx := testApp.NewContext(false, tmproto.Header{Height: 1, ChainID: "sei-test", Time: time.Now().UTC()}) + + encodingConfig := app.MakeEncodingConfig() + txConfig := encodingConfig.TxConfig + + privKey, pubKey, addr := testdata.KeyTestPubAddr() + + acc := authtypes.NewBaseAccount(addr, pubKey, 0, 0) + signerAccounts := []authtypes.AccountI{acc} + authParams := authtypes.DefaultParams() + + // Build and sign a tx. + txBuilder := txConfig.NewTxBuilder() + err := txBuilder.SetMsgs(testdata.NewTestMsg(addr)) + require.NoError(t, err) + txBuilder.SetFeeAmount(testdata.NewTestFeeAmount()) + txBuilder.SetGasLimit(testdata.NewTestGasLimit()) + + // First pass: set empty sigs so signer info is populated. + sigsV2 := []signing.SignatureV2{{ + PubKey: pubKey, + Data: &signing.SingleSignatureData{ + SignMode: txConfig.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: 0, + }} + require.NoError(t, txBuilder.SetSignatures(sigsV2...)) + + // Second pass: real signature. + signerData := authsigning.SignerData{ChainID: "sei-test", AccountNumber: 0, Sequence: 0} + sigV2, err := tx.SignWithPrivKey(txConfig.SignModeHandler().DefaultMode(), signerData, txBuilder, privKey, txConfig, 0) + require.NoError(t, err) + require.NoError(t, txBuilder.SetSignatures(sigV2)) + + signedTx := txBuilder.GetTx() + + // Use an infinite gas meter so signature verification gas consumption doesn't + // cause a panic when the context has no real store behind it. + gasCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter(1, 1)) + + // DeliverTx context: both flags false → events must be populated. + deliverCtx := gasCtx.WithIsCheckTx(false) + events, err := anteante.CheckSignatures(deliverCtx, txConfig, signedTx, signerAccounts, authParams) + require.NoError(t, err) + require.NotEmpty(t, events, "expected signature events in DeliverTx context") + + // CheckTx context → events must be empty. + checkCtx := gasCtx.WithIsCheckTx(true) + events, err = anteante.CheckSignatures(checkCtx, txConfig, signedTx, signerAccounts, authParams) + require.NoError(t, err) + require.Empty(t, events, "expected no signature events in CheckTx context") + + // ReCheckTx context → events must be empty. + recheckCtx := gasCtx.WithIsCheckTx(false).WithIsReCheckTx(true) + events, err = anteante.CheckSignatures(recheckCtx, txConfig, signedTx, signerAccounts, authParams) + require.NoError(t, err) + require.Empty(t, events, "expected no signature events in ReCheckTx context") +}