fix: do not allow unordered txs to have sequence values set (#24581)
Co-authored-by: Alex | Interchain Labs <alex@interchainlabs.io> Co-authored-by: Aaron Craelius <aaronc@users.noreply.github.com>
This commit is contained in:
parent
a158c24b38
commit
f9f3bfb066
@ -408,6 +408,11 @@ Lastly, add an entry for epochs in the ModuleConfig:
|
||||
|
||||
To enable unordered transaction support on an application, the `x/auth` keeper must be supplied with the `WithUnorderedTransactions` option.
|
||||
|
||||
Note that unordered transactions require sequence values to be zero, and will **FAIL** if a non-zero sequence value is set.
|
||||
Please ensure no sequence value is set when submitting an unordered transaction.
|
||||
Services that rely on prior assumptions about sequence values should be updated to handle unordered transactions.
|
||||
Services should be aware that when the transaction is unordered, the transaction sequence will always be zero.
|
||||
|
||||
```go
|
||||
app.AccountKeeper = authkeeper.NewAccountKeeper(
|
||||
appCodec,
|
||||
|
||||
@ -25,6 +25,11 @@ To submit an unordered transaction, clients must set the `unordered` flag to
|
||||
used as a TTL for the transaction and provides replay protection. Each transaction's `timeout_timestamp` must be
|
||||
unique to the account; however, the difference may be as small as a nanosecond. See [ADR-070](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-070-unordered-transactions.md) for more details.
|
||||
|
||||
Note that unordered transactions require sequence values to be zero, and will **FAIL** if a non-zero sequence value is set.
|
||||
Please ensure no sequence value is set when submitting an unordered transaction.
|
||||
Services that rely on prior assumptions about sequence values should be updated to handle unordered transactions.
|
||||
Services should be aware that when the transaction is unordered, the transaction sequence will always be zero.
|
||||
|
||||
#### Enabling Unordered Transactions
|
||||
|
||||
To enable unordered transactions, supply the `WithUnorderedTransactions` option to the `x/auth` keeper:
|
||||
|
||||
@ -8561,6 +8561,10 @@ type TxBody struct {
|
||||
// Note, when set to true, the existing 'timeout_timestamp' value must
|
||||
// be set and will be used to correspond to a timestamp in which the transaction is deemed
|
||||
// valid.
|
||||
//
|
||||
// When true, the sequence value MUST be 0, and any transaction with unordered=true and a non-zero sequence value will
|
||||
// be rejected.
|
||||
// External services that make assumptions about sequence values may need to be updated because of this.
|
||||
Unordered bool `protobuf:"varint,4,opt,name=unordered,proto3" json:"unordered,omitempty"`
|
||||
// timeout_timestamp is the block time after which this transaction will not
|
||||
// be processed by the chain.
|
||||
|
||||
@ -150,6 +150,8 @@ func AddTxFlagsToCmd(cmd *cobra.Command) {
|
||||
GasFlagAuto, GasFlagAuto, FlagFees, DefaultGasLimit))
|
||||
|
||||
cmd.MarkFlagsMutuallyExclusive(FlagTimeoutHeight, TimeoutDuration)
|
||||
// unordered transactions must not have sequence values.
|
||||
cmd.MarkFlagsMutuallyExclusive(FlagUnordered, FlagSequence)
|
||||
cmd.MarkFlagsRequiredTogether(FlagUnordered, TimeoutDuration)
|
||||
|
||||
AddKeyringFlags(f)
|
||||
|
||||
@ -515,6 +515,9 @@ func (f Factory) getSimSignatureData(pk cryptotypes.PubKey) signing.SignatureDat
|
||||
// A new Factory with the updated fields will be returned.
|
||||
// Note: When in offline mode, the Prepare does nothing and returns the original factory.
|
||||
func (f Factory) Prepare(clientCtx client.Context) (Factory, error) {
|
||||
if f.sequence > 0 && f.unordered {
|
||||
return f, errors.New("unordered transactions must not have sequence values set")
|
||||
}
|
||||
if clientCtx.Offline {
|
||||
return f, nil
|
||||
}
|
||||
@ -537,7 +540,7 @@ func (f Factory) Prepare(clientCtx client.Context) (Factory, error) {
|
||||
fc = fc.WithAccountNumber(num)
|
||||
}
|
||||
|
||||
if initSeq == 0 {
|
||||
if initSeq == 0 && !f.unordered {
|
||||
fc = fc.WithSequence(seq)
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,6 +41,17 @@ func TestFactoryPrepare(t *testing.T) {
|
||||
require.NotEqual(t, output, factory)
|
||||
require.Equal(t, output.AccountNumber(), uint64(10))
|
||||
require.Equal(t, output.Sequence(), uint64(1))
|
||||
|
||||
// sequence and unordered set should break the tx.
|
||||
factory = Factory{}.WithAccountRetriever(client.MockAccountRetriever{ReturnAccNum: 10, ReturnAccSeq: 15}).WithUnordered(true).WithSequence(15)
|
||||
_, err = factory.Prepare(clientCtx.WithFrom("foo"))
|
||||
require.ErrorContains(t, err, "unordered transactions must not have sequence values set")
|
||||
|
||||
// unordered set should ignore the retrieved sequence value.
|
||||
factory = Factory{}.WithAccountRetriever(client.MockAccountRetriever{ReturnAccNum: 10, ReturnAccSeq: 15}).WithUnordered(true)
|
||||
output, err = factory.Prepare(clientCtx.WithFrom("foo"))
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, output.Sequence())
|
||||
}
|
||||
|
||||
func TestFactory_getSimPKType(t *testing.T) {
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
- Dec 4, 2023: Initial Draft (@yihuang, @tac0turtle, @alexanderbez)
|
||||
- Jan 30, 2024: Include section on deterministic transaction encoding
|
||||
- Mar 18, 2025: Revise implementation to use Cosmos SDK KV Store and require unique timeouts per-address (@technicallyty)
|
||||
- Apr 25, 2025: Add note about rejecting unordered txs with sequence values.
|
||||
|
||||
## Status
|
||||
|
||||
@ -289,6 +290,8 @@ for _, tx := range txs {
|
||||
}
|
||||
```
|
||||
|
||||
We will reject transactions that have both sequence and unordered timeouts set. We do this to avoid assuming the intent of the user.
|
||||
|
||||
### State Management
|
||||
|
||||
The storage of unordered sequences will be facilitated using the Cosmos SDK's KV Store service.
|
||||
|
||||
@ -139,7 +139,7 @@ https://github.com/cosmos/cosmos-sdk/blob/v0.53.0-rc.2/client/tx_config.go#L39-L
|
||||
* `Memo`, a note or comment to send with the transaction.
|
||||
* `FeeAmount`, the maximum amount the user is willing to pay in fees.
|
||||
* `TimeoutHeight`, block height until which the transaction is valid.
|
||||
* `Unordered`, an option indicating this transaction may be executed in any order (requires TimeoutTimestamp to be set)
|
||||
* `Unordered`, an option indicating this transaction may be executed in any order (requires Sequence to be unset.)
|
||||
* `TimeoutTimestamp`, the timeout timestamp (unordered nonce) of the transaction (required to be used with Unordered).
|
||||
* `Signatures`, the array of signatures from all signers of the transaction.
|
||||
|
||||
@ -211,11 +211,19 @@ Check out the [v0.53.0 Upgrade Guide](https://docs.cosmos.network/v0.53/build/mi
|
||||
|
||||
:::
|
||||
|
||||
:::warning
|
||||
|
||||
Unordered transactions MUST leave sequence values unset. When a transaction is both unordered and contains a non-zero sequence value,
|
||||
the transaction will be rejected. Services that operate on prior assumptions about transaction sequence values should be updated to handle unordered transactions.
|
||||
Services should be aware that when the transaction is unordered, the transaction sequence will always be zero.
|
||||
|
||||
:::
|
||||
|
||||
Beginning with Cosmos SDK v0.53.0, chains may enable unordered transaction support.
|
||||
Unordered transactions work by using a timestamp as the transaction's nonce value.
|
||||
Unordered transactions work by using a timestamp as the transaction's nonce value. The sequence value must NOT be set in the signature(s) of the transaction.
|
||||
The timestamp must be greater than the current block time and not exceed the chain's configured max unordered timeout timestamp duration.
|
||||
Senders must use a unique timestamp for each distinct transaction. The difference may be as small as a nanosecond, however.
|
||||
|
||||
These unique timestamps serve as a one-shot nonce, and their lifespan in state is short-lived.
|
||||
Upon transaction inclusion, an entry consisting of timeout timestamp and account address will be recorded to state.
|
||||
Once the block time is passed the timeout timestamp value, the entry will be removed. This ensures that unordered nonces do not indefinitely fill up the chain's storage.
|
||||
Once the block time is passed the timeout timestamp value, the entry will be removed. This ensures that unordered nonces do not indefinitely fill up the chain's storage.
|
||||
@ -46,9 +46,17 @@ Later, validators decide whether to include the transaction in their block by co
|
||||
With Cosmos SDK v0.53.0, users may send unordered transactions to chains that have this feature enabled.
|
||||
The following flags allow a user to build an unordered transaction from the CLI.
|
||||
|
||||
* `--unordered` specifies that this transaction should be unordered.
|
||||
* `--unordered` specifies that this transaction should be unordered. (transaction sequence must be unset)
|
||||
* `--timeout-duration` specifies the amount of time the unordered transaction should be valid in the mempool. The transaction's unordered nonce will be set to the time of transaction creation + timeout duration.
|
||||
|
||||
:::warning
|
||||
|
||||
Unordered transactions MUST leave sequence values unset. When a transaction is both unordered and contains a non-zero sequence value,
|
||||
the transaction will be rejected. External services that operate on prior assumptions about transaction sequence values should be updated to handle unordered transactions.
|
||||
Services should be aware that when the transaction is unordered, the transaction sequence will always be zero.
|
||||
|
||||
:::
|
||||
|
||||
#### CLI Example
|
||||
|
||||
Users of the application `app` can enter the following command into their CLI to generate a transaction to send 1000uatom from a `senderAddress` to a `recipientAddress`. The command specifies how much gas they are willing to pay: an automatic estimate scaled up by 1.5 times, with a gas price of 0.025uatom per unit gas.
|
||||
|
||||
@ -183,6 +183,14 @@ At this point, `TxBuilder`'s underlying transaction is ready to be signed.
|
||||
|
||||
Starting with Cosmos SDK v0.53.0, users may send unordered transactions to chains that have the feature enabled.
|
||||
|
||||
:::warning
|
||||
|
||||
Unordered transactions MUST leave sequence values unset. When a transaction is both unordered and contains a non-zero sequence value,
|
||||
the transaction will be rejected. External services that operate on prior assumptions about transaction sequence values should be updated to handle unordered transactions.
|
||||
Services should be aware that when the transaction is unordered, the transaction sequence will always be zero.
|
||||
|
||||
:::
|
||||
|
||||
Using the example above, we can set the required fields to mark a transaction as unordered.
|
||||
By default, unordered transactions charge an extra 2240 units of gas to offset the additional storage overhead that supports their functionality.
|
||||
The extra units of gas are customizable and therefore vary by chain, so be sure to check the chain's ante handler for the gas value set, if any.
|
||||
|
||||
@ -122,6 +122,10 @@ message TxBody {
|
||||
// Note, when set to true, the existing 'timeout_timestamp' value must
|
||||
// be set and will be used to correspond to a timestamp in which the transaction is deemed
|
||||
// valid.
|
||||
//
|
||||
// When true, the sequence value MUST be 0, and any transaction with unordered=true and a non-zero sequence value will
|
||||
// be rejected.
|
||||
// External services that make assumptions about sequence values may need to be updated because of this.
|
||||
bool unordered = 4 [(cosmos_proto.field_added_in) = "cosmos-sdk 0.53"];
|
||||
|
||||
// timeout_timestamp is the block time after which this transaction will not
|
||||
|
||||
@ -557,6 +557,26 @@ func (s *E2ETestSuite) TestBroadcastTx_GRPCGateway() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TestUnorderedCannotUseSequence() {
|
||||
val1 := *s.network.Validators[0]
|
||||
coins := sdk.NewInt64Coin(s.cfg.BondDenom, 15)
|
||||
_, err := cli.MsgSendExec(
|
||||
val1.ClientCtx,
|
||||
val1.Address,
|
||||
val1.Address,
|
||||
sdk.NewCoins(coins),
|
||||
addresscodec.NewBech32Codec("cosmos"),
|
||||
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, math.NewInt(10))).String()),
|
||||
fmt.Sprintf("--gas=%d", flags.DefaultGasLimit),
|
||||
fmt.Sprintf("--sequence=%d", 15),
|
||||
"--unordered",
|
||||
fmt.Sprintf("--timeout-duration=%s", "10s"),
|
||||
)
|
||||
s.Require().ErrorContains(err, "if any flags in the group [unordered sequence] are set none of the others can be; [sequence unordered] were all set")
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TestSimMultiSigTx() {
|
||||
val1 := *s.network.Validators[0]
|
||||
|
||||
|
||||
26
types/mempool/nonce.go
Normal file
26
types/mempool/nonce.go
Normal file
@ -0,0 +1,26 @@
|
||||
package mempool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// ChooseNonce gets the nonce from a transaction. If the transaction is unordered,
|
||||
// it uses the timeout timestamp as the nonce. Sequence values must be zero in this case.
|
||||
// If the transaction is ordered, it uses the sequence number as the nonce.
|
||||
func ChooseNonce(seq uint64, tx sdk.Tx) (uint64, error) {
|
||||
// if it's an unordered tx, we use the timeout timestamp instead of the nonce
|
||||
if unordered, ok := tx.(sdk.TxWithUnordered); ok && unordered.GetUnordered() {
|
||||
if seq > 0 {
|
||||
return 0, errors.New("unordered txs must not have sequence set")
|
||||
}
|
||||
timestamp := unordered.GetTimeoutTimeStamp().UnixNano()
|
||||
if timestamp < 0 {
|
||||
return 0, errors.New("invalid timestamp value")
|
||||
}
|
||||
return uint64(timestamp), nil
|
||||
}
|
||||
// otherwise, use the sequence as normal.
|
||||
return seq, nil
|
||||
}
|
||||
58
types/mempool/nonce_test.go
Normal file
58
types/mempool/nonce_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
package mempool_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types/mempool"
|
||||
)
|
||||
|
||||
func TestChooseNonce(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
seq uint64
|
||||
unordered bool
|
||||
timeout time.Time
|
||||
expErr string
|
||||
expNonce int64
|
||||
}{
|
||||
{
|
||||
name: "unordered nonce chosen",
|
||||
unordered: true,
|
||||
timeout: time.Unix(100, 15),
|
||||
expNonce: time.Unix(100, 15).UnixNano(),
|
||||
},
|
||||
{
|
||||
name: "sequence chosen",
|
||||
seq: 15,
|
||||
expNonce: 15,
|
||||
},
|
||||
{
|
||||
name: "timeout invalid",
|
||||
unordered: true,
|
||||
timeout: time.Time{},
|
||||
expErr: "invalid timestamp value",
|
||||
},
|
||||
{
|
||||
name: "invalid if sequence and unordered set",
|
||||
unordered: true,
|
||||
seq: 15,
|
||||
expErr: "unordered txs must not have sequence set",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tx := testTx{unordered: tc.unordered, nonce: tc.seq, timeout: &tc.timeout}
|
||||
nonce, err := mempool.ChooseNonce(tc.seq, tx)
|
||||
if tc.expErr != "" {
|
||||
require.ErrorContains(t, err, tc.expErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, nonce, uint64(tc.expNonce))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,6 @@ package mempool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
@ -222,15 +221,9 @@ func (mp *PriorityNonceMempool[C]) Insert(ctx context.Context, tx sdk.Tx) error
|
||||
sig := sigs[0]
|
||||
sender := sig.Signer.String()
|
||||
priority := mp.cfg.TxPriority.GetTxPriority(ctx, tx)
|
||||
nonce := sig.Sequence
|
||||
|
||||
// if it's an unordered tx, we use the timeout timestamp instead of the nonce
|
||||
if unordered, ok := tx.(sdk.TxWithUnordered); ok && unordered.GetUnordered() {
|
||||
timestamp := unordered.GetTimeoutTimeStamp().UnixNano()
|
||||
if timestamp < 0 {
|
||||
return errors.New("invalid timestamp value")
|
||||
}
|
||||
nonce = uint64(timestamp)
|
||||
nonce, err := ChooseNonce(sig.Sequence, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := txMeta[C]{nonce: nonce, priority: priority, sender: sender}
|
||||
@ -467,15 +460,9 @@ func (mp *PriorityNonceMempool[C]) Remove(tx sdk.Tx) error {
|
||||
|
||||
sig := sigs[0]
|
||||
sender := sig.Signer.String()
|
||||
nonce := sig.Sequence
|
||||
|
||||
// if it's an unordered tx, we use the timeout timestamp instead of the nonce
|
||||
if unordered, ok := tx.(sdk.TxWithUnordered); ok && unordered.GetUnordered() {
|
||||
timestamp := unordered.GetTimeoutTimeStamp().UnixNano()
|
||||
if timestamp < 0 {
|
||||
return errors.New("invalid timestamp value")
|
||||
}
|
||||
nonce = uint64(timestamp)
|
||||
nonce, err := ChooseNonce(sig.Sequence, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scoreKey := txMeta[C]{nonce: nonce, sender: sender}
|
||||
|
||||
@ -973,6 +973,14 @@ func TestNextSenderTx_TxReplacement(t *testing.T) {
|
||||
require.Equal(t, txs[3], iter.Tx())
|
||||
}
|
||||
|
||||
func TestPriorityNonceMempool_UnorderedTx_FailsForSequence(t *testing.T) {
|
||||
mp := mempool.DefaultPriorityMempool()
|
||||
accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 1)
|
||||
tx := testTx{id: 1, priority: 0, address: accounts[0].Address, nonce: 1, unordered: true}
|
||||
err := mp.Insert(sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()), tx)
|
||||
require.ErrorContains(t, err, "unordered txs must not have sequence set")
|
||||
}
|
||||
|
||||
func TestPriorityNonceMempool_UnorderedTx(t *testing.T) {
|
||||
ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger())
|
||||
accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 2)
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
crand "crypto/rand" // #nosec // crypto/rand is used for seed generation
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand" // #nosec // math/rand is used for random selection and seeded from crypto/rand
|
||||
"slices"
|
||||
@ -139,15 +138,9 @@ func (snm *SenderNonceMempool) Insert(_ context.Context, tx sdk.Tx) error {
|
||||
|
||||
sig := sigs[0]
|
||||
sender := sdk.AccAddress(sig.PubKey.Address()).String()
|
||||
nonce := sig.Sequence
|
||||
|
||||
// if it's an unordered tx, we use the timeout timestamp instead of the nonce
|
||||
if unordered, ok := tx.(sdk.TxWithUnordered); ok && unordered.GetUnordered() {
|
||||
timestamp := unordered.GetTimeoutTimeStamp().UnixNano()
|
||||
if timestamp < 0 {
|
||||
return errors.New("invalid timestamp value")
|
||||
}
|
||||
nonce = uint64(timestamp)
|
||||
nonce, err := ChooseNonce(sig.Sequence, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
senderTxs, found := snm.senders[sender]
|
||||
@ -236,15 +229,9 @@ func (snm *SenderNonceMempool) Remove(tx sdk.Tx) error {
|
||||
|
||||
sig := sigs[0]
|
||||
sender := sdk.AccAddress(sig.PubKey.Address()).String()
|
||||
nonce := sig.Sequence
|
||||
|
||||
// if it's an unordered tx, we use the timeout timestamp instead of the nonce
|
||||
if unordered, ok := tx.(sdk.TxWithUnordered); ok && unordered.GetUnordered() {
|
||||
timestamp := unordered.GetTimeoutTimeStamp().UnixNano()
|
||||
if timestamp < 0 {
|
||||
return errors.New("invalid timestamp value")
|
||||
}
|
||||
nonce = uint64(timestamp)
|
||||
nonce, err := ChooseNonce(sig.Sequence, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
senderTxs, found := snm.senders[sender]
|
||||
|
||||
@ -170,6 +170,22 @@ func (s *MempoolTestSuite) TestMaxTx() {
|
||||
require.Equal(t, mempool.ErrMempoolTxMaxCapacity, err)
|
||||
}
|
||||
|
||||
func (s *MempoolTestSuite) TestTxRejectedWithUnorderedAndSequence() {
|
||||
t := s.T()
|
||||
ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger())
|
||||
accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 1)
|
||||
mp := mempool.NewSenderNonceMempool(mempool.SenderNonceMaxTxOpt(5000))
|
||||
|
||||
txSender := testTx{
|
||||
nonce: 15,
|
||||
address: accounts[0].Address,
|
||||
priority: rand.Int63(),
|
||||
unordered: true,
|
||||
}
|
||||
err := mp.Insert(ctx, txSender)
|
||||
require.ErrorContains(t, err, "unordered txs must not have sequence set")
|
||||
}
|
||||
|
||||
func (s *MempoolTestSuite) TestTxNotFoundOnSender() {
|
||||
t := s.T()
|
||||
ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger())
|
||||
|
||||
@ -373,6 +373,10 @@ type TxBody struct {
|
||||
// Note, when set to true, the existing 'timeout_timestamp' value must
|
||||
// be set and will be used to correspond to a timestamp in which the transaction is deemed
|
||||
// valid.
|
||||
//
|
||||
// When true, the sequence value MUST be 0, and any transaction with unordered=true and a non-zero sequence value will
|
||||
// be rejected.
|
||||
// External services that make assumptions about sequence values may need to be updated because of this.
|
||||
Unordered bool `protobuf:"varint,4,opt,name=unordered,proto3" json:"unordered,omitempty"`
|
||||
// timeout_timestamp is the block time after which this transaction will not
|
||||
// be processed by the chain.
|
||||
|
||||
@ -120,11 +120,21 @@ func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
isUnordered := false
|
||||
utx, ok := tx.(sdk.TxWithUnordered)
|
||||
if ok && utx.GetUnordered() {
|
||||
isUnordered = true
|
||||
}
|
||||
|
||||
var events sdk.Events
|
||||
for i, sig := range sigs {
|
||||
events = append(events, sdk.NewEvent(sdk.EventTypeTx,
|
||||
sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signerStrs[i], sig.Sequence)),
|
||||
))
|
||||
// this shouldn't happen, but if we somehow got a tx with both a sequence set, and is unordered,
|
||||
// we shouldn't emit the event, as this is a false sequence, and won't actually be used.
|
||||
if !isUnordered {
|
||||
events = append(events, sdk.NewEvent(sdk.EventTypeTx,
|
||||
sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signerStrs[i], sig.Sequence)),
|
||||
))
|
||||
}
|
||||
|
||||
sigBzs, err := signatureDataToBz(sig.Data)
|
||||
if err != nil {
|
||||
@ -334,6 +344,9 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
|
||||
}
|
||||
|
||||
for i, sig := range sigs {
|
||||
if sig.Sequence > 0 && isUnordered {
|
||||
return ctx, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "sequence is not allowed for unordered transactions")
|
||||
}
|
||||
acc, err := GetSignerAcc(ctx, svd.ak, signers[i])
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
|
||||
@ -72,6 +72,35 @@ func TestSetPubKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetPubKey_UnorderedNoEvents tests that when the tx is unordered, the sequence event is not emitted.
|
||||
func TestSetPubKey_UnorderedNoEvents(t *testing.T) {
|
||||
suite := SetupTestSuite(t, true)
|
||||
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// prepare accounts for tx
|
||||
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||
acc := suite.accountKeeper.NewAccountWithAddress(suite.ctx, addr1)
|
||||
require.NoError(t, acc.SetAccountNumber(uint64(1000)))
|
||||
suite.accountKeeper.SetAccount(suite.ctx, acc)
|
||||
require.NoError(t, suite.txBuilder.SetMsgs(testdata.NewTestMsg(addr1)))
|
||||
|
||||
privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
|
||||
tx, err := suite.CreateTestUnorderedTx(suite.ctx, privs, accNums, accSeqs, suite.ctx.ChainID(), signing.SignMode_SIGN_MODE_DIRECT, true, time.Unix(100, 0))
|
||||
require.NoError(t, err)
|
||||
|
||||
spkd := ante.NewSetPubKeyDecorator(suite.accountKeeper)
|
||||
antehandler := sdk.ChainAnteDecorators(spkd)
|
||||
|
||||
ctx, err := antehandler(suite.ctx.WithBlockTime(time.Unix(95, 0)), tx, false)
|
||||
require.NoError(t, err)
|
||||
events := ctx.EventManager().Events()
|
||||
for _, event := range events {
|
||||
// if this event were emitted, the tx search by address/sequence would break when an unordered
|
||||
// transaction uses the same sequence number as another transaction from the same sender.
|
||||
require.NotContains(t, event.Attributes, sdk.AttributeKeyAccountSequence)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsumeSignatureVerificationGas(t *testing.T) {
|
||||
suite := SetupTestSuite(t, true)
|
||||
params := types.DefaultParams()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user