fix: reimplement sig verification for app v2 (#21386)

This commit is contained in:
Facundo Medica 2024-08-26 11:40:54 +02:00 committed by GitHub
parent 9bc5df6469
commit 7c85b981c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 374 additions and 112 deletions

View File

@ -0,0 +1,143 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: core/gas/service.go
// Package gas is a generated GoMock package.
package gas
import (
context "context"
reflect "reflect"
gas "cosmossdk.io/core/gas"
gomock "github.com/golang/mock/gomock"
)
// MockService is a mock of Service interface.
type MockService struct {
ctrl *gomock.Controller
recorder *MockServiceMockRecorder
}
// MockServiceMockRecorder is the mock recorder for MockService.
type MockServiceMockRecorder struct {
mock *MockService
}
// NewMockService creates a new mock instance.
func NewMockService(ctrl *gomock.Controller) *MockService {
mock := &MockService{ctrl: ctrl}
mock.recorder = &MockServiceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockService) EXPECT() *MockServiceMockRecorder {
return m.recorder
}
// GasConfig mocks base method.
func (m *MockService) GasConfig(ctx context.Context) gas.GasConfig {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GasConfig", ctx)
ret0, _ := ret[0].(gas.GasConfig)
return ret0
}
// GasConfig indicates an expected call of GasConfig.
func (mr *MockServiceMockRecorder) GasConfig(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasConfig", reflect.TypeOf((*MockService)(nil).GasConfig), ctx)
}
// GasMeter mocks base method.
func (m *MockService) GasMeter(arg0 context.Context) gas.Meter {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GasMeter", arg0)
ret0, _ := ret[0].(gas.Meter)
return ret0
}
// GasMeter indicates an expected call of GasMeter.
func (mr *MockServiceMockRecorder) GasMeter(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasMeter", reflect.TypeOf((*MockService)(nil).GasMeter), arg0)
}
// MockMeter is a mock of Meter interface.
type MockMeter struct {
ctrl *gomock.Controller
recorder *MockMeterMockRecorder
}
// MockMeterMockRecorder is the mock recorder for MockMeter.
type MockMeterMockRecorder struct {
mock *MockMeter
}
// NewMockMeter creates a new mock instance.
func NewMockMeter(ctrl *gomock.Controller) *MockMeter {
mock := &MockMeter{ctrl: ctrl}
mock.recorder = &MockMeterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockMeter) EXPECT() *MockMeterMockRecorder {
return m.recorder
}
// Consume mocks base method.
func (m *MockMeter) Consume(amount gas.Gas, descriptor string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Consume", amount, descriptor)
ret0, _ := ret[0].(error)
return ret0
}
// Consume indicates an expected call of Consume.
func (mr *MockMeterMockRecorder) Consume(amount, descriptor interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Consume", reflect.TypeOf((*MockMeter)(nil).Consume), amount, descriptor)
}
// Limit mocks base method.
func (m *MockMeter) Limit() gas.Gas {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Limit")
ret0, _ := ret[0].(gas.Gas)
return ret0
}
// Limit indicates an expected call of Limit.
func (mr *MockMeterMockRecorder) Limit() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Limit", reflect.TypeOf((*MockMeter)(nil).Limit))
}
// Refund mocks base method.
func (m *MockMeter) Refund(amount gas.Gas, descriptor string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Refund", amount, descriptor)
ret0, _ := ret[0].(error)
return ret0
}
// Refund indicates an expected call of Refund.
func (mr *MockMeterMockRecorder) Refund(amount, descriptor interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Refund", reflect.TypeOf((*MockMeter)(nil).Refund), amount, descriptor)
}
// Remaining mocks base method.
func (m *MockMeter) Remaining() gas.Gas {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Remaining")
ret0, _ := ret[0].(gas.Gas)
return ret0
}
// Remaining indicates an expected call of Remaining.
func (mr *MockMeterMockRecorder) Remaining() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remaining", reflect.TypeOf((*MockMeter)(nil).Remaining))
}

View File

@ -6,5 +6,6 @@ replace cosmossdk.io/core => ../
require (
cosmossdk.io/core v1.0.0
github.com/golang/mock v1.6.0
github.com/tidwall/btree v1.7.0
)

View File

