Skip to content

Commit

Permalink
feat: slashing unsign bridge call refund
Browse files Browse the repository at this point in the history
  • Loading branch information
fx0x55 authored and github-actions[bot] committed Mar 21, 2024
1 parent 62881ec commit 9a9f3f3
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 3 deletions.
43 changes: 42 additions & 1 deletion x/crosschain/keeper/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ func (k Keeper) slashing(ctx sdk.Context, signedWindow uint64) {
oracles := k.GetAllOracles(ctx, true)
oracleSetHasSlash := k.oracleSetSlashing(ctx, oracles, signedWindow)
batchHasSlash := k.batchSlashing(ctx, oracles, signedWindow)
if oracleSetHasSlash || batchHasSlash {
refundHasSlash := k.refundSlashing(ctx, signedWindow)
if oracleSetHasSlash || batchHasSlash || refundHasSlash {
k.CommonSetOracleTotalPower(ctx)
}
}
Expand Down Expand Up @@ -121,6 +122,46 @@ func (k Keeper) batchSlashing(ctx sdk.Context, oracles types.Oracles, signedWind
return hasSlash
}

func (k Keeper) refundSlashing(ctx sdk.Context, signedWindow uint64) (hasSlash bool) {
maxHeight := uint64(ctx.BlockHeight()) - signedWindow

Check failure

Code scanning / gosec

Potential integer overflow by integer type conversion Error

Potential integer overflow by integer type conversion
unSlashRefunds := k.GetUnSlashedRefundRecords(ctx, maxHeight)

snapshotOracleMap := make(map[uint64]*types.SnapshotOracle)
for _, record := range unSlashRefunds {
confirmOracleMap := make(map[string]bool)
k.IterRefundConfirmByNonce(ctx, record.EventNonce, func(confirm *types.MsgConfirmRefund) bool {
confirmOracleMap[confirm.ExternalAddress] = true
return false
})

snapshotOracle, found := snapshotOracleMap[record.OracleSetNonce]
if !found {
snapshotOracle, found = k.GetSnapshotOracle(ctx, record.OracleSetNonce)
if !found {
k.Logger(ctx).Error("refund slashing", "oracle set not found", record.OracleSetNonce)
continue
}
snapshotOracleMap[record.OracleSetNonce] = snapshotOracle
}

for _, members := range snapshotOracle.GetMembers() {
if _, ok := confirmOracleMap[members.ExternalAddress]; !ok {
oracle, found := k.GetOracleByExternalAddress(ctx, members.ExternalAddress)
if !found {
k.Logger(ctx).Error("refund slashing", "oracle not found", members.ExternalAddress)
continue
}
k.Logger(ctx).Info("slash oracle by refund", "externalAddress", members.ExternalAddress,
"oracleAddress", oracle.String(), "recordNonce", record.EventNonce, "blockHeight", ctx.BlockHeight())
k.SlashOracle(ctx, oracle.String())
hasSlash = true
}
}
k.SetLastSlashedRefundNonce(ctx, record.EventNonce)
}
return hasSlash
}

// cleanupTimedOutBatches deletes batches that have passed their expiration on Ethereum
// keep in mind several things when modifying this function
// A) unlike nonces timeouts are not monotonically increasing, meaning batch 5 can have a later timeout than batch 6
Expand Down
28 changes: 28 additions & 0 deletions x/crosschain/keeper/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
tmrand "github.com/tendermint/tendermint/libs/rand"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

Expand Down Expand Up @@ -674,3 +675,30 @@ func (suite *KeeperTestSuite) TestCleanUpRefundTimeout() {
_, err = suite.QueryClient().RefundRecordByNonce(sdk.WrapSDKContext(suite.ctx), &types.QueryRefundRecordByNonceRequest{ChainName: suite.chainName, EventNonce: 2})
suite.ErrorIs(err, status.Error(codes.NotFound, "refund record"), suite.chainName)
}

func (suite *KeeperTestSuite) TestRefundSlashing() {
suite.bondedOracle()
suite.Commit()

eventNonce := tmrand.Uint64()

err := suite.Keeper().AddRefundRecord(suite.ctx, helpers.GenerateZeroAddressByModule(suite.chainName), eventNonce, []types.ERC20Token{})
suite.NoError(err)

params := suite.Keeper().GetParams(suite.ctx)
signedWindow := uint64(tmrand.Int63n(10) + 2)
params.SignedWindow = signedWindow
suite.NoError(suite.Keeper().SetParams(suite.ctx, &params))

slashedRefundNonce := suite.Keeper().GetLastSlashedRefundNonce(suite.ctx)
suite.EqualValues(0, slashedRefundNonce)
suite.Commit(int64(signedWindow))

slashedRefundNonce = suite.Keeper().GetLastSlashedRefundNonce(suite.ctx)
suite.EqualValues(eventNonce, slashedRefundNonce)

oracle, found := suite.Keeper().GetOracle(suite.ctx, suite.oracleAddrs[0])
suite.True(found)
suite.False(oracle.Online)
suite.EqualValues(1, oracle.SlashTimes)
}
41 changes: 41 additions & 0 deletions x/crosschain/keeper/bridge_call_refund.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package keeper

import (
"math"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

Expand Down Expand Up @@ -188,3 +190,42 @@ func (k Keeper) DeleteRefundConfirm(ctx sdk.Context, nonce uint64) {
store.Delete(iterator.Key())
}
}

func (k Keeper) SetLastSlashedRefundNonce(ctx sdk.Context, nonce uint64) {
store := ctx.KVStore(k.storeKey)
store.Set(types.LastSlashedRefundNonce, sdk.Uint64ToBigEndian(nonce))
}

func (k Keeper) GetLastSlashedRefundNonce(ctx sdk.Context) uint64 {
store := ctx.KVStore(k.storeKey)
return sdk.BigEndianToUint64(store.Get(types.LastSlashedRefundNonce))
}

func (k Keeper) GetUnSlashedRefundRecords(ctx sdk.Context, height uint64) []types.RefundRecord {
nonce := k.GetLastSlashedRefundNonce(ctx)
var refunds []types.RefundRecord
k.IterateRefundRecordByNonce(ctx, nonce, func(record *types.RefundRecord) bool {
if record.Block <= height {
refunds = append(refunds, *record)
return false
}
return true
})
return refunds
}

func (k Keeper) IterateRefundRecordByNonce(ctx sdk.Context, startNonce uint64, cb func(record *types.RefundRecord) bool) {
store := ctx.KVStore(k.storeKey)
startKey := append(types.BridgeCallRefundEventNonceKey, sdk.Uint64ToBigEndian(startNonce)...)
endKey := append(types.BridgeCallRefundEventNonceKey, sdk.Uint64ToBigEndian(math.MaxUint64)...)
iter := store.Iterator(startKey, endKey)
defer iter.Close()

for ; iter.Valid(); iter.Next() {
value := new(types.RefundRecord)
k.cdc.MustUnmarshal(iter.Value(), value)
if cb(value) {
break
}
}
}
46 changes: 46 additions & 0 deletions x/crosschain/keeper/bridge_call_refund_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package keeper_test

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/functionx/fx-core/v7/testutil/helpers"
Expand Down Expand Up @@ -31,3 +33,47 @@ func (suite *KeeperTestSuite) TestKeeper_AddRefundRecord() {
suite.Equal(refundRecord.Receiver, receiver)
suite.Equal(refundRecord.Tokens, tokens)
}

func (suite *KeeperTestSuite) TestKeeper_IterateRefundRecordByNonce() {
testCases := []struct {
name string
eventNonces []uint64
startNonce uint64
expectEventNonce []uint64
}{
{
name: "only 1 - start 0",
eventNonces: []uint64{1},
startNonce: uint64(0),
expectEventNonce: []uint64{1},
},
{
name: "only 1 - start 1",
eventNonces: []uint64{1},
startNonce: uint64(1),
expectEventNonce: []uint64{1},
},
{
name: "out-of-order",
eventNonces: []uint64{6, 2, 5},
startNonce: uint64(1),
expectEventNonce: []uint64{2, 5, 6},
},
}

for _, testCase := range testCases {
suite.T().Run(testCase.name, func(t *testing.T) {
suite.SetupTest()
for _, nonce := range testCase.eventNonces {
suite.Keeper().SetRefundRecord(suite.ctx, &types.RefundRecord{EventNonce: nonce})
}
actualEventNonces := make([]uint64, 0, len(testCase.expectEventNonce))
suite.Keeper().IterateRefundRecordByNonce(suite.ctx, testCase.startNonce, func(record *types.RefundRecord) bool {
actualEventNonces = append(actualEventNonces, record.EventNonce)
return false
})

suite.EqualValues(testCase.expectEventNonce, actualEventNonces)
})
}
}
3 changes: 1 addition & 2 deletions x/crosschain/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1305,7 +1305,7 @@ func (suite *KeeperTestSuite) TestConfirmRefund() {
}
}

