Skip to content

Commit

Permalink
feat: query refund record (#268)
Browse files Browse the repository at this point in the history
Co-authored-by: nulnut <[email protected]>
  • Loading branch information
zakir-code and nulnut authored Mar 12, 2024
1 parent b35cf69 commit ef22111
Show file tree
Hide file tree
Showing 11 changed files with 3,743 additions and 944 deletions.
2 changes: 1 addition & 1 deletion docs/swagger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func TestSwaggerConfig(t *testing.T) {
assert.Equal(t, 2, handler.Value().Len())
}
if handler.Key().String() == "GET" {
assert.Equal(t, 205, handler.Value().Len())
assert.Equal(t, 209, handler.Value().Len())
}
}
assert.Equal(t, 32, len(route))
Expand Down
47 changes: 47 additions & 0 deletions proto/fx/crosschain/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ service Query {
rpc GetPendingSendToExternal(QueryPendingSendToExternalRequest) returns (QueryPendingSendToExternalResponse) {
option (google.api.http).get = "/fx/crosschain/v1/pending_send_to_external";
}
rpc RefundRecordByNonce(QueryRefundRecordByNonceRequest) returns (QueryRefundRecordByNonceResponse) {
option (google.api.http).get = "/fx/crosschain/v1/refund_record_by_nonce";
}
rpc RefundRecordByReceiver(QueryRefundRecordByReceiverRequest) returns (QueryRefundRecordByReceiverResponse) {
option (google.api.http).get = "/fx/crosschain/v1/refund_record_by_receiver";
}
rpc RefundConfirmByNonce(QueryRefundConfirmByNonceRequest) returns (QueryRefundConfirmByNonceResponse) {
option (google.api.http).get = "/fx/crosschain/v1/refund_confirm_by_nonce";
}
rpc LastPendingRefundRecordByAddr(QueryLastPendingRefundRecordByAddrRequest) returns (QueryLastPendingRefundRecordByAddrResponse) {
option (google.api.http).get = "/fx/crosschain/v1/last_pending_refund_record_by_addr";
}

// Validators queries all oracle that match the given status.
rpc Oracles(QueryOraclesRequest) returns (QueryOraclesResponse) {
option (google.api.http).get = "/fx/crosschain/v1/oracles";
Expand Down Expand Up @@ -300,3 +313,37 @@ message QueryBridgeChainListRequest {}
message QueryBridgeChainListResponse {
repeated string chain_names = 1;
}

message QueryRefundRecordByNonceRequest {
string chain_name = 1;
uint64 event_nonce = 2;
}
message QueryRefundRecordByNonceResponse {
RefundRecord record = 1;
}

message QueryRefundRecordByReceiverRequest {
string chain_name = 1;
string receiver_address = 2;
}
message QueryRefundRecordByReceiverResponse {
repeated RefundRecord records = 1;
}

message QueryRefundConfirmByNonceRequest {
string chain_name = 1;
uint64 event_nonce = 2;
}

message QueryRefundConfirmByNonceResponse {
repeated MsgConfirmRefund confirms = 1;
bool enough_power = 2;
}

message QueryLastPendingRefundRecordByAddrRequest {
string chain_name = 1;
string external_address = 2;
}
message QueryLastPendingRefundRecordByAddrResponse {
repeated RefundRecord records = 1;
}
112 changes: 112 additions & 0 deletions x/crosschain/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ func GetQuerySubCmds(chainName string) []*cobra.Command {

// help cmd.
CmdCovertBridgeToken(chainName),

// refund token
CmdRefundRecord(chainName),
CmdRefundRecordByAddr(chainName),
CmdRefundConfirm(chainName),
CmdLastPendingRefundRecord(chainName),
}

for _, command := range cmds {
Expand Down Expand Up @@ -821,3 +827,109 @@ func CmdGetBridgeCoinByDenom(chainName string) *cobra.Command {
}
return cmd
}

func CmdRefundRecord(chainName string) *cobra.Command {
cmd := &cobra.Command{
Use: "refund-record [nonce]",
Short: "Query refund record by event nonce",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)

nonce, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return err
}

res, err := queryClient.RefundRecordByNonce(cmd.Context(), &types.QueryRefundRecordByNonceRequest{
ChainName: chainName,
EventNonce: nonce,
})
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}
return cmd
}

func CmdRefundRecordByAddr(chainName string) *cobra.Command {
cmd := &cobra.Command{
Use: "refund-record-by-receiver [address]",
Short: "Query refund records by receiver",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)

receiver, err := getContractAddr(args[0])
if err != nil {
return err
}
res, err := queryClient.RefundRecordByReceiver(cmd.Context(), &types.QueryRefundRecordByReceiverRequest{
ChainName: chainName,
ReceiverAddress: receiver,
})
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}
return cmd
}

