From e68dcbba945d0358b2be0e7acca54d5ebc8baa17 Mon Sep 17 00:00:00 2001 From: lumtis Date: Fri, 5 Apr 2024 15:59:42 +0200 Subject: [PATCH] refactor add tracker messages --- docs/spec/crosschain/messages.md | 2 - .../keeper/msg_server_add_to_intx_tracker.go | 74 ++++++----- .../keeper/msg_server_add_to_outtx_tracker.go | 117 ++++++++++-------- x/crosschain/types/errors.go | 1 + x/observer/keeper/grpc_query_tss.go | 2 +- 5 files changed, 110 insertions(+), 86 deletions(-) diff --git a/docs/spec/crosschain/messages.md b/docs/spec/crosschain/messages.md index f802ad4356..d037521606 100644 --- a/docs/spec/crosschain/messages.md +++ b/docs/spec/crosschain/messages.md @@ -6,8 +6,6 @@ AddToOutTxTracker adds a new record to the outbound transaction tracker. only the admin policy account and the observer validators are authorized to broadcast this message without proof. If no pending cctx is found, the tracker is removed, if there is an existed tracker with the nonce & chainID. -Authorized: admin policy group 1, observer. - ```proto message MsgAddToOutTxTracker { string creator = 1; diff --git a/x/crosschain/keeper/msg_server_add_to_intx_tracker.go b/x/crosschain/keeper/msg_server_add_to_intx_tracker.go index f08df0f092..766cbce7d7 100644 --- a/x/crosschain/keeper/msg_server_add_to_intx_tracker.go +++ b/x/crosschain/keeper/msg_server_add_to_intx_tracker.go @@ -19,48 +19,58 @@ func (k msgServer) AddToInTxTracker(goCtx context.Context, msg *types.MsgAddToIn return nil, observertypes.ErrSupportedChains } - isAdmin := k.GetAuthorityKeeper().IsAuthorized(ctx, msg.Creator, authoritytypes.PolicyType_groupEmergency) - isObserver := k.zetaObserverKeeper.IsNonTombstonedObserver(ctx, msg.Creator) + // emergency or observer group can submit tracker without proof + isEmergencyGroup := k.GetAuthorityKeeper().IsAuthorized(ctx, msg.Creator, authoritytypes.PolicyType_groupEmergency) + isObserver := k.GetObserverKeeper().IsNonTombstonedObserver(ctx, msg.Creator) - isProven := false - if !(isAdmin || isObserver) && msg.Proof != nil { - txBytes, err := k.lightclientKeeper.VerifyProof(ctx, msg.Proof, msg.ChainId, msg.BlockHash, msg.TxIndex) - if err != nil { - return nil, types.ErrProofVerificationFail.Wrapf(err.Error()) + if !(isEmergencyGroup || isObserver) { + // if not directly authorized, check the proof, if not provided, return unauthorized + if msg.Proof == nil { + return nil, errorsmod.Wrap(authoritytypes.ErrUnauthorized, fmt.Sprintf("Creator %s", msg.Creator)) } - // get chain params and tss addresses to verify the inTx body - chainParams, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, msg.ChainId) - if !found || chainParams == nil { - return nil, types.ErrUnsupportedChain.Wrapf("chain params not found for chain %d", msg.ChainId) - } - tss, err := k.zetaObserverKeeper.GetTssAddress(ctx, &observertypes.QueryGetTssAddressRequest{ - BitcoinChainId: msg.ChainId, - }) - if err != nil || tss == nil { - reason := "tss response is nil" - if err != nil { - reason = err.Error() - } - return nil, observertypes.ErrTssNotFound.Wrapf("tss address not found %s", reason) + // verify the proof and tx body + if err := verifyProofAndInTxBody(ctx, k, msg); err != nil { + return nil, err } - - if err := types.VerifyInTxBody(*msg, txBytes, *chainParams, *tss); err != nil { - return nil, types.ErrTxBodyVerificationFail.Wrapf(err.Error()) - } - - isProven = true - } - - // Sender needs to be either the admin policy account or an observer - if !(isAdmin || isObserver || isProven) { - return nil, errorsmod.Wrap(authoritytypes.ErrUnauthorized, fmt.Sprintf("Creator %s", msg.Creator)) } + // add the inTx tracker k.SetInTxTracker(ctx, types.InTxTracker{ ChainId: msg.ChainId, TxHash: msg.TxHash, CoinType: msg.CoinType, }) + return &types.MsgAddToInTxTrackerResponse{}, nil } + +// verifyProofAndInTxBody verifies the proof and inbound tx body +func verifyProofAndInTxBody(ctx sdk.Context, k msgServer, msg *types.MsgAddToInTxTracker) error { + txBytes, err := k.GetLightclientKeeper().VerifyProof(ctx, msg.Proof, msg.ChainId, msg.BlockHash, msg.TxIndex) + if err != nil { + return types.ErrProofVerificationFail.Wrapf(err.Error()) + } + + // get chain params and tss addresses to verify the inTx body + chainParams, found := k.GetObserverKeeper().GetChainParamsByChainID(ctx, msg.ChainId) + if !found || chainParams == nil { + return types.ErrUnsupportedChain.Wrapf("chain params not found for chain %d", msg.ChainId) + } + tss, err := k.GetObserverKeeper().GetTssAddress(ctx, &observertypes.QueryGetTssAddressRequest{ + BitcoinChainId: msg.ChainId, + }) + if err != nil || tss == nil { + reason := "tss response is nil" + if err != nil { + reason = err.Error() + } + return observertypes.ErrTssNotFound.Wrapf("tss address not found %s", reason) + } + + if err := types.VerifyInTxBody(*msg, txBytes, *chainParams, *tss); err != nil { + return types.ErrTxBodyVerificationFail.Wrapf(err.Error()) + } + + return nil +} diff --git a/x/crosschain/keeper/msg_server_add_to_outtx_tracker.go b/x/crosschain/keeper/msg_server_add_to_outtx_tracker.go index 318da84685..4e385bc6c7 100644 --- a/x/crosschain/keeper/msg_server_add_to_outtx_tracker.go +++ b/x/crosschain/keeper/msg_server_add_to_outtx_tracker.go @@ -13,13 +13,16 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +// MaxOutTxTrackerHashes is the maximum number of hashes that can be stored in the outbound transaction tracker +const MaxOutTxTrackerHashes = 2 + // AddToOutTxTracker adds a new record to the outbound transaction tracker. // only the admin policy account and the observer validators are authorized to broadcast this message without proof. // If no pending cctx is found, the tracker is removed, if there is an existed tracker with the nonce & chainID. -// -// Authorized: admin policy group 1, observer. func (k msgServer) AddToOutTxTracker(goCtx context.Context, msg *types.MsgAddToOutTxTracker) (*types.MsgAddToOutTxTrackerResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + + // check the chain is supported chain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, msg.ChainId) if chain == nil { return nil, observertypes.ErrSupportedChains @@ -36,54 +39,33 @@ func (k msgServer) AddToOutTxTracker(goCtx context.Context, msg *types.MsgAddToO if cctx == nil || cctx.CrossChainTx == nil { return nil, cosmoserrors.Wrapf(types.ErrCannotFindCctx, "no corresponding cctx found for chain %d, nonce %d", msg.ChainId, msg.Nonce) } + // tracker submission is only allowed when the cctx is pending if !IsPending(*cctx.CrossChainTx) { - // garbage tracker (for any reason) is harmful to outTx observation and should be removed + // garbage tracker (for any reason) is harmful to outTx observation and should be removed if it exists + // it if does not exist, RemoveOutTxTracker is a no-op k.RemoveOutTxTracker(ctx, msg.ChainId, msg.Nonce) return &types.MsgAddToOutTxTrackerResponse{IsRemoved: true}, nil } - if msg.Proof == nil { // without proof, only certain accounts can send this message - isAdmin := k.GetAuthorityKeeper().IsAuthorized(ctx, msg.Creator, authoritytypes.PolicyType_groupEmergency) - isObserver := k.zetaObserverKeeper.IsNonTombstonedObserver(ctx, msg.Creator) - - // Sender needs to be either the admin policy account or an observer - if !(isAdmin || isObserver) { - return nil, cosmoserrors.Wrap(authoritytypes.ErrUnauthorized, fmt.Sprintf("Creator %s", msg.Creator)) - } - } - + isEmergencyGroup := k.GetAuthorityKeeper().IsAuthorized(ctx, msg.Creator, authoritytypes.PolicyType_groupEmergency) + isObserver := k.zetaObserverKeeper.IsNonTombstonedObserver(ctx, msg.Creator) isProven := false - if msg.Proof != nil { // verify proof when it is provided - txBytes, err := k.lightclientKeeper.VerifyProof(ctx, msg.Proof, msg.ChainId, msg.BlockHash, msg.TxIndex) - if err != nil { - return nil, types.ErrProofVerificationFail.Wrapf(err.Error()) - } - // get tss address - var bitcoinChainID int64 - if chains.IsBitcoinChain(msg.ChainId) { - bitcoinChainID = msg.ChainId + if !(isEmergencyGroup || isObserver) { + if msg.Proof == nil { + return nil, cosmoserrors.Wrap(authoritytypes.ErrUnauthorized, fmt.Sprintf("Creator %s", msg.Creator)) } - - tss, err := k.zetaObserverKeeper.GetTssAddress(ctx, &observertypes.QueryGetTssAddressRequest{ - BitcoinChainId: bitcoinChainID, - }) - if err != nil || tss == nil { - reason := "tss response is nil" - if err != nil { - reason = err.Error() - } - return nil, observertypes.ErrTssNotFound.Wrapf("tss address not found %s", reason) + // verify proof when it is provided + if err := verifyProofAndOutTxBody(ctx, k, msg); err != nil { + return nil, err } - err = types.VerifyOutTxBody(*msg, txBytes, *tss) - if err != nil { - return nil, types.ErrTxBodyVerificationFail.Wrapf(err.Error()) - } isProven = true } + // fetch the tracker + // if the tracker does not exist, initialize a new one tracker, found := k.GetOutTxTracker(ctx, msg.ChainId, msg.Nonce) hash := types.TxHashList{ TxHash: msg.TxHash, @@ -96,32 +78,65 @@ func (k msgServer) AddToOutTxTracker(goCtx context.Context, msg *types.MsgAddToO Nonce: msg.Nonce, HashList: []*types.TxHashList{&hash}, }) - ctx.Logger().Info(fmt.Sprintf("Add tracker %s: , Block Height : %d ", getOutTrackerIndex(chain.ChainId, msg.Nonce), ctx.BlockHeight())) return &types.MsgAddToOutTxTrackerResponse{}, nil } - var isDup = false + // check if max hashes are reached + if len(tracker.HashList) >= MaxOutTxTrackerHashes { + return nil, types.ErrMaxTxOutTrackerHashesReached.Wrapf( + "max hashes reached for chain %d, nonce %d, hash number: %d", + msg.ChainId, + msg.Nonce, + len(tracker.HashList), + ) + } + + // check if the hash is already in the tracker for _, hash := range tracker.HashList { if strings.EqualFold(hash.TxHash, msg.TxHash) { - isDup = true + // if the hash is already in the tracker but we have a proof, mark it as proven and only keep this one in the list if isProven { hash.Proved = true + tracker.HashList = []*types.TxHashList{hash} k.SetOutTxTracker(ctx, tracker) - k.Logger(ctx).Info("Proof'd outbound transaction") - return &types.MsgAddToOutTxTrackerResponse{}, nil } - break + return &types.MsgAddToOutTxTrackerResponse{}, nil } } - if !isDup { - if isProven { - hash.Proved = true - tracker.HashList = append([]*types.TxHashList{&hash}, tracker.HashList...) - k.Logger(ctx).Info("Proof'd outbound transaction") - } else if len(tracker.HashList) < 2 { - tracker.HashList = append(tracker.HashList, &hash) + + // add the tracker to the list + tracker.HashList = append(tracker.HashList, &hash) + k.SetOutTxTracker(ctx, tracker) + return &types.MsgAddToOutTxTrackerResponse{}, nil +} + +// verifyProofAndOutTxBody verifies the proof and outbound tx body +func verifyProofAndOutTxBody(ctx sdk.Context, k msgServer, msg *types.MsgAddToOutTxTracker) error { + txBytes, err := k.lightclientKeeper.VerifyProof(ctx, msg.Proof, msg.ChainId, msg.BlockHash, msg.TxIndex) + if err != nil { + return types.ErrProofVerificationFail.Wrapf(err.Error()) + } + + // get tss address + var bitcoinChainID int64 + if chains.IsBitcoinChain(msg.ChainId) { + bitcoinChainID = msg.ChainId + } + + tss, err := k.zetaObserverKeeper.GetTssAddress(ctx, &observertypes.QueryGetTssAddressRequest{ + BitcoinChainId: bitcoinChainID, + }) + if err != nil || tss == nil { + reason := "tss response is nil" + if err != nil { + reason = err.Error() } - k.SetOutTxTracker(ctx, tracker) + return observertypes.ErrTssNotFound.Wrapf("tss address not found %s", reason) } - return &types.MsgAddToOutTxTrackerResponse{}, nil + + if err := types.VerifyOutTxBody(*msg, txBytes, *tss); err != nil { + return types.ErrTxBodyVerificationFail.Wrapf(err.Error()) + } + + return nil } diff --git a/x/crosschain/types/errors.go b/x/crosschain/types/errors.go index 62720aaf66..0578e68fa6 100644 --- a/x/crosschain/types/errors.go +++ b/x/crosschain/types/errors.go @@ -41,4 +41,5 @@ var ( ErrUnableProcessRefund = errorsmod.Register(ModuleName, 1148, "unable to process refund") ErrUnableToFindZetaAccounting = errorsmod.Register(ModuleName, 1149, "unable to find zeta accounting") ErrInsufficientZetaAmount = errorsmod.Register(ModuleName, 1150, "insufficient zeta amount") + ErrMaxTxOutTrackerHashesReached = errorsmod.Register(ModuleName, 1151, "max tx out tracker hashes reached") ) diff --git a/x/observer/keeper/grpc_query_tss.go b/x/observer/keeper/grpc_query_tss.go index 48485f0112..0b6f4a7614 100644 --- a/x/observer/keeper/grpc_query_tss.go +++ b/x/observer/keeper/grpc_query_tss.go @@ -12,7 +12,7 @@ import ( "google.golang.org/grpc/status" ) -// Tss returns the tss address for the current tss only +// TSS returns the tss address for the current tss only func (k Keeper) TSS(c context.Context, req *types.QueryGetTSSRequest) (*types.QueryGetTSSResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request")