Merge pull request #168 from CosmWasm/genesis_io_codeids

Genesis code import not position agnostic
This commit is contained in:
Ethan Frey 2020-07-01 12:12:53 +02:00 committed by GitHub
commit 3f62e8a001
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 204 additions and 13 deletions

View File

@ -1,8 +1,6 @@
package keeper package keeper
import ( import (
"bytes"
"github.com/CosmWasm/wasmd/x/wasm/internal/types" "github.com/CosmWasm/wasmd/x/wasm/internal/types"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
@ -14,23 +12,24 @@ import (
// //
// CONTRACT: all types of accounts must have been already initialized/created // CONTRACT: all types of accounts must have been already initialized/created
func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) error { func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) error {
var maxCodeID uint64
for i, code := range data.Codes { for i, code := range data.Codes {
newId, err := keeper.Create(ctx, code.CodeInfo.Creator, code.CodesBytes, code.CodeInfo.Source, code.CodeInfo.Builder) err := keeper.importCode(ctx, code.CodeID, code.CodeInfo, code.CodesBytes)
if err != nil { if err != nil {
return sdkerrors.Wrapf(err, "code number %d", i) return sdkerrors.Wrapf(err, "code %d with id: %d", i, code.CodeID)
} }
newInfo := keeper.GetCodeInfo(ctx, newId) if code.CodeID > maxCodeID {
if !bytes.Equal(code.CodeInfo.CodeHash, newInfo.CodeHash) { maxCodeID = code.CodeID
return sdkerrors.Wrap(types.ErrInvalid, "code hashes not same")
} }
} }
var maxContractID int
for i, contract := range data.Contracts { for i, contract := range data.Contracts {
err := keeper.importContract(ctx, contract.ContractAddress, &contract.ContractInfo, contract.ContractState) err := keeper.importContract(ctx, contract.ContractAddress, &contract.ContractInfo, contract.ContractState)
if err != nil { if err != nil {
return sdkerrors.Wrapf(err, "contract number %d", i) return sdkerrors.Wrapf(err, "contract number %d", i)
} }
maxContractID = i + 1 // not ideal but max(contractID) is not persisted otherwise
} }
for i, seq := range data.Sequences { for i, seq := range data.Sequences {
@ -39,6 +38,15 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) error
return sdkerrors.Wrapf(err, "sequence number %d", i) return sdkerrors.Wrapf(err, "sequence number %d", i)
} }
} }
// sanity check seq values
if keeper.peekAutoIncrementID(ctx, types.KeyLastCodeID) <= maxCodeID {
return sdkerrors.Wrapf(types.ErrInvalid, "seq %s must be greater %d ", string(types.KeyLastCodeID), maxCodeID)
}
if keeper.peekAutoIncrementID(ctx, types.KeyLastInstanceID) <= uint64(maxContractID) {
return sdkerrors.Wrapf(types.ErrInvalid, "seq %s must be greater %d ", string(types.KeyLastInstanceID), maxContractID)
}
return nil return nil
} }
@ -48,11 +56,15 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState {
maxCodeID := keeper.GetNextCodeID(ctx) maxCodeID := keeper.GetNextCodeID(ctx)
for i := uint64(1); i < maxCodeID; i++ { for i := uint64(1); i < maxCodeID; i++ {
if !keeper.containsCodeInfo(ctx, i) {
continue
}
bytecode, err := keeper.GetByteCode(ctx, i) bytecode, err := keeper.GetByteCode(ctx, i)
if err != nil { if err != nil {
panic(err) panic(err)
} }
genState.Codes = append(genState.Codes, types.Code{ genState.Codes = append(genState.Codes, types.Code{
CodeID: i,
CodeInfo: *keeper.GetCodeInfo(ctx, i), CodeInfo: *keeper.GetCodeInfo(ctx, i),
CodesBytes: bytecode, CodesBytes: bytecode,
}) })
@ -78,8 +90,7 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState {
return false return false
}) })
// types.KeyLastCodeID is updated via keeper create for _, k := range [][]byte{types.KeyLastCodeID, types.KeyLastInstanceID} {
for _, k := range [][]byte{types.KeyLastInstanceID} {
genState.Sequences = append(genState.Sequences, types.Sequence{ genState.Sequences = append(genState.Sequences, types.Sequence{
IDKey: k, IDKey: k,
Value: keeper.peekAutoIncrementID(ctx, k), Value: keeper.peekAutoIncrementID(ctx, k),

View File

@ -3,6 +3,7 @@ package keeper
import ( import (
"crypto/sha256" "crypto/sha256"
"io/ioutil" "io/ioutil"
"math/rand"
"os" "os"
"testing" "testing"
"time" "time"
@ -49,9 +50,21 @@ func TestGenesisExportImport(t *testing.T) {
// export // export
genesisState := ExportGenesis(srcCtx, srcKeeper) genesisState := ExportGenesis(srcCtx, srcKeeper)
// order should not matter
rand.Shuffle(len(genesisState.Codes), func(i, j int) {
genesisState.Codes[i], genesisState.Codes[j] = genesisState.Codes[j], genesisState.Codes[i]
})
rand.Shuffle(len(genesisState.Contracts), func(i, j int) {
genesisState.Contracts[i], genesisState.Contracts[j] = genesisState.Contracts[j], genesisState.Contracts[i]
})
rand.Shuffle(len(genesisState.Sequences), func(i, j int) {
genesisState.Sequences[i], genesisState.Sequences[j] = genesisState.Sequences[j], genesisState.Sequences[i]
})
// re-import // re-import
dstKeeper, dstCtx, dstCleanup := setupKeeper(t) dstKeeper, dstCtx, dstCleanup := setupKeeper(t)
defer dstCleanup() defer dstCleanup()
InitGenesis(dstCtx, dstKeeper, genesisState) InitGenesis(dstCtx, dstKeeper, genesisState)
// compare whole DB // compare whole DB
@ -81,6 +94,7 @@ func TestFailFastImport(t *testing.T) {
"happy path: code info correct": { "happy path: code info correct": {
src: types.GenesisState{ src: types.GenesisState{
Codes: []types.Code{{ Codes: []types.Code{{
CodeID: 1,
CodeInfo: wasmTypes.CodeInfo{ CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:], CodeHash: codeHash[:],
Creator: anyAddress, Creator: anyAddress,
@ -88,11 +102,66 @@ func TestFailFastImport(t *testing.T) {
CodesBytes: wasmCode, CodesBytes: wasmCode,
}}, }},
Contracts: nil, Contracts: nil,
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 2},
{IDKey: types.KeyLastInstanceID, Value: 1},
},
},
expSuccess: true,
},
"happy path: code ids can contain gaps": {
src: types.GenesisState{
Codes: []types.Code{{
CodeID: 1,
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
}, {
CodeID: 3,
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
}},
Contracts: nil,
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 10},
{IDKey: types.KeyLastInstanceID, Value: 1},
},
},
expSuccess: true,
},
"happy path: code order does not matter": {
src: types.GenesisState{
Codes: []types.Code{{
CodeID: 2,
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
}, {
CodeID: 1,
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
}},
Contracts: nil,
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 3},
{IDKey: types.KeyLastInstanceID, Value: 1},
},
}, },
expSuccess: true, expSuccess: true,
}, },
"prevent code hash mismatch": {src: types.GenesisState{ "prevent code hash mismatch": {src: types.GenesisState{
Codes: []types.Code{{ Codes: []types.Code{{
CodeID: 1,
CodeInfo: wasmTypes.CodeInfo{ CodeInfo: wasmTypes.CodeInfo{
CodeHash: make([]byte, len(codeHash)), CodeHash: make([]byte, len(codeHash)),
Creator: anyAddress, Creator: anyAddress,
@ -101,9 +170,30 @@ func TestFailFastImport(t *testing.T) {
}}, }},
Contracts: nil, Contracts: nil,
}}, }},
"prevent duplicate codeIDs": {src: types.GenesisState{
Codes: []types.Code{
{
CodeID: 1,
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
},
{
CodeID: 1,
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
},
},
}},
"happy path: code id in info and contract do match": { "happy path: code id in info and contract do match": {
src: types.GenesisState{ src: types.GenesisState{
Codes: []types.Code{{ Codes: []types.Code{{
CodeID: 1,
CodeInfo: wasmTypes.CodeInfo{ CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:], CodeHash: codeHash[:],
Creator: anyAddress, Creator: anyAddress,
@ -116,12 +206,17 @@ func TestFailFastImport(t *testing.T) {
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }), ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }),
}, },
}, },
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 2},
{IDKey: types.KeyLastInstanceID, Value: 2},
},
}, },
expSuccess: true, expSuccess: true,
}, },
"happy path: code info with two contracts": { "happy path: code info with two contracts": {
src: types.GenesisState{ src: types.GenesisState{
Codes: []types.Code{{ Codes: []types.Code{{
CodeID: 1,
CodeInfo: wasmTypes.CodeInfo{ CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:], CodeHash: codeHash[:],
Creator: anyAddress, Creator: anyAddress,
@ -133,10 +228,14 @@ func TestFailFastImport(t *testing.T) {
ContractAddress: contractAddress(1, 1), ContractAddress: contractAddress(1, 1),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }), ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }),
}, { }, {
ContractAddress: contractAddress(2, 1), ContractAddress: contractAddress(1, 2),
ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }), ContractInfo: types.ContractInfoFixture(func(c *wasmTypes.ContractInfo) { c.CodeID = 1 }),
}, },
}, },
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 2},
{IDKey: types.KeyLastInstanceID, Value: 3},
},
}, },
expSuccess: true, expSuccess: true,
}, },
@ -153,6 +252,7 @@ func TestFailFastImport(t *testing.T) {
"prevent duplicate contract address": { "prevent duplicate contract address": {
src: types.GenesisState{ src: types.GenesisState{
Codes: []types.Code{{ Codes: []types.Code{{
CodeID: 1,
CodeInfo: wasmTypes.CodeInfo{ CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:], CodeHash: codeHash[:],
Creator: anyAddress, Creator: anyAddress,
@ -173,6 +273,7 @@ func TestFailFastImport(t *testing.T) {
"prevent duplicate contract model keys": { "prevent duplicate contract model keys": {
src: types.GenesisState{ src: types.GenesisState{
Codes: []types.Code{{ Codes: []types.Code{{
CodeID: 1,
CodeInfo: wasmTypes.CodeInfo{ CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:], CodeHash: codeHash[:],
Creator: anyAddress, Creator: anyAddress,
@ -205,6 +306,43 @@ func TestFailFastImport(t *testing.T) {
}, },
}, },
}, },
"prevent code id seq init value == max codeID used": {
src: types.GenesisState{
Codes: []types.Code{{
CodeID: 2,
CodeInfo: wasmTypes.CodeInfo{
CodeHash: codeHash[:],
Creator: anyAddress,
},
CodesBytes: wasmCode,
}},
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 1},
},
},
},
"prevent contract id seq init value == count contracts": {
src: types.GenesisState{
Codes: []types.Code{{
CodeID: 1,
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 }),
},
},
Sequences: []types.Sequence{
{IDKey: types.KeyLastCodeID, Value: 2},
{IDKey: types.KeyLastInstanceID, Value: 1},
},
},
},
} }
for msg, spec := range specs { for msg, spec := range specs {

View File

@ -1,6 +1,7 @@
package keeper package keeper
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"path/filepath" "path/filepath"
@ -97,6 +98,29 @@ func (k Keeper) Create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte,
return codeID, nil return codeID, nil
} }
func (k Keeper) importCode(ctx sdk.Context, codeID uint64, codeInfo types.CodeInfo, wasmCode []byte) error {
wasmCode, err := uncompress(wasmCode)
if err != nil {
return sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
}
newCodeHash, err := k.wasmer.Create(wasmCode)
if err != nil {
return sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
}
if !bytes.Equal(codeInfo.CodeHash, newCodeHash) {
return sdkerrors.Wrap(types.ErrInvalid, "code hashes not same")
}
store := ctx.KVStore(k.storeKey)
key := types.GetCodeKey(codeID)
if store.Has(key) {
return sdkerrors.Wrapf(types.ErrDuplicate, "duplicate code: %d", codeID)
}
// 0x01 | codeID (uint64) -> ContractInfo
store.Set(key, k.cdc.MustMarshalBinaryBare(codeInfo))
return nil
}
// Instantiate creates an instance of a WASM contract // Instantiate creates an instance of a WASM contract
func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, error) { func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, error) {
ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: init") ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: init")

View File

@ -1,5 +1,6 @@
package types package types
import "C"
import ( import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
@ -45,11 +46,15 @@ func (s GenesisState) ValidateBasic() error {
// Code struct encompasses CodeInfo and CodeBytes // Code struct encompasses CodeInfo and CodeBytes
type Code struct { type Code struct {
CodeID uint64 `json:"code_id"`
CodeInfo CodeInfo `json:"code_info"` CodeInfo CodeInfo `json:"code_info"`
CodesBytes []byte `json:"code_bytes"` CodesBytes []byte `json:"code_bytes"`
} }
func (c Code) ValidateBasic() error { func (c Code) ValidateBasic() error {
if c.CodeID == 0 {
return sdkerrors.Wrap(ErrEmpty, "code id")
}
if err := c.CodeInfo.ValidateBasic(); err != nil { if err := c.CodeInfo.ValidateBasic(); err != nil {
return sdkerrors.Wrap(err, "code info") return sdkerrors.Wrap(err, "code info")
} }

View File

@ -53,6 +53,12 @@ func TestCodeValidateBasic(t *testing.T) {
expError bool expError bool
}{ }{
"all good": {srcMutator: func(_ *Code) {}}, "all good": {srcMutator: func(_ *Code) {}},
"code id invalid": {
srcMutator: func(c *Code) {
c.CodeID = 0
},
expError: true,
},
"codeinfo invalid": { "codeinfo invalid": {
srcMutator: func(c *Code) { srcMutator: func(c *Code) {
c.CodeInfo.CodeHash = nil c.CodeInfo.CodeHash = nil

View File

@ -1,6 +1,8 @@
package types package types
import ( import (
"encoding/binary"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
@ -32,11 +34,15 @@ var (
) )
// GetCodeKey constructs the key for retreiving the ID for the WASM code // GetCodeKey constructs the key for retreiving the ID for the WASM code
func GetCodeKey(contractID uint64) []byte { func GetCodeKey(codeID uint64) []byte {
contractIDBz := sdk.Uint64ToBigEndian(contractID) contractIDBz := sdk.Uint64ToBigEndian(codeID)
return append(CodeKeyPrefix, contractIDBz...) return append(CodeKeyPrefix, contractIDBz...)
} }
func decodeCodeKey(src []byte) uint64 {
return binary.BigEndian.Uint64(src[len(CodeKeyPrefix):])
}
// GetContractAddressKey returns the key for the WASM contract instance // GetContractAddressKey returns the key for the WASM contract instance
func GetContractAddressKey(addr sdk.AccAddress) []byte { func GetContractAddressKey(addr sdk.AccAddress) []byte {
return append(ContractKeyPrefix, addr...) return append(ContractKeyPrefix, addr...)

View File

@ -43,6 +43,7 @@ func CodeFixture(mutators ...func(*Code)) Code {
anyAddress := make([]byte, 20) anyAddress := make([]byte, 20)
fixture := Code{ fixture := Code{
CodeID: 1,
CodeInfo: CodeInfo{ CodeInfo: CodeInfo{
CodeHash: codeHash[:], CodeHash: codeHash[:],
Creator: anyAddress, Creator: anyAddress,