Skip to content

Commit

Permalink
refactor add tracker messages
Browse files Browse the repository at this point in the history
  • Loading branch information
lumtis committed Apr 5, 2024
1 parent 47cad7e commit e68dcbb
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 86 deletions.
2 changes: 0 additions & 2 deletions docs/spec/crosschain/messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
74 changes: 42 additions & 32 deletions x/crosschain/keeper/msg_server_add_to_intx_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
117 changes: 66 additions & 51 deletions x/crosschain/keeper/msg_server_add_to_outtx_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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
}
1 change: 1 addition & 0 deletions x/crosschain/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
2 changes: 1 addition & 1 deletion x/observer/keeper/grpc_query_tss.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit e68dcbb

Please sign in to comment.