Skip to content

Commit

Permalink
feat: allow (de)activation of the wasm connection with an access cont…
Browse files Browse the repository at this point in the history
…rol command (#2166)
  • Loading branch information
cgorenflo authored Jul 30, 2024
1 parent 35082d5 commit 25482b4
Show file tree
Hide file tree
Showing 10 changed files with 520 additions and 50 deletions.
18 changes: 18 additions & 0 deletions x/nexus/keeper/chain.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper

import (
"bytes"
"fmt"
"time"

Expand Down Expand Up @@ -129,6 +130,23 @@ func (k Keeper) DeactivateChain(ctx sdk.Context, chain exported.Chain) {
k.setChainState(ctx, chainState)
}

const wasmIsActivated = 1
const wasmIsDeactivated = 0

func (k Keeper) ActivateWasmConnection(ctx sdk.Context) {
k.getStore(ctx).SetRawNew(wasmActivation, []byte{wasmIsActivated})
}

func (k Keeper) DeactivateWasmConnection(ctx sdk.Context) {
k.getStore(ctx).SetRawNew(wasmActivation, []byte{wasmIsDeactivated})
}

func (k Keeper) IsWasmConnectionActivated(ctx sdk.Context) bool {
bz := k.getStore(ctx).GetRawNew(wasmActivation)
// either it has never been set or it's active
return bz == nil || bytes.Equal(bz, []byte{wasmIsActivated})
}

// IsChainActivated returns true if the given chain is activated; false otherwise
func (k Keeper) IsChainActivated(ctx sdk.Context, chain exported.Chain) bool {
chainState, ok := k.getChainState(ctx, chain)
Expand Down
1 change: 1 addition & 0 deletions x/nexus/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
generalMessagePrefix = key.RegisterStaticKey(types.ModuleName, 4)
processingMessagePrefix = key.RegisterStaticKey(types.ModuleName, 5)
messageNonceKey = key.RegisterStaticKey(types.ModuleName, 6)
wasmActivation = key.RegisterStaticKey(types.ModuleName, 7)

// temporary
// TODO: add description about what temporary means
Expand Down
4 changes: 4 additions & 0 deletions x/nexus/keeper/msg_dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ func NewMessenger(nexus types.Nexus) Messenger {

// DispatchMsg decodes the messages from the cosmowasm gateway and routes them to the nexus module if possible
func (m Messenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, _ string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
if !m.IsWasmConnectionActivated(ctx) {
return nil, nil, fmt.Errorf("wasm connection is not activated")
}

req, err := encodeRoutingMessage(contractAddr, msg.Custom)
if err != nil {
return nil, nil, err
Expand Down
31 changes: 30 additions & 1 deletion x/nexus/keeper/msg_dispatcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ func TestMessenger_DispatchMsg(t *testing.T) {
givenMessenger := Given("a messenger", func() {
ctx = sdk.NewContext(fake.NewMultiStore(), tmproto.Header{}, false, log.TestingLogger())
nexus = &mock.NexusMock{
LoggerFunc: func(ctx sdk.Context) log.Logger { return ctx.Logger() },
LoggerFunc: func(ctx sdk.Context) log.Logger { return ctx.Logger() },
IsWasmConnectionActivatedFunc: func(sdk.Context) bool { return true },
}

messenger = keeper.NewMessenger(nexus)
})

Expand Down Expand Up @@ -230,3 +232,30 @@ func TestMessenger_DispatchMsg(t *testing.T) {
).
Run(t)
}

func TestMessenger_DispatchMsg_WasmConnectionNotActivated(t *testing.T) {
var (
ctx sdk.Context
messenger keeper.Messenger
nexus *mock.NexusMock
)

contractAddr := rand.AccAddr()

Given("a messenger", func() {
ctx = sdk.NewContext(fake.NewMultiStore(), tmproto.Header{}, false, log.TestingLogger())
nexus = &mock.NexusMock{
LoggerFunc: func(ctx sdk.Context) log.Logger { return ctx.Logger() },
}
messenger = keeper.NewMessenger(nexus)
}).
When("wasm connection is not activated", func() {
nexus.IsWasmConnectionActivatedFunc = func(_ sdk.Context) bool { return false }
}).
Then("should return error", func(t *testing.T) {
_, _, err := messenger.DispatchMsg(ctx, contractAddr, "", wasmvmtypes.CosmosMsg{})

assert.ErrorContains(t, err, "wasm connection is not activated")
}).
Run(t)
}
60 changes: 38 additions & 22 deletions x/nexus/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import (
"github.com/axelarnetwork/axelar-core/x/nexus/types"
snapshot "github.com/axelarnetwork/axelar-core/x/snapshot/exported"
"github.com/axelarnetwork/utils/funcs"
"github.com/axelarnetwork/utils/slices"
)

var _ types.MsgServiceServer = msgServer{}

const allChain = ":all:"
const wasmAsChain = ":wasm:"

type msgServer struct {
types.Nexus
Expand Down Expand Up @@ -127,41 +129,55 @@ func (s msgServer) DeregisterChainMaintainer(c context.Context, req *types.Dereg

func (s msgServer) ActivateChain(c context.Context, req *types.ActivateChainRequest) (*types.ActivateChainResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
if strings.ToLower(req.Chains[0].String()) == allChain {
for _, chain := range s.GetChains(ctx) {
s.activateChain(ctx, chain)
}
} else {
for _, chainStr := range req.Chains {
chain, ok := s.GetChain(ctx, chainStr)
if !ok {
return nil, fmt.Errorf("%s is not a registered chain", chainStr)
}
s.activateChain(ctx, chain)
}

chains, doWasm := s.findRelevantChains(req.Chains, ctx)
for _, chain := range chains {
s.activateChain(ctx, chain)
}
if doWasm {
s.ActivateWasmConnection(ctx)
}

return &types.ActivateChainResponse{}, nil
}

// DeactivateChain handles deactivate chains in case of emergencies
func (s msgServer) DeactivateChain(c context.Context, req *types.DeactivateChainRequest) (*types.DeactivateChainResponse, error) {
ctx := sdk.UnwrapSDKContext(c)

if strings.ToLower(req.Chains[0].String()) == allChain {
for _, chain := range s.GetChains(ctx) {
s.deactivateChain(ctx, chain)
}
chains, doWasm := s.findRelevantChains(req.Chains, ctx)

for _, chain := range chains {
s.deactivateChain(ctx, chain)
}
if doWasm {
s.DeactivateWasmConnection(ctx)
}

return &types.DeactivateChainResponse{}, nil
}

func (s msgServer) findRelevantChains(chainNames []exported.ChainName, ctx sdk.Context) (chains []exported.Chain, doWasm bool) {
doAllChains := strings.ToLower(chainNames[0].String()) == allChain
doWasm = doAllChains || slices.Any(chainNames, chainIsWasm)

if doAllChains {
chains = s.GetChains(ctx)
} else {
for _, chainStr := range req.Chains {
chain, ok := s.GetChain(ctx, chainStr)
for _, chain := range chainNames {
chain, ok := s.GetChain(ctx, chain)
if !ok {
return nil, fmt.Errorf("%s is not a registered chain", chainStr)
continue
}

s.deactivateChain(ctx, chain)
chains = append(chains, chain)
}
}
return &types.DeactivateChainResponse{}, nil

return chains, doWasm
}

func chainIsWasm(chain exported.ChainName) bool {
return strings.ToLower(chain.String()) == wasmAsChain
}

func (s msgServer) activateChain(ctx sdk.Context, chain exported.Chain) {
Expand Down
54 changes: 54 additions & 0 deletions x/nexus/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package keeper_test

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/stretchr/testify/assert"
"github.com/tendermint/tendermint/libs/log"
abci "github.com/tendermint/tendermint/proto/tendermint/types"

"github.com/axelarnetwork/axelar-core/app/params"
"github.com/axelarnetwork/axelar-core/testutils/fake"
nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported"
"github.com/axelarnetwork/axelar-core/x/nexus/keeper"
"github.com/axelarnetwork/axelar-core/x/nexus/types"
"github.com/axelarnetwork/axelar-core/x/nexus/types/mock"
)

func TestMsgServerActivateDeactivateWasm(t *testing.T) {
encodingConfig := params.MakeEncodingConfig()
types.RegisterLegacyAminoCodec(encodingConfig.Amino)
types.RegisterInterfaces(encodingConfig.InterfaceRegistry)
subspace := paramstypes.NewSubspace(encodingConfig.Codec, encodingConfig.Amino, sdk.NewKVStoreKey("paramsKey"), sdk.NewKVStoreKey("tparamsKey"), "nexus")

k := keeper.NewKeeper(
encodingConfig.Codec,
sdk.NewKVStoreKey(types.StoreKey),
subspace,
)

snap := mock.SnapshotterMock{}
slashing := mock.SlashingKeeperMock{}
staking := mock.StakingKeeperMock{}
ax := mock.AxelarnetKeeperMock{}

msgServer := keeper.NewMsgServerImpl(k, &snap, &slashing, &staking, &ax)

ctx := sdk.NewContext(fake.NewMultiStore(), abci.Header{}, false, log.TestingLogger())

assert.True(t, k.IsWasmConnectionActivated(ctx))

_, err := msgServer.DeactivateChain(sdk.WrapSDKContext(ctx), &types.DeactivateChainRequest{Chains: []nexus.ChainName{":all:"}})
assert.NoError(t, err)
assert.False(t, k.IsWasmConnectionActivated(ctx))

_, err = msgServer.ActivateChain(sdk.WrapSDKContext(ctx), &types.ActivateChainRequest{Chains: []nexus.ChainName{":wasm:"}})
assert.NoError(t, err)
assert.True(t, k.IsWasmConnectionActivated(ctx))

_, err = msgServer.DeactivateChain(sdk.WrapSDKContext(ctx), &types.DeactivateChainRequest{Chains: []nexus.ChainName{"not_wasm"}})
assert.NoError(t, err)
assert.True(t, k.IsWasmConnectionActivated(ctx))
}
4 changes: 4 additions & 0 deletions x/nexus/keeper/wasm_message_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ func NewMessageRoute(nexus types.Nexus, account types.AccountKeeper, wasm types.
return fmt.Errorf("asset transfer is not supported")
}

if !nexus.IsWasmConnectionActivated(ctx) {
return fmt.Errorf("wasm connection is not activated")
}

gateway := nexus.GetParams(ctx).Gateway
if gateway.Empty() {
return fmt.Errorf("gateway is not set")
Expand Down
48 changes: 48 additions & 0 deletions x/nexus/keeper/wasm_message_route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/axelarnetwork/axelar-core/testutils/fake"
"github.com/axelarnetwork/axelar-core/testutils/rand"
"github.com/axelarnetwork/axelar-core/x/nexus/exported"
"github.com/axelarnetwork/axelar-core/x/nexus/exported/testutils"
"github.com/axelarnetwork/axelar-core/x/nexus/keeper"
"github.com/axelarnetwork/axelar-core/x/nexus/types"
"github.com/axelarnetwork/axelar-core/x/nexus/types/mock"
Expand Down Expand Up @@ -39,6 +40,7 @@ func TestNewMessageRoute(t *testing.T) {
ctx = sdk.NewContext(fake.NewMultiStore(), tmproto.Header{}, false, log.TestingLogger())

nexusK = &mock.NexusMock{}
nexusK.IsWasmConnectionActivatedFunc = func(_ sdk.Context) bool { return true }
accountK = &mock.AccountKeeperMock{}
wasmK = &mock.WasmKeeperMock{}

Expand Down Expand Up @@ -103,6 +105,52 @@ func TestNewMessageRoute(t *testing.T) {
Run(t)
}

func TestMessageRoute_WasmConnectionNotActivated(t *testing.T) {
var (
ctx sdk.Context
route exported.MessageRoute
nexusK *mock.NexusMock
accountK *mock.AccountKeeperMock
wasmK *mock.WasmKeeperMock
)

Given("the message route", func() {
ctx = sdk.NewContext(fake.NewMultiStore(), tmproto.Header{}, false, log.TestingLogger())

nexusK = &mock.NexusMock{}

nexusK.GetParamsFunc = func(ctx sdk.Context) types.Params {
params := types.DefaultParams()
params.Gateway = rand.AccAddr()

return params
}
nexusK.SetMessageExecutedFunc = func(_ sdk.Context, _ string) error { return nil }

accountK = &mock.AccountKeeperMock{}
accountK.GetModuleAddressFunc = func(_ string) sdk.AccAddress { return rand.AccAddr() }
wasmK = &mock.WasmKeeperMock{}
wasmK.ExecuteFunc = func(_ sdk.Context, _, _ sdk.AccAddress, _ []byte, _ sdk.Coins) ([]byte, error) { return nil, nil }

route = keeper.NewMessageRoute(nexusK, accountK, wasmK)
}).
When("the wasm connection is not activated", func() {
nexusK.IsWasmConnectionActivatedFunc = func(_ sdk.Context) bool { return false }
}).
Then("should return error", func(t *testing.T) {
err := route(ctx, exported.RoutingContext{}, exported.GeneralMessage{
ID: "id",
Sender: testutils.RandomCrossChainAddress(),
Recipient: testutils.RandomCrossChainAddress(),
PayloadHash: rand.Bytes(32),
SourceTxID: rand.Bytes(32),
SourceTxIndex: 0,
})
assert.ErrorContains(t, err, "wasm connection is not activated")
}).
Run(t)
}

func TestReq_MarshalUnmarshalJSON(t *testing.T) {
bz := []byte("{\"route_messages_from_nexus\":[{\"source_chain\":\"sourcechain\",\"source_address\":\"0xb860\",\"destination_chain\":\"destinationchain\",\"destination_address\":\"0xD419\",\"payload_hash\":[187,155,85,102,194,244,135,104,99,51,62,72,31,70,152,53,1,84,37,159,254,98,38,226,131,177,108,225,138,100,188,241],\"source_tx_id\":[47,228],\"source_tx_index\":100},{\"source_chain\":\"sourcechain\",\"source_address\":\"0xc860\",\"destination_chain\":\"destinationchain\",\"destination_address\":\"0xA419\",\"payload_hash\":[203,155,85,102,194,244,135,104,83,51,62,72,31,70,152,53,1,84,37,159,254,98,38,226,131,177,108,225,138,100,188,241],\"source_tx_id\":[35,244],\"source_tx_index\":0}]}")

Expand Down
5 changes: 4 additions & 1 deletion x/nexus/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
snapshot "github.com/axelarnetwork/axelar-core/x/snapshot/exported"
)

//go:generate moq -out ./mock/expected_keepers.go -pkg mock . Nexus Snapshotter AxelarnetKeeper RewardKeeper SlashingKeeper WasmKeeper AccountKeeper
//go:generate moq -out ./mock/expected_keepers.go -pkg mock . Nexus Snapshotter AxelarnetKeeper RewardKeeper SlashingKeeper WasmKeeper AccountKeeper StakingKeeper

// Nexus provides functionality to manage cross-chain transfers
type Nexus interface {
Expand All @@ -26,6 +26,9 @@ type Nexus interface {
SetParams(ctx sdk.Context, p Params)
GetParams(ctx sdk.Context) Params

ActivateWasmConnection(ctx sdk.Context)
DeactivateWasmConnection(ctx sdk.Context)
IsWasmConnectionActivated(ctx sdk.Context) bool
IsChainActivated(ctx sdk.Context, chain exported.Chain) bool
ActivateChain(ctx sdk.Context, chain exported.Chain)
GetChains(ctx sdk.Context) []exported.Chain
Expand Down
Loading

0 comments on commit 25482b4

Please sign in to comment.