func CmdRefundConfirm(chainName string) *cobra.Command {
cmd := &cobra.Command{
Use: "refund-confirm [nonce]",
Short: "Query refund confirm by event nonce",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)

nonce, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return err
}

res, err := queryClient.RefundConfirmByNonce(cmd.Context(), &types.QueryRefundConfirmByNonceRequest{
ChainName: chainName,
EventNonce: nonce,
})
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}
return cmd
}

func CmdLastPendingRefundRecord(chainName string) *cobra.Command {
cmd := &cobra.Command{
Use: "last-pending-refund-record [external-address]",
Short: "Query last pending refund record for bridge address",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)

externalAddress, err := getContractAddr(args[0])
if err != nil {
return err
}
res, err := queryClient.LastPendingRefundRecordByAddr(cmd.Context(), &types.QueryLastPendingRefundRecordByAddrRequest{
ChainName: chainName,
ExternalAddress: externalAddress,
})
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}
return cmd
}
49 changes: 49 additions & 0 deletions x/crosschain/keeper/bridge_call_refund.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,35 @@ func (k Keeper) GetRefundRecord(ctx sdk.Context, eventNonce uint64) (*types.Refu
return refundRecord, true
}

func (k Keeper) IterRefundRecord(ctx sdk.Context, cb func(record *types.RefundRecord) bool) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.BridgeCallRefundEventNonceKey)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
record := new(types.RefundRecord)
k.cdc.MustUnmarshal(iterator.Value(), record)
if cb(record) {
break
}
}
}

func (k Keeper) IterRefundRecordByAddr(ctx sdk.Context, addr string, cb func(record *types.RefundRecord) bool) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.GetBridgeCallRefundAddressKey(addr))
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
nonce := types.ParseBridgeCallRefundNonce(iterator.Key(), addr)
record, found := k.GetRefundRecord(ctx, nonce)
if !found {
continue
}
if cb(record) {
break
}
}
}

func (k Keeper) SetSnapshotOracle(ctx sdk.Context, snapshotOracleKey *types.SnapshotOracle) {
store := ctx.KVStore(k.storeKey)
store.Set(types.GetSnapshotOracleKey(snapshotOracleKey.OracleSetNonce), k.cdc.MustMarshal(snapshotOracleKey))
Expand All @@ -103,6 +132,11 @@ func (k Keeper) GetSnapshotOracle(ctx sdk.Context, oracleSetNonce uint64) (*type
return snapshotOracle, true
}

func (k Keeper) HasRefundConfirm(ctx sdk.Context, nonce uint64, addr sdk.AccAddress) bool {
store := ctx.KVStore(k.storeKey)
return store.Has(types.GetRefundConfirmKey(nonce, addr))
}

func (k Keeper) DeleteSnapshotOracle(ctx sdk.Context, nonce uint64) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.GetSnapshotOracleKey(nonce))
Expand All @@ -124,6 +158,21 @@ func (k Keeper) SetRefundConfirm(ctx sdk.Context, addr sdk.AccAddress, msg *type
store.Set(types.GetRefundConfirmKey(msg.Nonce, addr), k.cdc.MustMarshal(msg))
}

func (k Keeper) IterRefundConfirmByNonce(ctx sdk.Context, nonce uint64, cb func(msg *types.MsgConfirmRefund) bool) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, types.GetRefundConfirmNonceKey(nonce))
defer iter.Close()

for ; iter.Valid(); iter.Next() {
confirm := new(types.MsgConfirmRefund)
k.cdc.MustUnmarshal(iter.Value(), confirm)
// cb returns true to stop early
if cb(confirm) {
break
}
}
}

func (k Keeper) DeleteRefundConfirm(ctx sdk.Context, nonce uint64) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.GetRefundConfirmKeyByNonce(nonce))
Expand Down
77 changes: 77 additions & 0 deletions x/crosschain/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"sort"

sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -433,3 +434,79 @@ func (k Keeper) BridgeChainList(_ context.Context, _ *types.QueryBridgeChainList
optimismtypes.ModuleName,
}}, nil
}

func (k Keeper) RefundRecordByNonce(c context.Context, req *types.QueryRefundRecordByNonceRequest) (*types.QueryRefundRecordByNonceResponse, error) {
if req.GetEventNonce() == 0 {
return nil, status.Error(codes.InvalidArgument, "event nonce")
}
record, found := k.GetRefundRecord(sdk.UnwrapSDKContext(c), req.GetEventNonce())
if !found {
return nil, status.Error(codes.NotFound, "refund record")
}
return &types.QueryRefundRecordByNonceResponse{Record: record}, nil
}

func (k Keeper) RefundRecordByReceiver(c context.Context, req *types.QueryRefundRecordByReceiverRequest) (*types.QueryRefundRecordByReceiverResponse, error) {
if len(req.GetReceiverAddress()) == 0 {
return nil, status.Error(codes.InvalidArgument, "receiver")
}

refundRecords := make([]*types.RefundRecord, 0)
k.IterRefundRecordByAddr(sdk.UnwrapSDKContext(c), req.GetReceiverAddress(), func(record *types.RefundRecord) bool {
refundRecords = append(refundRecords, record)
return false
})
return &types.QueryRefundRecordByReceiverResponse{Records: refundRecords}, nil
}

func (k Keeper) RefundConfirmByNonce(c context.Context, req *types.QueryRefundConfirmByNonceRequest) (*types.QueryRefundConfirmByNonceResponse, error) {
if req.GetEventNonce() == 0 {
return nil, status.Error(codes.InvalidArgument, "event nonce")
}

ctx := sdk.UnwrapSDKContext(c)
currentOracleSet := k.GetCurrentOracleSet(ctx)
confirmPowers := uint64(0)
refundConfirms := make([]*types.MsgConfirmRefund, 0)
k.IterRefundConfirmByNonce(ctx, req.GetEventNonce(), func(msg *types.MsgConfirmRefund) bool {
power, found := currentOracleSet.GetBridgePower(msg.ExternalAddress)
if !found {
return false
}
confirmPowers += power
refundConfirms = append(refundConfirms, msg)
return false
})
totalPower := currentOracleSet.GetTotalPower()
requiredPower := types.AttestationVotesPowerThreshold.Mul(sdkmath.NewIntFromUint64(totalPower)).Quo(sdkmath.NewInt(100))
enoughPower := requiredPower.GTE(sdkmath.NewIntFromUint64(confirmPowers))
return &types.QueryRefundConfirmByNonceResponse{Confirms: refundConfirms, EnoughPower: enoughPower}, nil
}

func (k Keeper) LastPendingRefundRecordByAddr(c context.Context, req *types.QueryLastPendingRefundRecordByAddrRequest) (*types.QueryLastPendingRefundRecordByAddrResponse, error) {
if len(req.GetExternalAddress()) == 0 {
return nil, status.Error(codes.InvalidArgument, "empty external address")
}
ctx := sdk.UnwrapSDKContext(c)
pendingRecords := make([]*types.RefundRecord, 0)

accAddr := types.ExternalAddressToAccAddress(k.moduleName, req.GetExternalAddress())
snapshotOracleCache := make(map[uint64]*types.SnapshotOracle)
k.IterRefundRecord(ctx, func(record *types.RefundRecord) bool {
snapshotOracle, found := snapshotOracleCache[record.OracleSetNonce]
if !found {
snapshotOracle, found = k.GetSnapshotOracle(ctx, record.OracleSetNonce)
if !found {
return false
}
snapshotOracleCache[record.OracleSetNonce] = snapshotOracle
}

if !snapshotOracle.HasExternalAddress(req.GetExternalAddress()) || k.HasRefundConfirm(ctx, record.EventNonce, accAddr) {
return false
}
pendingRecords = append(pendingRecords, record)
return false
})
return &types.QueryLastPendingRefundRecordByAddrResponse{Records: pendingRecords}, nil
}
32 changes: 32 additions & 0 deletions x/crosschain/keeper/grpc_query_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,35 @@ func (k RouterKeeper) BridgeChainList(c context.Context, req *types.QueryBridgeC
return queryServer.BridgeChainList(c, req)
}
}

func (k RouterKeeper) RefundRecordByNonce(c context.Context, req *types.QueryRefundRecordByNonceRequest) (*types.QueryRefundRecordByNonceResponse, error) {
if queryServer, err := k.getQueryServerByChainName(ethtypes.ModuleName); err != nil {
return nil, err
} else {
return queryServer.RefundRecordByNonce(c, req)
}
}

func (k RouterKeeper) RefundRecordByReceiver(c context.Context, req *types.QueryRefundRecordByReceiverRequest) (*types.QueryRefundRecordByReceiverResponse, error) {
if queryServer, err := k.getQueryServerByChainName(ethtypes.ModuleName); err != nil {
return nil, err
} else {
return queryServer.RefundRecordByReceiver(c, req)
}
}

func (k RouterKeeper) RefundConfirmByNonce(c context.Context, req *types.QueryRefundConfirmByNonceRequest) (*types.QueryRefundConfirmByNonceResponse, error) {
if queryServer, err := k.getQueryServerByChainName(ethtypes.ModuleName); err != nil {
return nil, err
} else {
return queryServer.RefundConfirmByNonce(c, req)
}
}

func (k RouterKeeper) LastPendingRefundRecordByAddr(c context.Context, req *types.QueryLastPendingRefundRecordByAddrRequest) (*types.QueryLastPendingRefundRecordByAddrResponse, error) {
if queryServer, err := k.getQueryServerByChainName(ethtypes.ModuleName); err != nil {
return nil, err
} else {
return queryServer.LastPendingRefundRecordByAddr(c, req)
}
}
Loading

0 comments on commit ef22111

Please sign in to comment.