-
Notifications
You must be signed in to change notification settings - Fork 409
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement tx counter for transaction info #621
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package app | ||
|
||
import ( | ||
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/cosmos/cosmos-sdk/x/auth/ante" | ||
"github.com/cosmos/cosmos-sdk/x/auth/signing" | ||
"github.com/cosmos/cosmos-sdk/x/auth/types" | ||
) | ||
|
||
// NewAnteHandler returns an AnteHandler that checks and increments sequence | ||
// numbers, checks signatures & account numbers, and deducts fees from the first | ||
// signer. | ||
func NewAnteHandler( | ||
ak ante.AccountKeeper, bankKeeper types.BankKeeper, | ||
sigGasConsumer ante.SignatureVerificationGasConsumer, | ||
signModeHandler signing.SignModeHandler, | ||
txCounterStoreKey sdk.StoreKey, | ||
) sdk.AnteHandler { | ||
// copied sdk https://github.com/cosmos/cosmos-sdk/blob/v0.42.9/x/auth/ante/ante.go | ||
return sdk.ChainAnteDecorators( | ||
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first | ||
wasmkeeper.NewCountTXDecorator(txCounterStoreKey), | ||
ante.NewRejectExtensionOptionsDecorator(), | ||
ante.NewMempoolFeeDecorator(), | ||
ante.NewValidateBasicDecorator(), | ||
ante.TxTimeoutHeightDecorator{}, | ||
ante.NewValidateMemoDecorator(ak), | ||
ante.NewConsumeGasForTxSizeDecorator(ak), | ||
ante.NewRejectFeeGranterDecorator(), | ||
ante.NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators | ||
ante.NewValidateSigCountDecorator(ak), | ||
ante.NewDeductFeeDecorator(ak, bankKeeper), | ||
ante.NewSigGasConsumeDecorator(ak, sigGasConsumer), | ||
ante.NewSigVerificationDecorator(ak, signModeHandler), | ||
ante.NewIncrementSequenceDecorator(ak), | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ echo "## Add new CosmWasm contract" | |
RESP=$(wasmd tx wasm store "$DIR/../../x/wasm/keeper/testdata/hackatom.wasm" \ | ||
--from validator --gas 1500000 -y --chain-id=testing --node=http://localhost:26657 -b block) | ||
|
||
CODE_ID=$(echo "$RESP" | jq -r '.logs[0].events[0].attributes[-1].value') | ||
CODE_ID=$(echo "$RESP" | jq -r '.logs[0].events[1].attributes[-1].value') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ante handler added an event? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a 🐛 that I found with manual tests. Not related to the ante handler. |
||
echo "* Code id: $CODE_ID" | ||
echo "* Download code" | ||
TMPDIR=$(mktemp -t wasmdXXXXXX) | ||
|
@@ -57,7 +57,7 @@ echo "### Upload new code" | |
RESP=$(wasmd tx wasm store "$DIR/../../x/wasm/keeper/testdata/burner.wasm" \ | ||
--from validator --gas 1000000 -y --chain-id=testing --node=http://localhost:26657 -b block) | ||
|
||
BURNER_CODE_ID=$(echo "$RESP" | jq -r '.logs[0].events[0].attributes[-1].value') | ||
BURNER_CODE_ID=$(echo "$RESP" | jq -r '.logs[0].events[1].attributes[-1].value') | ||
echo "### Migrate to code id: $BURNER_CODE_ID" | ||
|
||
DEST_ACCOUNT=$(wasmd keys show fred -a) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package keeper | ||
|
||
import ( | ||
"encoding/binary" | ||
"github.com/CosmWasm/wasmd/x/wasm/types" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
// CountTXDecorator ante handler to count the tx position in a block. | ||
type CountTXDecorator struct { | ||
storeKey sdk.StoreKey | ||
} | ||
|
||
// NewCountTXDecorator constructor | ||
func NewCountTXDecorator(storeKey sdk.StoreKey) *CountTXDecorator { | ||
return &CountTXDecorator{storeKey: storeKey} | ||
} | ||
|
||
// AnteHandle handler stores a tx counter with current height encoded in the store to let the app handle | ||
// global rollback behavior instead of keeping state in the handler itself. | ||
// The ante handler passes the counter value via sdk.Context upstream. See `types.TXCounter(ctx)` to read the value. | ||
// Simulations don't get a tx counter value assigned. | ||
func (a CountTXDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { | ||
if simulate { | ||
return next(ctx, tx, simulate) | ||
} | ||
store := ctx.KVStore(a.storeKey) | ||
currentHeight := ctx.BlockHeight() | ||
|
||
var txCounter uint32 = 0 // start with 0 | ||
// load counter when exists | ||
if bz := store.Get(types.TXCounterPrefix); bz != nil { | ||
lastHeight, val := decodeHeightCounter(bz) | ||
if currentHeight == lastHeight { | ||
// then use stored counter | ||
txCounter = val | ||
} // else use `0` from above to start with | ||
} | ||
// store next counter value for current height | ||
store.Set(types.TXCounterPrefix, encodeHeightCounter(currentHeight, txCounter+1)) | ||
|
||
return next(types.WithTXCounter(ctx, txCounter), tx, simulate) | ||
} | ||
|
||
func encodeHeightCounter(height int64, counter uint32) []byte { | ||
b := make([]byte, 4) | ||
binary.BigEndian.PutUint32(b, counter) | ||
return append(sdk.Uint64ToBigEndian(uint64(height)), b...) | ||
} | ||
|
||
func decodeHeightCounter(bz []byte) (int64, uint32) { | ||
return int64(sdk.BigEndianToUint64(bz[0:8])), binary.BigEndian.Uint32(bz[8:]) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package keeper | ||
|
||
import ( | ||
"github.com/CosmWasm/wasmd/x/wasm/types" | ||
"github.com/cosmos/cosmos-sdk/store" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"github.com/tendermint/tendermint/libs/log" | ||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" | ||
dbm "github.com/tendermint/tm-db" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestCountTxDecorator(t *testing.T) { | ||
keyWasm := sdk.NewKVStoreKey(types.StoreKey) | ||
db := dbm.NewMemDB() | ||
ms := store.NewCommitMultiStore(db) | ||
ms.MountStoreWithDB(keyWasm, sdk.StoreTypeIAVL, db) | ||
require.NoError(t, ms.LoadLatestVersion()) | ||
const myCurrentBlockHeight = 100 | ||
|
||
specs := map[string]struct { | ||
setupDB func(t *testing.T, ctx sdk.Context) | ||
simulate bool | ||
nextAssertAnte func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) | ||
expErr bool | ||
}{ | ||
"no initial counter set": { | ||
setupDB: func(t *testing.T, ctx sdk.Context) {}, | ||
nextAssertAnte: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { | ||
gotCounter, ok := types.TXCounter(ctx) | ||
require.True(t, ok) | ||
assert.Equal(t, uint32(0), gotCounter) | ||
// and stored +1 | ||
bz := ctx.MultiStore().GetKVStore(keyWasm).Get(types.TXCounterPrefix) | ||
assert.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, myCurrentBlockHeight, 0, 0, 0, 1}, bz) | ||
return ctx, nil | ||
}, | ||
}, | ||
"persistent counter incremented - big endian": { | ||
setupDB: func(t *testing.T, ctx sdk.Context) { | ||
bz := []byte{0, 0, 0, 0, 0, 0, 0, myCurrentBlockHeight, 1, 0, 0, 2} | ||
ctx.MultiStore().GetKVStore(keyWasm).Set(types.TXCounterPrefix, bz) | ||
}, | ||
nextAssertAnte: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { | ||
gotCounter, ok := types.TXCounter(ctx) | ||
require.True(t, ok) | ||
assert.Equal(t, uint32(1<<24+2), gotCounter) | ||
// and stored +1 | ||
bz := ctx.MultiStore().GetKVStore(keyWasm).Get(types.TXCounterPrefix) | ||
assert.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, myCurrentBlockHeight, 1, 0, 0, 3}, bz) | ||
return ctx, nil | ||
}, | ||
}, | ||
"old height counter replaced": { | ||
setupDB: func(t *testing.T, ctx sdk.Context) { | ||
previousHeight := byte(myCurrentBlockHeight - 1) | ||
bz := []byte{0, 0, 0, 0, 0, 0, 0, previousHeight, 0, 0, 0, 1} | ||
ctx.MultiStore().GetKVStore(keyWasm).Set(types.TXCounterPrefix, bz) | ||
}, | ||
nextAssertAnte: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { | ||
gotCounter, ok := types.TXCounter(ctx) | ||
require.True(t, ok) | ||
assert.Equal(t, uint32(0), gotCounter) | ||
// and stored +1 | ||
bz := ctx.MultiStore().GetKVStore(keyWasm).Get(types.TXCounterPrefix) | ||
assert.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, myCurrentBlockHeight, 0, 0, 0, 1}, bz) | ||
return ctx, nil | ||
}, | ||
}, | ||
"simulation not persisted": { | ||
setupDB: func(t *testing.T, ctx sdk.Context) { | ||
}, | ||
simulate: true, | ||
nextAssertAnte: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { | ||
_, ok := types.TXCounter(ctx) | ||
assert.False(t, ok) | ||
require.True(t, simulate) | ||
// and not stored | ||
assert.False(t, ctx.MultiStore().GetKVStore(keyWasm).Has(types.TXCounterPrefix)) | ||
return ctx, nil | ||
}, | ||
}, | ||
} | ||
for name, spec := range specs { | ||
t.Run(name, func(t *testing.T) { | ||
ctx := sdk.NewContext(ms.CacheMultiStore(), tmproto.Header{ | ||
Height: myCurrentBlockHeight, | ||
Time: time.Date(2021, time.September, 27, 12, 0, 0, 0, time.UTC), | ||
}, false, log.NewNopLogger()) | ||
|
||
spec.setupDB(t, ctx) | ||
var anyTx sdk.Tx | ||
|
||
// when | ||
ante := NewCountTXDecorator(keyWasm) | ||
_, gotErr := ante.AnteHandle(ctx, anyTx, spec.simulate, spec.nextAssertAnte) | ||
if spec.expErr { | ||
require.Error(t, gotErr) | ||
return | ||
} | ||
require.NoError(t, gotErr) | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package types | ||
|
||
import ( | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
type contextKey int | ||
|
||
const ( | ||
// private type creates an interface key for Context that cannot be accessed by any other package | ||
contextKeyTXCount contextKey = iota | ||
) | ||
|
||
// WithTXCounter stores a transaction counter value in the context | ||
func WithTXCounter(ctx sdk.Context, counter uint32) sdk.Context { | ||
return ctx.WithValue(contextKeyTXCount, counter) | ||
} | ||
|
||
// TXCounter returns the tx counter value and found bool from the context. | ||
// The result will be (0, false) for external queries or simulations where no counter available. | ||
func TXCounter(ctx sdk.Context) (uint32, bool) { | ||
val, ok := ctx.Value(contextKeyTXCount).(uint32) | ||
return val, ok | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice