Skip to content

Commit

Permalink
Merge pull request #165 from CosmWasm/genesis_io_resilient
Browse files Browse the repository at this point in the history
Validate genesis model
  • Loading branch information
ethanfrey authored Jul 1, 2020
2 parents 8653adf + 44696c4 commit 1cdda2d
Show file tree
Hide file tree
Showing 13 changed files with 799 additions and 78 deletions.
26 changes: 17 additions & 9 deletions x/wasm/internal/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,41 @@ import (

"github.com/CosmWasm/wasmd/x/wasm/internal/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
// authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
// "github.com/CosmWasm/wasmd/x/wasm/internal/types"
)

// InitGenesis sets supply information for genesis.
//
// CONTRACT: all types of accounts must have been already initialized/created
func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) {
for _, code := range data.Codes {
func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) error {
for i, code := range data.Codes {
newId, err := keeper.Create(ctx, code.CodeInfo.Creator, code.CodesBytes, code.CodeInfo.Source, code.CodeInfo.Builder)
if err != nil {
panic(err)
return sdkerrors.Wrapf(err, "code number %d", i)

}
newInfo := keeper.GetCodeInfo(ctx, newId)
if !bytes.Equal(code.CodeInfo.CodeHash, newInfo.CodeHash) {
panic("code hashes not same")
return sdkerrors.Wrap(types.ErrInvalid, "code hashes not same")
}
}

for _, contract := range data.Contracts {
keeper.setContractInfo(ctx, contract.ContractAddress, &contract.ContractInfo)
keeper.setContractState(ctx, contract.ContractAddress, contract.ContractState)
for i, contract := range data.Contracts {
err := keeper.importContract(ctx, contract.ContractAddress, &contract.ContractInfo, contract.ContractState)
if err != nil {
return sdkerrors.Wrapf(err, "contract number %d", i)
}
}

for _, seq := range data.Sequences {
keeper.setAutoIncrementID(ctx, seq.IDKey, seq.Value)
for i, seq := range data.Sequences {
err := keeper.importAutoIncrementID(ctx, seq.IDKey, seq.Value)
if err != nil {
return sdkerrors.Wrapf(err, "sequence number %d", i)
}
}
return nil
}

// ExportGenesis returns a GenesisState for a given context and keeper.
Expand Down
158 changes: 157 additions & 1 deletion x/wasm/internal/keeper/genesis_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper

import (
"crypto/sha256"
"io/ioutil"
"os"
"testing"
Expand Down Expand Up @@ -42,7 +43,7 @@ func TestGenesisExportImport(t *testing.T) {
contract.CodeID = codeID
contractAddr := srcKeeper.generateContractAddress(srcCtx, codeID)
srcKeeper.setContractInfo(srcCtx, contractAddr, &contract)
srcKeeper.setContractState(srcCtx, contractAddr, stateModels)
srcKeeper.importContractState(srcCtx, contractAddr, stateModels)
}

// export
Expand All @@ -67,6 +68,161 @@ func TestGenesisExportImport(t *testing.T) {
require.False(t, dstIT.Valid())
}

func TestFailFastImport(t *testing.T) {
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
codeHash := sha256.Sum256(wasmCode)
anyAddress := make([]byte, 20)

specs := map[string]struct {
src types.GenesisState
expSuccess bool
}{
"happy path: code info correct": {
src: types.GenesisState{
Codes: []types.Code{{
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
}},
Contracts: nil,
},
expSuccess: true,
},
"prevent code hash mismatch": {src: types.GenesisState{
Codes: []types.Code{{
CodeInfo: wasmTypes.CodeInfo{
CodeHash: make([]byte, len(codeHash)),
Creator: anyAddress,
},
CodesBytes: wasmCode,
}},
Contracts: nil,
}},
"happy path: code id in info and contract do match": {
src: types.GenesisState{
Codes: []types.Code{{
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
}},
Contracts: []types.Contract{
{
ContractAddress: contractAddress(1, 1),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }),
},
},
},
expSuccess: true,
},
"happy path: code info with two contracts": {
src: types.GenesisState{
Codes: []types.Code{{
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
}},
Contracts: []types.Contract{
{
ContractAddress: contractAddress(1, 1),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }),
}, {
ContractAddress: contractAddress(2, 1),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }),
},
},
},
expSuccess: true,
},
"prevent contracts that points to non existing codeID": {
src: types.GenesisState{
Contracts: []types.Contract{
{
ContractAddress: contractAddress(1, 1),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }),
},
},
},
},
"prevent duplicate contract address": {
src: types.GenesisState{
Codes: []types.Code{{
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
}},
Contracts: []types.Contract{
{
ContractAddress: contractAddress(1, 1),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }),
}, {
ContractAddress: contractAddress(1, 1),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }),
},
},
},
},
"prevent duplicate contract model keys": {
src: types.GenesisState{
Codes: []types.Code{{
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
}},
Contracts: []types.Contract{
{
ContractAddress: contractAddress(1, 1),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }),
ContractState: []types.Model{
{
Key: []byte{0x1},
Value: []byte("foo"),
},
{
Key: []byte{0x1},
Value: []byte("bar"),
},
},
},
},
},
},
"prevent duplicate sequences": {
src: types.GenesisState{
Sequences: []types.Sequence{
{IDKey: []byte("foo"), Value: 1},
{IDKey: []byte("foo"), Value: 9999},
},
},
},
}

for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
keeper, ctx, cleanup := setupKeeper(t)
defer cleanup()

require.NoError(t, types.ValidateGenesis(spec.src))
got := InitGenesis(ctx, keeper, spec.src)
if spec.expSuccess {
require.NoError(t, got)
return
}
require.Error(t, got)
})
}
}

func setupKeeper(t *testing.T) (Keeper, sdk.Context, func()) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
Expand Down
39 changes: 37 additions & 2 deletions x/wasm/internal/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"path/filepath"

"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/pkg/errors"

wasm "github.com/CosmWasm/go-cosmwasm"
wasmTypes "github.com/CosmWasm/go-cosmwasm/types"
Expand Down Expand Up @@ -374,6 +375,11 @@ func (k Keeper) GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress)
return &contract
}

func (k Keeper) containsContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool {
store := ctx.KVStore(k.storeKey)
return store.Has(types.GetContractAddressKey(contractAddress))
}

func (k Keeper) setContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress, contract *types.ContractInfo) {
store := ctx.KVStore(k.storeKey)
store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshalBinaryBare(contract))
Expand All @@ -398,15 +404,19 @@ func (k Keeper) GetContractState(ctx sdk.Context, contractAddress sdk.AccAddress
return prefixStore.Iterator(nil, nil)
}

func (k Keeper) setContractState(ctx sdk.Context, contractAddress sdk.AccAddress, models []types.Model) {
func (k Keeper) importContractState(ctx sdk.Context, contractAddress sdk.AccAddress, models []types.Model) error {
prefixStoreKey := types.GetContractStorePrefixKey(contractAddress)
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
for _, model := range models {
if model.Value == nil {
model.Value = []byte{}
}
if prefixStore.Has(model.Key) {
return sdkerrors.Wrapf(types.ErrDuplicate, "duplicate key: %x", model.Key)
}
prefixStore.Set(model.Key, model.Value)
}
return nil
}

func (k Keeper) GetCodeInfo(ctx sdk.Context, codeID uint64) *types.CodeInfo {
Expand All @@ -420,6 +430,11 @@ func (k Keeper) GetCodeInfo(ctx sdk.Context, codeID uint64) *types.CodeInfo {
return &codeInfo
}

func (k Keeper) containsCodeInfo(ctx sdk.Context, codeID uint64) bool {
store := ctx.KVStore(k.storeKey)
return store.Has(types.GetCodeKey(codeID))
}

func (k Keeper) GetByteCode(ctx sdk.Context, codeID uint64) ([]byte, error) {
store := ctx.KVStore(k.storeKey)
var codeInfo types.CodeInfo
Expand Down Expand Up @@ -457,10 +472,15 @@ func consumeGas(ctx sdk.Context, gas uint64) {
// generates a contract address from codeID + instanceID
func (k Keeper) generateContractAddress(ctx sdk.Context, codeID uint64) sdk.AccAddress {
instanceID := k.autoIncrementID(ctx, types.KeyLastInstanceID)
return contractAddress(codeID, instanceID)
}

func contractAddress(codeID, instanceID uint64) sdk.AccAddress {
// NOTE: It is possible to get a duplicate address if either codeID or instanceID
// overflow 32 bits. This is highly improbable, but something that could be refactored.
contractID := codeID<<32 + instanceID
return addrFromUint64(contractID)

}

func (k Keeper) GetNextCodeID(ctx sdk.Context) uint64 {
Expand Down Expand Up @@ -496,10 +516,25 @@ func (k Keeper) peekAutoIncrementID(ctx sdk.Context, lastIDKey []byte) uint64 {
return id
}

func (k Keeper) setAutoIncrementID(ctx sdk.Context, lastIDKey []byte, val uint64) {
func (k Keeper) importAutoIncrementID(ctx sdk.Context, lastIDKey []byte, val uint64) error {
store := ctx.KVStore(k.storeKey)
if store.Has(lastIDKey) {
return sdkerrors.Wrapf(types.ErrDuplicate, "autoincrement id: %s", string(lastIDKey))
}
bz := sdk.Uint64ToBigEndian(val)
store.Set(lastIDKey, bz)
return nil
}

func (k Keeper) importContract(ctx sdk.Context, address sdk.AccAddress, c *types.ContractInfo, state []types.Model) error {
if !k.containsCodeInfo(ctx, c.CodeID) {
return errors.Wrapf(types.ErrNotFound, "code id: %d", c.CodeID)
}
if k.containsContractInfo(ctx, address) {
return errors.Wrapf(types.ErrDuplicate, "contract: %s", address)
}
k.setContractInfo(ctx, address, c)
return k.importContractState(ctx, address, state)
}

func addrFromUint64(id uint64) sdk.AccAddress {
Expand Down
2 changes: 1 addition & 1 deletion x/wasm/internal/keeper/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestQueryContractState(t *testing.T) {
{Key: []byte("foo"), Value: []byte(`"bar"`)},
{Key: []byte{0x0, 0x1}, Value: []byte(`{"count":8}`)},
}
keeper.setContractState(ctx, addr, contractModel)
keeper.importContractState(ctx, addr, contractModel)

// this gets us full error, not redacted sdk.Error
q := NewQuerier(keeper)
Expand Down
12 changes: 12 additions & 0 deletions x/wasm/internal/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,16 @@ var (

// ErrMigrationFailed error for rust execution contract failure
ErrMigrationFailed = sdkErrors.Register(DefaultCodespace, 10, "migrate wasm contract failed")

// ErrEmpty error for empty content
ErrEmpty = sdkErrors.Register(DefaultCodespace, 11, "empty")

// ErrLimit error for content that exceeds a limit
ErrLimit = sdkErrors.Register(DefaultCodespace, 12, "exceeds limit")

// ErrInvalid error for content that is invalid in this context
ErrInvalid = sdkErrors.Register(DefaultCodespace, 13, "invalid")

// ErrDuplicate error for content that exsists
ErrDuplicate = sdkErrors.Register(DefaultCodespace, 14, "duplicate")
)
Loading

0 comments on commit 1cdda2d

Please sign in to comment.