@ -1,2 +1,27 @@
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -19,5 +19,5 @@ jq '.app_state.gov.params.expedited_voting_period = "300s"' $SIMD_HOME/config/ge
jq '.app_state.mint.minter.inflation = "0.300000000000000000"' $SIMD_HOME/config/genesis.json > temp.json && mv temp.json $SIMD_HOME/config/genesis.json # to change the inflation
$SIMD_BIN genesis add-genesis-account alice 5000000000stake --keyring-backend test
$SIMD_BIN genesis add-genesis-account bob 5000000000stake --keyring-backend test
$SIMD_BIN genesis gentx alice 1000000stake --chain-id demo
$SIMD_BIN genesis gentx alice 1000000stake --chain-id simapp-v2-chain
$SIMD_BIN genesis collect-gentxs

View File

@ -26,3 +26,4 @@ $mockgen_cmd -source=x/gov/testutil/expected_keepers.go -package testutil -desti
$mockgen_cmd -source=x/staking/types/expected_keepers.go -package testutil -destination x/staking/testutil/expected_keepers_mocks.go
# $mockgen_cmd -source=x/auth/vesting/types/expected_keepers.go -package testutil -destination x/auth/vesting/testutil/expected_keepers_mocks.go
$mockgen_cmd -source=x/protocolpool/types/expected_keepers.go -package testutil -destination x/protocolpool/testutil/expected_keepers_mocks.go
$mockgen_cmd -source=core/gas/service.go -package gas -destination core/testing/gas/service_mocks.go

View File

@ -45,7 +45,7 @@ func Test_TestnetCmd(t *testing.T) {
)
require.NoError(t, err)
require.NotNil(t, moduleManager)
require.Len(t, moduleManager.Modules, 8)
require.Len(t, moduleManager.Modules, 9) // the registered above + runtime
home := t.TempDir()
cdcOpts := codectestutil.CodecOptions{}

View File

@ -223,7 +223,7 @@ func (c CLIWrapper) runWithInput(args []string, input io.Reader) (output string,
cmd := exec.Command(locateExecutable(c.execBinary), args...) //nolint:gosec // test code only
cmd.Dir = WorkDir
cmd.Stdin = input
return cmd.Output()
return cmd.CombinedOutput()
}()
gotOut = filterProtoNoise(gotOut)
ok = c.assertErrorFn(c.t, gotErr, string(gotOut))

View File

