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: query refund record #268

Merged
merged 1 commit into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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
Loading