Skip to content

Commit

Permalink
fix(octane): explicit execution head state (#1260)
Browse files Browse the repository at this point in the history
Explicit tracking of execution head height and hash from genesis onward.

This ensures that blocks MUST be built on latest expected head all the
way from genesis.

task: none
  • Loading branch information
corverroos authored Jun 19, 2024
1 parent 45ef659 commit 0808374
Show file tree
Hide file tree
Showing 26 changed files with 967 additions and 215 deletions.
16 changes: 3 additions & 13 deletions e2e/app/geth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,17 @@ import (
"path/filepath"
"time"

"github.com/omni-network/omni/e2e/app/eoa"
"github.com/omni-network/omni/e2e/types"
evmgenutil "github.com/omni-network/omni/halo/genutil/evm"
"github.com/omni-network/omni/lib/errors"

"github.com/ethereum/go-ethereum/core"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/downloader"
)

// WriteAllConfig writes all the geth config files for all omniEVMs.
func WriteAllConfig(testnet types.Testnet) error {
admin, err := eoa.Admin(testnet.Network)
if err != nil {
return errors.Wrap(err, "admin")
}

gethGenesis, err := evmgenutil.MakeGenesis(testnet.Network, admin)
if err != nil {
return errors.Wrap(err, "make genesis")
}
gethGenesisBz, err := json.MarshalIndent(gethGenesis, "", " ")
func WriteAllConfig(testnet types.Testnet, genesis core.Genesis) error {
gethGenesisBz, err := json.MarshalIndent(genesis, "", " ")
if err != nil {
return errors.Wrap(err, "marshal genesis")
}
Expand Down
25 changes: 20 additions & 5 deletions e2e/app/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
halocmd "github.com/omni-network/omni/halo/cmd"
halocfg "github.com/omni-network/omni/halo/config"
"github.com/omni-network/omni/halo/genutil"
evmgenutil "github.com/omni-network/omni/halo/genutil/evm"
"github.com/omni-network/omni/lib/errors"
"github.com/omni-network/omni/lib/k1util"
"github.com/omni-network/omni/lib/log"
Expand Down Expand Up @@ -64,14 +65,32 @@ func Setup(ctx context.Context, def Definition, depCfg DeployConfig) error {
return SetupOnlyMonitor(ctx, def)
}

// Setup geth execution genesis
admin, err := eoa.Admin(def.Testnet.Network)
if err != nil {
return errors.Wrap(err, "admin")
}
gethGenesis, err := evmgenutil.MakeGenesis(def.Manifest.Network, admin)
if err != nil {
return errors.Wrap(err, "make genesis")
}
if err := geth.WriteAllConfig(def.Testnet, gethGenesis); err != nil {
return err
}

// Setup halo consensus genesis
var vals []crypto.PubKey
var valPrivKeys []crypto.PrivKey
for val := range def.Testnet.Validators {
vals = append(vals, val.PrivvalKey.PubKey())
valPrivKeys = append(valPrivKeys, val.PrivvalKey)
}

cosmosGenesis, err := genutil.MakeGenesis(def.Manifest.Network, time.Now(), vals...)
cosmosGenesis, err := genutil.MakeGenesis(
def.Manifest.Network,
time.Now(),
gethGenesis.ToBlock().Hash(),
vals...)
if err != nil {
return errors.Wrap(err, "make genesis")
}
Expand All @@ -80,10 +99,6 @@ func Setup(ctx context.Context, def Definition, depCfg DeployConfig) error {
return errors.Wrap(err, "convert genesis")
}

if err := geth.WriteAllConfig(def.Testnet); err != nil {
return err
}

logCfg := logConfig(def)
if err := writeMonitorConfig(ctx, def, logCfg, valPrivKeys); err != nil {
return err
Expand Down
34 changes: 27 additions & 7 deletions halo/app/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,30 @@ import (
"github.com/omni-network/omni/lib/log"

abci "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"

storetypes "cosmossdk.io/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

type finaliseCallback func(context.Context, *abci.RequestFinalizeBlock, *abci.ResponseFinalizeBlock) error
type postFinalizeCallback func(sdk.Context) error
type multiStoreProvider func() storetypes.CacheMultiStore

type abciWrapper struct {
abci.Application
finaliseCallback finaliseCallback
postFinalize postFinalizeCallback
multiStoreProvider multiStoreProvider
}

func newABCIWrapper(app abci.Application, finaliseCallback finaliseCallback) *abciWrapper {
func newABCIWrapper(
app abci.Application,
finaliseCallback postFinalizeCallback,
multiStoreProvider multiStoreProvider,
) *abciWrapper {
return &abciWrapper{
Application: app,
finaliseCallback: finaliseCallback,
Application: app,
postFinalize: finaliseCallback,
multiStoreProvider: multiStoreProvider,
}
}

Expand Down Expand Up @@ -86,8 +97,17 @@ func (l abciWrapper) FinalizeBlock(ctx context.Context, req *abci.RequestFinaliz
return resp, err
}

if err := l.finaliseCallback(ctx, req, resp); err != nil {
log.Error(ctx, "Finalize callback failed [BUG]", err, "height", req.Height)
// Call custom `PostFinalize` callback after the block is finalized.
header := cmtproto.Header{
Height: req.Height,
Time: req.Time,
ProposerAddress: req.ProposerAddress,
NextValidatorsHash: req.NextValidatorsHash,
AppHash: resp.AppHash, // The app hash after the block is finalized.
}
sdkCtx := sdk.NewContext(l.multiStoreProvider(), header, false, nil)
if err := l.postFinalize(sdkCtx); err != nil {
log.Error(ctx, "PostFinalize callback failed [BUG]", err, "height", req.Height)
return resp, err
}

Expand Down
1 change: 1 addition & 0 deletions halo/app/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ var (
slashingtypes.ModuleName,
genutiltypes.ModuleName,
valsynctypes.ModuleName,
engevmtypes.ModuleName,
}

beginBlockers = []string{
Expand Down
6 changes: 5 additions & 1 deletion halo/app/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
pruningtypes "cosmossdk.io/store/pruning/types"
"cosmossdk.io/store/snapshots"
snapshottypes "cosmossdk.io/store/snapshots/types"
storetypes "cosmossdk.io/store/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/server"
Expand Down Expand Up @@ -209,7 +210,10 @@ func newCometNode(ctx context.Context, cfg *cmtcfg.Config, app *App, privVal cmt

wrapper := newABCIWrapper(
server.NewCometABCIWrapper(app),
app.EVMEngKeeper.Finalize,
app.EVMEngKeeper.PostFinalize,
func() storetypes.CacheMultiStore {
return app.CommitMultiStore().CacheMultiStore()
},
)

cmtNode, err := node.NewNode(cfg,
Expand Down
13 changes: 9 additions & 4 deletions halo/app/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
halocmd "github.com/omni-network/omni/halo/cmd"
halocfg "github.com/omni-network/omni/halo/config"
cprovider "github.com/omni-network/omni/lib/cchain/provider"
"github.com/omni-network/omni/lib/ethclient"
"github.com/omni-network/omni/lib/log"
"github.com/omni-network/omni/lib/netconf"
"github.com/omni-network/omni/lib/tutil"
Expand Down Expand Up @@ -150,10 +151,14 @@ func setupSimnet(t *testing.T) haloapp.Config {
Comet: cmtCfg,
}

err := halocmd.InitFiles(log.WithNoopLogger(context.Background()), halocmd.InitConfig{
HomeDir: homeDir,
Network: netconf.Simnet,
Cosmos: true,
executionGenesis, err := ethclient.MockGenesisBlock()
tutil.RequireNoError(t, err)

err = halocmd.InitFiles(log.WithNoopLogger(context.Background()), halocmd.InitConfig{
HomeDir: homeDir,
Network: netconf.Simnet,
Cosmos: true,
ExecutionHash: executionGenesis.Hash(),
})
tutil.RequireNoError(t, err)

Expand Down
19 changes: 11 additions & 8 deletions halo/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,21 @@ import (
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
"github.com/cometbft/cometbft/types"

"github.com/ethereum/go-ethereum/common"

"github.com/spf13/cobra"
)

// InitConfig is the config for the init command.
type InitConfig struct {
HomeDir string
Network netconf.ID
TrustedSync bool
RCPEndpoints xchain.RPCEndpoints
Force bool
Clean bool
Cosmos bool
HomeDir string
Network netconf.ID
TrustedSync bool
RCPEndpoints xchain.RPCEndpoints
Force bool
Clean bool
Cosmos bool
ExecutionHash common.Hash
}

// newInitCmd returns a new cobra command that initializes the files and folders required by halo.
Expand Down Expand Up @@ -230,7 +233,7 @@ func InitFiles(ctx context.Context, initCfg InitConfig) error {

var genDoc *types.GenesisDoc
if initCfg.Cosmos {
cosmosGen, err := genutil.MakeGenesis(network, time.Now(), pubKey)
cosmosGen, err := genutil.MakeGenesis(network, time.Now(), initCfg.ExecutionHash, pubKey)
if err != nil {
return err
}
Expand Down
20 changes: 17 additions & 3 deletions halo/genutil/genutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (

"github.com/cometbft/cometbft/crypto"

"github.com/ethereum/go-ethereum/common"

"cosmossdk.io/math"
"cosmossdk.io/x/tx/signing"
"github.com/cosmos/cosmos-sdk/client"
Expand Down Expand Up @@ -43,12 +45,17 @@ const slashingBlocksWindow = 1000
// It is also the minimum stake enforced by the omni staking contract.
const validatorPower = 100

func MakeGenesis(network netconf.ID, genesisTime time.Time, valPubkeys ...crypto.PubKey) (*gtypes.AppGenesis, error) {
func MakeGenesis(
network netconf.ID,
genesisTime time.Time,
executionBlockHash common.Hash,
valPubkeys ...crypto.PubKey,
) (*gtypes.AppGenesis, error) {
cdc := getCodec()
txConfig := authtx.NewTxConfig(cdc, nil)

// Step 1: Create the default genesis app state for all modules.
appState1 := defaultAppState(network.Static().MaxValidators, cdc.MustMarshalJSON)
appState1 := defaultAppState(network.Static().MaxValidators, executionBlockHash, cdc.MustMarshalJSON)
appState1Bz, err := json.MarshalIndent(appState1, "", " ")
if err != nil {
return nil, errors.Wrap(err, "marshal app state")
Expand Down Expand Up @@ -209,20 +216,27 @@ func addValidator(txConfig client.TxConfig, pubkey crypto.PubKey, cdc codec.Code
}

// defaultAppState returns the default genesis application state.
func defaultAppState(maxVals uint32, marshal func(proto.Message) []byte) map[string]json.RawMessage {
func defaultAppState(
maxVals uint32,
executionBlockHash common.Hash,
marshal func(proto.Message) []byte,
) map[string]json.RawMessage {
stakingGenesis := sttypes.DefaultGenesisState()
stakingGenesis.Params.MaxValidators = maxVals

slashingGenesis := sltypes.DefaultGenesisState()
slashingGenesis.Params.SignedBlocksWindow = slashingBlocksWindow

evmengGenesis := etypes.NewGenesisState(executionBlockHash)

return map[string]json.RawMessage{
sttypes.ModuleName: marshal(stakingGenesis),
sltypes.ModuleName: marshal(slashingGenesis),
atypes.ModuleName: marshal(atypes.DefaultGenesisState()),
btypes.ModuleName: marshal(btypes.DefaultGenesisState()),
dtypes.ModuleName: marshal(dtypes.DefaultGenesisState()),
vtypes.ModuleName: marshal(vtypes.DefaultGenesisState()),
etypes.ModuleName: marshal(evmengGenesis),
}
}

Expand Down
6 changes: 5 additions & 1 deletion halo/genutil/genutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (

k1 "github.com/cometbft/cometbft/crypto/secp256k1"

"github.com/ethereum/go-ethereum/common"

_ "github.com/omni-network/omni/halo/app" // To init SDK config.
)

Expand All @@ -22,7 +24,9 @@ func TestMakeGenesis(t *testing.T) {
val1 := k1.GenPrivKeySecp256k1([]byte("secret1")).PubKey()
val2 := k1.GenPrivKeySecp256k1([]byte("secret2")).PubKey()

resp, err := genutil.MakeGenesis(netconf.Simnet, timestamp, val1, val2)
executionBlockHash := common.BytesToHash([]byte("blockhash"))

resp, err := genutil.MakeGenesis(netconf.Simnet, timestamp, executionBlockHash, val1, val2)
tutil.RequireNoError(t, err)

tutil.RequireGoldenJSON(t, resp)
Expand Down
3 changes: 3 additions & 0 deletions halo/genutil/testdata/TestMakeGenesis.golden
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@
"delegator_starting_infos": [],
"validator_slash_events": []
},
"evmengine": {
"execution_block_hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABibG9ja2hhc2g="
},
"genutil": {
"gen_txs": [
{
Expand Down
44 changes: 34 additions & 10 deletions lib/ethclient/enginemock.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,32 +134,41 @@ func hasRandomErr(ctx context.Context) bool {
return ok && v
}

// NewEngineMock returns a new mock engine API client.
//
// Note only some methods are implemented, it will panic if you call an unimplemented method.
func NewEngineMock(opts ...func(mock *engineMock)) (EngineClient, error) {
// MockGenesisBlock returns a deterministic genesis block for testing.
func MockGenesisBlock() (*types.Block, error) {
// Deterministic genesis block
var (
// Deterministic genesis block
height uint64 // 0
parentHash common.Hash
parentBeaconRoot common.Hash
timestamp = time.Now().Truncate(time.Hour * 24).Unix() // TODO(corver): Improve this.

// Deterministic fuzzer
fuzzer = NewFuzzer(timestamp)
timestamp = time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC).Unix()
fuzzer = NewFuzzer(timestamp)
)

genesisPayload, err := makePayload(fuzzer, height, uint64(timestamp), parentHash, common.Address{}, parentHash, &parentBeaconRoot)
if err != nil {
return nil, errors.Wrap(err, "make next payload")
}

genesisBlock, err := engine.ExecutableDataToBlock(genesisPayload, nil, &parentBeaconRoot)
if err != nil {
return nil, errors.Wrap(err, "executable data to block")
}

return genesisBlock, nil
}

// NewEngineMock returns a new mock engine API client.
//
// Note only some methods are implemented, it will panic if you call an unimplemented method.
func NewEngineMock(opts ...func(mock *engineMock)) (EngineClient, error) {
genesisBlock, err := MockGenesisBlock()
if err != nil {
return nil, err
}

m := &engineMock{
fuzzer: fuzzer,
fuzzer: NewFuzzer(int64(genesisBlock.Time())),
head: genesisBlock,
pendingLogs: make(map[common.Address][]types.Log),
payloads: make(map[engine.PayloadID]payloadArgs),
Expand Down Expand Up @@ -251,6 +260,21 @@ func (m *engineMock) HeaderByType(ctx context.Context, typ HeadType) (*types.Hea
return m.HeaderByNumber(ctx, big.NewInt(int64(number)))
}

func (m *engineMock) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
if err := m.maybeErr(ctx); err != nil {
return nil, err
}

m.mu.Lock()
defer m.mu.Unlock()

if hash != m.head.Hash() {
return nil, errors.New("only head hash supported") // Only support latest block
}

return m.head.Header(), nil
}

func (m *engineMock) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
if err := m.maybeErr(ctx); err != nil {
return nil, err
Expand Down
Loading

0 comments on commit 0808374

Please sign in to comment.