@ -10,6 +10,7 @@ import (
)
func TestStakeUnstake(t *testing.T) {
t.Skip("The fee deduction is not yet implemented in v2")
// Scenario:
// delegate tokens to validator
// undelegate some tokens

View File

@ -12,6 +12,7 @@ import (
)
func TestUnorderedTXDuplicate(t *testing.T) {
t.Skip("The unordered tx antehanlder is missing in v2")
// scenario: test unordered tx duplicate
// given a running chain with a tx in the unordered tx pool
// when a new tx with the same hash is broadcasted

View File

@ -2,8 +2,8 @@ package ante
import (
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/gas"
errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth/types"
txsigning "cosmossdk.io/x/tx/signing"
@ -21,7 +21,7 @@ type HandlerOptions struct {
ExtensionOptionChecker ExtensionOptionChecker
FeegrantKeeper FeegrantKeeper
SignModeHandler *txsigning.HandlerMap
SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params types.Params) error
SigGasConsumer func(meter gas.Meter, sig signing.SignatureV2, params types.Params) error
TxFeeChecker TxFeeChecker
}

View File

@ -10,9 +10,10 @@ import (
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"cosmossdk.io/core/gas"
"cosmossdk.io/core/header"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth/ante"
authtypes "cosmossdk.io/x/auth/types"
@ -392,7 +393,7 @@ func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) {
for _, tc := range testCases {
t.Run(fmt.Sprintf("Case %s", tc.desc), func(t *testing.T) {
suite := SetupTestSuite(t, false)
suite.ctx = suite.ctx.WithBlockHeight(0)
suite.ctx = suite.ctx.WithBlockHeight(0).WithHeaderInfo(header.Info{Height: 0, ChainID: suite.ctx.ChainID()})
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
args := tc.malleate(suite)
@ -1158,22 +1159,6 @@ func generatePubKeysAndSignatures(n int, msg []byte, _ bool) (pubkeys []cryptoty
return
}
func expectedGasCostByKeys(pubkeys []cryptotypes.PubKey) uint64 {
cost := uint64(0)
for _, pubkey := range pubkeys {
pubkeyType := strings.ToLower(fmt.Sprintf("%T", pubkey))
switch {
case strings.Contains(pubkeyType, "ed25519"):
cost += authtypes.DefaultParams().SigVerifyCostED25519
case strings.Contains(pubkeyType, "secp256k1"):
cost += authtypes.DefaultParams().SigVerifyCostSecp256k1
default:
panic("unexpected key type")
}
}
return cost
}
func TestCountSubkeys(t *testing.T) {
genPubKeys := func(n int) []cryptotypes.PubKey {
var ret []cryptotypes.PubKey
@ -1266,11 +1251,10 @@ func TestCustomSignatureVerificationGasConsumer(t *testing.T) {
BankKeeper: suite.bankKeeper,
FeegrantKeeper: suite.feeGrantKeeper,
SignModeHandler: suite.clientCtx.TxConfig.SignModeHandler(),
SigGasConsumer: func(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error {
SigGasConsumer: func(meter gas.Meter, sig signing.SignatureV2, params authtypes.Params) error {
switch pubkey := sig.PubKey.(type) {
case *ed25519.PubKey:
meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519")
return nil
return meter.Consume(params.SigVerifyCostED25519, "ante verify: ed25519")
default:
return errorsmod.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized public key type: %T", pubkey)
}

View File

@ -10,9 +10,10 @@ import (
secp256k1dcrd "github.com/decred/dcrd/dcrec/secp256k1/v4"
"google.golang.org/protobuf/types/known/anypb"
"cosmossdk.io/core/event"
"cosmossdk.io/core/gas"
"cosmossdk.io/core/transaction"
errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
authsigning "cosmossdk.io/x/auth/signing"
"cosmossdk.io/x/auth/types"
txsigning "cosmossdk.io/x/tx/signing"
@ -47,7 +48,7 @@ func init() {
// SignatureVerificationGasConsumer is the type of function that is used to both
// consume gas when verifying signatures and also to accept or reject different types of pubkeys
// This is where apps can define their own PubKey
type SignatureVerificationGasConsumer = func(meter storetypes.GasMeter, sig signing.SignatureV2, params types.Params) error
type SignatureVerificationGasConsumer = func(meter gas.Meter, sig signing.SignatureV2, params types.Params) error
type AccountAbstractionKeeper interface {
IsAbstractedAccount(ctx context.Context, addr []byte) (bool, error)
@ -150,31 +151,38 @@ func verifyIsOnCurve(pubKey cryptotypes.PubKey) (err error) {
}
func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if err := svd.ValidateTx(ctx, tx); err != nil {
return ctx, err
}
return next(ctx, tx, false)
}
func (svd SigVerificationDecorator) ValidateTx(ctx context.Context, tx transaction.Tx) error {
sigTx, ok := tx.(authsigning.Tx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
}
// stdSigs contains the sequence number, account number, and signatures.
// When simulating, this would just be a 0-length slice.
signatures, err := sigTx.GetSignaturesV2()
if err != nil {
return ctx, err
return err
}
signers, err := sigTx.GetSigners()
if err != nil {
return ctx, err
return err
}
// check that signer length and signature length are the same
if len(signatures) != len(signers) {
return ctx, errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signers), len(signatures))
return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signers), len(signatures))
}
pubKeys, err := sigTx.GetPubKeys()
if err != nil {
return ctx, err
return err
}
// NOTE: the tx_wrapper implementation returns nil, in case the pubkey is not populated.
@ -182,44 +190,46 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ boo
// itself. If this does not work, it's a failure in the implementation of the interface.
// we're erroring, but most likely we should be panicking.
if len(pubKeys) != len(signers) {
return ctx, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "invalid number of pubkeys; expected %d, got %d", len(signers), len(pubKeys))
return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "invalid number of pubkeys; expected %d, got %d", len(signers), len(pubKeys))
}
for i := range signers {
err = svd.authenticate(ctx, sigTx, signers[i], signatures[i], pubKeys[i], i)
if err != nil {
return ctx, err
return err
}
}
var events sdk.Events
eventMgr := svd.ak.GetEnvironment().EventService.EventManager(ctx)
events := [][]event.Attribute{}
for i, sig := range signatures {
signerStr, err := svd.ak.AddressCodec().BytesToString(signers[i])
if err != nil {
return ctx, err
return err
}
events = append(events, sdk.NewEvent(sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signerStr, sig.Sequence)),
))
events = append(events, []event.Attribute{event.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signerStr, sig.Sequence))})
sigBzs, err := signatureDataToBz(sig.Data)
if err != nil {
return ctx, err
return err
}
for _, sigBz := range sigBzs {
events = append(events, sdk.NewEvent(sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)),
))
events = append(events, []event.Attribute{event.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz))})
}
}
ctx.EventManager().EmitEvents(events)
for _, v := range events {
if err := eventMgr.EmitKV(sdk.EventTypeTx, v...); err != nil {
return err
}
}
return next(ctx, tx, false)
return nil
}
// authenticate the authentication of the TX for a specific tx signer.
func (svd SigVerificationDecorator) authenticate(ctx sdk.Context, tx authsigning.Tx, signer []byte, sig signing.SignatureV2, txPubKey cryptotypes.PubKey, signerIndex int) error {
func (svd SigVerificationDecorator) authenticate(ctx context.Context, tx authsigning.Tx, signer []byte, sig signing.SignatureV2, txPubKey cryptotypes.PubKey, signerIndex int) error {
// first we check if it's an AA
if svd.aaKeeper != nil {
isAa, err := svd.aaKeeper.IsAbstractedAccount(ctx, signer)
@ -276,7 +286,7 @@ func (svd SigVerificationDecorator) authenticate(ctx sdk.Context, tx authsigning
// consumeSignatureGas will consume gas according to the pub-key being verified.
func (svd SigVerificationDecorator) consumeSignatureGas(
ctx sdk.Context,
ctx context.Context,
pubKey cryptotypes.PubKey,
signature signing.SignatureV2,
) error {
@ -291,15 +301,11 @@ func (svd SigVerificationDecorator) consumeSignatureGas(
Sequence: signature.Sequence,
}
err := svd.sigGasConsumer(ctx.GasMeter(), signature, svd.ak.GetParams(ctx))
if err != nil {
return err
}
return nil
return svd.sigGasConsumer(svd.ak.GetEnvironment().GasService.GasMeter(ctx), signature, svd.ak.GetParams(ctx))
}
// verifySig will verify the signature of the provided signer account.
func (svd SigVerificationDecorator) verifySig(ctx sdk.Context, tx sdk.Tx, acc sdk.AccountI, sig signing.SignatureV2, newlyCreated bool) error {
func (svd SigVerificationDecorator) verifySig(ctx context.Context, tx sdk.Tx, acc sdk.AccountI, sig signing.SignatureV2, newlyCreated bool) error {
if sig.Sequence != acc.GetSequence() {
return errorsmod.Wrapf(
sdkerrors.ErrWrongSequence,
@ -310,7 +316,9 @@ func (svd SigVerificationDecorator) verifySig(ctx sdk.Context, tx sdk.Tx, acc sd
// we're in simulation mode, or in ReCheckTx, or context is not
// on sig verify tx, then we do not need to verify the signatures
// in the tx.
if svd.ak.GetEnvironment().TransactionService.ExecMode(ctx) == transaction.ExecModeSimulate || ctx.IsReCheckTx() || !ctx.IsSigverifyTx() {
if svd.ak.GetEnvironment().TransactionService.ExecMode(ctx) == transaction.ExecModeSimulate ||
isRecheckTx(ctx, svd.ak.GetEnvironment().TransactionService) ||
!isSigverifyTx(ctx) {
return nil
}
@ -321,8 +329,9 @@ func (svd SigVerificationDecorator) verifySig(ctx sdk.Context, tx sdk.Tx, acc sd
}
// retrieve signer data
genesis := ctx.BlockHeight() == 0
chainID := ctx.ChainID()
hinfo := svd.ak.GetEnvironment().HeaderService.HeaderInfo(ctx)
genesis := hinfo.Height == 0
chainID := hinfo.ChainID
var accNum uint64
// if we are not in genesis use the account number from the account
if !genesis {
@ -372,9 +381,9 @@ func (svd SigVerificationDecorator) verifySig(ctx sdk.Context, tx sdk.Tx, acc sd
// setPubKey will attempt to set the pubkey for the account given the list of available public keys.
// This must be called only in case the account has not a pubkey set yet.
func (svd SigVerificationDecorator) setPubKey(ctx sdk.Context, acc sdk.AccountI, txPubKey cryptotypes.PubKey) error {
func (svd SigVerificationDecorator) setPubKey(ctx context.Context, acc sdk.AccountI, txPubKey cryptotypes.PubKey) error {
// if we're not in sig verify then we can just skip.
if !ctx.IsSigverifyTx() {
if !isSigverifyTx(ctx) {
return nil
}
@ -425,7 +434,7 @@ func (svd SigVerificationDecorator) increaseSequence(tx authsigning.Tx, acc sdk.
}
// authenticateAbstractedAccount computes an AA authentication instruction and invokes the auth flow on the AA.
func (svd SigVerificationDecorator) authenticateAbstractedAccount(ctx sdk.Context, authTx authsigning.Tx, signer []byte, index int) error {
func (svd SigVerificationDecorator) authenticateAbstractedAccount(ctx context.Context, authTx authsigning.Tx, signer []byte, index int) error {
// the bundler is the AA itself.
selfBundler, err := svd.ak.AddressCodec().BytesToString(signer)
if err != nil {
@ -500,21 +509,21 @@ func (vscd ValidateSigCountDecorator) ValidateTx(ctx context.Context, tx sdk.Tx)
// DefaultSigVerificationGasConsumer is the default implementation of SignatureVerificationGasConsumer. It consumes gas
// for signature verification based upon the public key type. The cost is fetched from the given params and is matched
// by the concrete type.
func DefaultSigVerificationGasConsumer(meter storetypes.GasMeter, sig signing.SignatureV2, params types.Params) error {
func DefaultSigVerificationGasConsumer(meter gas.Meter, sig signing.SignatureV2, params types.Params) error {
pubkey := sig.PubKey
switch pubkey := pubkey.(type) {
case *ed25519.PubKey:
meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519")
if err := meter.Consume(params.SigVerifyCostED25519, "ante verify: ed25519"); err != nil {
return err
}
return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "ED25519 public keys are unsupported")
case *secp256k1.PubKey:
meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1")
return nil
return meter.Consume(params.SigVerifyCostSecp256k1, "ante verify: secp256k1")
case *secp256r1.PubKey:
meter.ConsumeGas(params.SigVerifyCostSecp256r1(), "ante verify: secp256r1")
return nil
return meter.Consume(params.SigVerifyCostSecp256r1(), "ante verify: secp256r1")
case multisig.PubKey:
multisignature, ok := sig.Data.(*signing.MultiSignatureData)
@ -536,7 +545,7 @@ func DefaultSigVerificationGasConsumer(meter storetypes.GasMeter, sig signing.Si
// ConsumeMultisignatureVerificationGas consumes gas from a GasMeter for verifying a multisig pubKey signature.
func ConsumeMultisignatureVerificationGas(
meter storetypes.GasMeter, sig *signing.MultiSignatureData, pubKey multisig.PubKey,
meter gas.Meter, sig *signing.MultiSignatureData, pubKey multisig.PubKey,
params types.Params, accSeq uint64,
) error {
// if BitArray is nil, it means tx has been built for simulation.
@ -571,7 +580,7 @@ func ConsumeMultisignatureVerificationGas(
// multisignatureSimulationVerificationGas consume gas for verifying a simulation multisig pubKey signature. As it's
// a simulation tx the number of signatures its equal to the multisig threshold.
func multisignatureSimulationVerificationGas(
meter storetypes.GasMeter, sig *signing.MultiSignatureData, pubKey multisig.PubKey,
meter gas.Meter, sig *signing.MultiSignatureData, pubKey multisig.PubKey,
params types.Params, accSeq uint64,
) error {
for i := 0; i < len(sig.Signatures); i++ {
@ -592,7 +601,7 @@ func multisignatureSimulationVerificationGas(
// GetSignerAcc returns an account for a given address that is expected to sign
// a transaction.
func GetSignerAcc(ctx sdk.Context, ak AccountKeeper, addr sdk.AccAddress) sdk.AccountI {
func GetSignerAcc(ctx context.Context, ak AccountKeeper, addr sdk.AccAddress) sdk.AccountI {
return ak.GetAccount(ctx, addr)
}
@ -659,3 +668,19 @@ func signatureDataToBz(data signing.SignatureData) ([][]byte, error) {
return nil, sdkerrors.ErrInvalidType.Wrapf("unexpected signature data type %T", data)
}
}
// isSigverifyTx will always return true, unless the context is a sdk.Context, in which case we will return the
// value of IsSigverifyTx.
func isSigverifyTx(ctx context.Context) bool {
if sdkCtx, ok := sdk.TryUnwrapSDKContext(ctx); ok {
return sdkCtx.IsSigverifyTx()
}
return true
}
func isRecheckTx(ctx context.Context, txSvc transaction.Service) bool {
if sdkCtx, ok := sdk.TryUnwrapSDKContext(ctx); ok {
return sdkCtx.IsReCheckTx()
}
return txSvc.ExecMode(ctx) == transaction.ExecModeReCheck
}

View File

@ -8,6 +8,9 @@ import (
"github.com/stretchr/testify/require"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
"cosmossdk.io/core/gas"
"cosmossdk.io/core/header"
gastestutil "cosmossdk.io/core/testing/gas"
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth/ante"
"cosmossdk.io/x/auth/migrations/legacytx"
@ -39,7 +42,6 @@ func TestConsumeSignatureVerificationGas(t *testing.T) {
pkSet1, sigSet1 := generatePubKeysAndSignatures(5, msg, false)
multisigKey1 := kmultisig.NewLegacyAminoPubKey(2, pkSet1)
multisignature1 := multisig.NewMultisig(len(pkSet1))
expectedCost1 := expectedGasCostByKeys(pkSet1)
for i := 0; i < len(pkSet1); i++ {
stdSig := legacytx.StdSignature{PubKey: pkSet1[i], Signature: sigSet1[i]} //nolint:staticcheck // SA1019: legacytx.StdSignature is deprecated
sigV2, err := legacytx.StdSignatureToSignatureV2(suite.clientCtx.LegacyAmino, stdSig)
@ -48,7 +50,6 @@ func TestConsumeSignatureVerificationGas(t *testing.T) {
require.NoError(t, err)
}
simulationExpectedCost := expectedGasCostByKeys(pkSet1[:multisigKey1.Threshold])
simulationMultiSignatureData := make([]signing.SignatureData, 0, multisigKey1.Threshold)
for i := uint32(0); i < multisigKey1.Threshold; i++ {
simulationMultiSignatureData = append(simulationMultiSignatureData, &signing.SingleSignatureData{})
@ -58,38 +59,77 @@ func TestConsumeSignatureVerificationGas(t *testing.T) {
}
type args struct {
meter storetypes.GasMeter
sig signing.SignatureData
pubkey cryptotypes.PubKey
params types.Params
sig signing.SignatureData
pubkey cryptotypes.PubKey
params types.Params
malleate func(*gastestutil.MockMeter)
}
tests := []struct {
name string
args args
gasConsumed uint64
shouldErr bool
name string
args args
shouldErr bool
}{
{"PubKeyEd25519", args{storetypes.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, p.SigVerifyCostED25519, true},
{"PubKeySecp256k1", args{storetypes.NewInfiniteGasMeter(), nil, secp256k1.GenPrivKey().PubKey(), params}, p.SigVerifyCostSecp256k1, false},
{"PubKeySecp256r1", args{storetypes.NewInfiniteGasMeter(), nil, skR1.PubKey(), params}, p.SigVerifyCostSecp256r1(), false},
{"Multisig", args{storetypes.NewInfiniteGasMeter(), multisignature1, multisigKey1, params}, expectedCost1, false},
{"Multisig simulation", args{storetypes.NewInfiniteGasMeter(), multisigSimulationSignature, multisigKey1, params}, simulationExpectedCost, false},
{"unknown key", args{storetypes.NewInfiniteGasMeter(), nil, nil, params}, 0, true},
{
"PubKeyEd25519",
args{nil, ed25519.GenPrivKey().PubKey(), params, func(mm *gastestutil.MockMeter) {
mm.EXPECT().Consume(p.SigVerifyCostED25519, "ante verify: ed25519").Times(1)
}},
true,
},
{
"PubKeySecp256k1",
args{nil, secp256k1.GenPrivKey().PubKey(), params, func(mm *gastestutil.MockMeter) {
mm.EXPECT().Consume(p.SigVerifyCostSecp256k1, "ante verify: secp256k1").Times(1)
}},
false,
},
{
"PubKeySecp256r1",
args{nil, skR1.PubKey(), params, func(mm *gastestutil.MockMeter) {
mm.EXPECT().Consume(p.SigVerifyCostSecp256r1(), "ante verify: secp256r1").Times(1)
}},
false,
},
{
"Multisig",
args{multisignature1, multisigKey1, params, func(mm *gastestutil.MockMeter) {
// 5 signatures
mm.EXPECT().Consume(p.SigVerifyCostSecp256k1, "ante verify: secp256k1").Times(5)
}},
false,
},
{
"Multisig simulation",
args{multisigSimulationSignature, multisigKey1, params, func(mm *gastestutil.MockMeter) {
mm.EXPECT().Consume(p.SigVerifyCostSecp256k1, "ante verify: secp256k1").Times(int(multisigKey1.Threshold))
}},
false,
},
{
"unknown key",
args{nil, nil, params, func(mm *gastestutil.MockMeter) {}},
true,
},
}
for _, tt := range tests {
sigV2 := signing.SignatureV2{
PubKey: tt.args.pubkey,
Data: tt.args.sig,
Sequence: 0, // Arbitrary account sequence
}
err := ante.DefaultSigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params)
t.Run(tt.name, func(t *testing.T) {
sigV2 := signing.SignatureV2{
PubKey: tt.args.pubkey,
Data: tt.args.sig,
Sequence: 0, // Arbitrary account sequence
}
if tt.shouldErr {
require.NotNil(t, err)
} else {
require.Nil(t, err)
require.Equal(t, tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed()))
}
ctrl := gomock.NewController(t)
mockMeter := gastestutil.NewMockMeter(ctrl)
tt.args.malleate(mockMeter)
err := ante.DefaultSigVerificationGasConsumer(mockMeter, sigV2, tt.args.params)
if tt.shouldErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
@ -118,7 +158,7 @@ func TestSigVerification(t *testing.T) {
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
// make block height non-zero to ensure account numbers part of signBytes
suite.ctx = suite.ctx.WithBlockHeight(1)
suite.ctx = suite.ctx.WithBlockHeight(1).WithHeaderInfo(header.Info{Height: 1, ChainID: suite.ctx.ChainID()})
// keys and addresses
priv1, _, addr1 := testdata.KeyTestPubAddr()
@ -154,7 +194,7 @@ func TestSigVerification(t *testing.T) {
txConfigOpts,
)
require.NoError(t, err)
noOpGasConsume := func(_ storetypes.GasMeter, _ signing.SignatureV2, _ types.Params) error { return nil }
noOpGasConsume := func(_ gas.Meter, _ signing.SignatureV2, _ types.Params) error { return nil }
svd := ante.NewSigVerificationDecorator(suite.accountKeeper, anteTxConfig.SignModeHandler(), noOpGasConsume, nil)
antehandler := sdk.ChainAnteDecorators(svd)
defaultSignMode, err := authsign.APISignModeToInternal(anteTxConfig.SignModeHandler().DefaultMode())
@ -188,6 +228,12 @@ func TestSigVerification(t *testing.T) {
t.Run(fmt.Sprintf("%s with %s", tc.name, signMode), func(t *testing.T) {
ctx, _ := suite.ctx.CacheContext()
ctx = ctx.WithIsReCheckTx(tc.recheck).WithIsSigverifyTx(tc.sigverify)
if tc.recheck {
ctx = ctx.WithExecMode(sdk.ExecModeReCheck)
} else {
ctx = ctx.WithExecMode(sdk.ExecModeCheck)
}
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test
require.NoError(t, suite.txBuilder.SetMsgs(msgs...))
@ -246,23 +292,23 @@ func TestSigIntegration(t *testing.T) {
params := types.DefaultParams()
initialSigCost := params.SigVerifyCostSecp256k1
initialCost, err := runSigDecorators(t, params, false, privs...)
initialCost, err := runSigDecorators(t, params, privs...)
require.Nil(t, err)
params.SigVerifyCostSecp256k1 *= 2
doubleCost, err := runSigDecorators(t, params, false, privs...)
doubleCost, err := runSigDecorators(t, params, privs...)
require.Nil(t, err)
require.Equal(t, initialSigCost*uint64(len(privs)), doubleCost-initialCost)
}
func runSigDecorators(t *testing.T, params types.Params, _ bool, privs ...cryptotypes.PrivKey) (storetypes.Gas, error) {
func runSigDecorators(t *testing.T, params types.Params, privs ...cryptotypes.PrivKey) (storetypes.Gas, error) {
t.Helper()
suite := SetupTestSuite(t, true)
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
// Make block-height non-zero to include accNum in SignBytes
suite.ctx = suite.ctx.WithBlockHeight(1)
suite.ctx = suite.ctx.WithBlockHeight(1).WithHeaderInfo(header.Info{Height: 1})
err := suite.accountKeeper.Params.Set(suite.ctx, params)
require.NoError(t, err)

View File

@ -13,6 +13,7 @@ import (
_ "cosmossdk.io/api/cosmos/bank/v1beta1"
_ "cosmossdk.io/api/cosmos/crypto/secp256k1"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/header"
coretesting "cosmossdk.io/core/testing"
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth"
@ -77,7 +78,7 @@ func SetupTestSuite(t *testing.T, isCheckTx bool) *AnteTestSuite {
key := storetypes.NewKVStoreKey(types.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test"))
suite.ctx = testCtx.Ctx.WithIsCheckTx(isCheckTx).WithBlockHeight(1)
suite.ctx = testCtx.Ctx.WithIsCheckTx(isCheckTx).WithBlockHeight(1).WithHeaderInfo(header.Info{Height: 1, ChainID: testCtx.Ctx.ChainID()})
suite.encCfg = moduletestutil.MakeTestEncodingConfig(codectestutil.CodecOptions{}, auth.AppModule{})
accNum := uint64(0)

View File

@ -15,6 +15,8 @@ import (
txconfigv1 "cosmossdk.io/api/cosmos/tx/config/v1"
"cosmossdk.io/core/address"
"cosmossdk.io/core/appmodule"
appmodulev2 "cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/transaction"
"cosmossdk.io/depinject"
"cosmossdk.io/depinject/appconfig"
"cosmossdk.io/x/auth/ante"
@ -49,12 +51,13 @@ type ModuleInputs struct {
ProtoFileResolver txsigning.ProtoFileResolver
Environment appmodule.Environment
// BankKeeper is the expected bank keeper to be passed to AnteHandlers
BankKeeper authtypes.BankKeeper `optional:"true"`
MetadataBankKeeper BankKeeper `optional:"true"`
AccountKeeper ante.AccountKeeper `optional:"true"`
FeeGrantKeeper ante.FeegrantKeeper `optional:"true"`
CustomSignModeHandlers func() []txsigning.SignModeHandler `optional:"true"`
CustomGetSigners []txsigning.CustomGetSigner `optional:"true"`
BankKeeper authtypes.BankKeeper `optional:"true"`
MetadataBankKeeper BankKeeper `optional:"true"`
AccountKeeper ante.AccountKeeper `optional:"true"`
FeeGrantKeeper ante.FeegrantKeeper `optional:"true"`
AccountAbstractionKeeper ante.AccountAbstractionKeeper `optional:"true"`
CustomSignModeHandlers func() []txsigning.SignModeHandler `optional:"true"`
CustomGetSigners []txsigning.CustomGetSigner `optional:"true"`
}
type ModuleOutputs struct {
@ -63,6 +66,7 @@ type ModuleOutputs struct {
TxConfig client.TxConfig
TxConfigOptions tx.ConfigOptions
BaseAppOption runtime.BaseAppOption // This is only useful for chains using baseapp. Server/v2 chains use TxValidator.
Module appmodule.AppModule
}
func ProvideProtoRegistry() txsigning.ProtoFileResolver {
@ -140,7 +144,15 @@ func ProvideModule(in ModuleInputs) ModuleOutputs {
app.SetTxEncoder(txConfig.TxEncoder())
}
return ModuleOutputs{TxConfig: txConfig, TxConfigOptions: txConfigOptions, BaseAppOption: baseAppOption}
svd := ante.NewSigVerificationDecorator(
in.AccountKeeper,
txConfig.SignModeHandler(),
ante.DefaultSigVerificationGasConsumer,
in.AccountAbstractionKeeper,
)
appModule := AppModule{svd}
return ModuleOutputs{TxConfig: txConfig, TxConfigOptions: txConfigOptions, BaseAppOption: baseAppOption, Module: appModule}
}
func newAnteHandler(txConfig client.TxConfig, in ModuleInputs) (sdk.AnteHandler, error) {
@ -220,3 +232,23 @@ func metadataExists(err error) error {
return err
}
var (
_ appmodulev2.AppModule = AppModule{}
_ appmodulev2.HasTxValidator[transaction.Tx] = AppModule{}
)
type AppModule struct {
sigVerification ante.SigVerificationDecorator
}
// TxValidator implements appmodule.HasTxValidator.
func (a AppModule) TxValidator(ctx context.Context, tx transaction.Tx) error {
return a.sigVerification.ValidateTx(ctx, tx)
}
// IsAppModule implements appmodule.AppModule.
func (a AppModule) IsAppModule() {}
// IsOnePerModuleType implements appmodule.AppModule.
func (a AppModule) IsOnePerModuleType() {}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strconv"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
@ -75,7 +76,8 @@ func (ar AccountRetriever) EnsureExists(clientCtx client.Context, addr sdk.AccAd
func (ar AccountRetriever) GetAccountNumberSequence(clientCtx client.Context, addr sdk.AccAddress) (uint64, uint64, error) {
acc, err := ar.GetAccount(clientCtx, addr)
if err != nil {
if status.Code(err) == codes.NotFound {
// the error might come wrapped from CometBFT, so we check with the string too
if status.Code(err) == codes.NotFound || strings.Contains(err.Error(), "code = NotFound") {
return 0, 0, nil
}
return 0, 0, err