Skip to content
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

feat: allow (de)activation of the wasm connection with an access control command #2166

Merged
merged 7 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
"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) 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)

Check warning on line 135 in x/nexus/keeper/msg_server.go

View check run for this annotation

Codecov / codecov/patch

x/nexus/keeper/msg_server.go#L135

Added line #L135 was not covered by tests
}
if doWasm {
s.ActivateWasmConnection(ctx)
cgorenflo marked this conversation as resolved.
Show resolved Hide resolved
}

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)

Check warning on line 151 in x/nexus/keeper/msg_server.go

View check run for this annotation

Codecov / codecov/patch

x/nexus/keeper/msg_server.go#L151

Added line #L151 was not covered by tests
}
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)

Check warning on line 172 in x/nexus/keeper/msg_server.go

View check run for this annotation

Codecov / codecov/patch

x/nexus/keeper/msg_server.go#L172

Added line #L172 was not covered by tests
}
}
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
52 changes: 52 additions & 0 deletions x/nexus/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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 TestMsgServer_De_ActivateWasm(t *testing.T) {
cgorenflo marked this conversation as resolved.
Show resolved Hide resolved
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"}})
cgorenflo marked this conversation as resolved.
Show resolved Hide resolved
assert.NoError(t, err)
assert.True(t, k.IsWasmConnectionActivated(ctx))

cgorenflo marked this conversation as resolved.
Show resolved Hide resolved
}
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
Loading