func (suite *KeeperTestSuite) bondedOracle() uint64 {
func (suite *KeeperTestSuite) bondedOracle() {
_, err := suite.MsgServer().BondedOracle(sdk.WrapSDKContext(suite.ctx), &types.MsgBondedOracle{
OracleAddress: suite.oracleAddrs[0].String(),
BridgerAddress: suite.bridgerAddrs[0].String(),
Expand All @@ -1318,7 +1318,6 @@ func (suite *KeeperTestSuite) bondedOracle() uint64 {

oracleLastEventNonce := suite.Keeper().GetLastEventNonceByOracle(suite.ctx, suite.oracleAddrs[0])
require.EqualValues(suite.T(), 0, oracleLastEventNonce)
return oracleLastEventNonce
}

func (suite *KeeperTestSuite) addBridgeToken(tokenContract string, md banktypes.Metadata) {
Expand Down
3 changes: 3 additions & 0 deletions x/crosschain/types/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ var (
SnapshotOracleKey = []byte{0x44}

BridgeCallRefundConfirmKey = []byte{0x45}

// LastSlashedRefundNonce indexes the latest slashed refund nonce
LastSlashedRefundNonce = []byte{0x46}
)

// GetOracleKey returns the following key format
Expand Down

0 comments on commit 9a9f3f3

Please sign in to comment.