diff --git a/cmd/tx.go b/cmd/tx.go index f57c4cb36..c561eee20 100644 --- a/cmd/tx.go +++ b/cmd/tx.go @@ -105,6 +105,8 @@ func createClientsCmd(a *appState) *cobra.Command { return fmt.Errorf("key %s not found on dst chain %s", c[dst].ChainProvider.Key(), c[dst].ChainID()) } + // TODO: make iconStartHeight compulsory + // if iconStartHeight is not given it can create confusion as starting relay at any time could miss number of btp block update_client clientSrc, clientDst, err := c[src].CreateClients(cmd.Context(), c[dst], allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override, customClientTrustingPeriod, a.config.memo(cmd), iconStartHeight) if err != nil { return err @@ -335,6 +337,7 @@ func upgradeClientsCmd(a *appState) *cobra.Command { return cmd } +// TODO: method has side_effect func createConnectionCmd(a *appState) *cobra.Command { cmd := &cobra.Command{ Use: "connection path_name", diff --git a/relayer/chains/archway/archway_chain_processor.go b/relayer/chains/archway/archway_chain_processor.go index fcd88dd40..c07fd9482 100644 --- a/relayer/chains/archway/archway_chain_processor.go +++ b/relayer/chains/archway/archway_chain_processor.go @@ -74,6 +74,7 @@ const ( latestHeightQueryRetryDelay = 1 * time.Second latestHeightQueryRetries = 5 + // TODO: review transfer to providerConfig defaultMinQueryLoopDuration = 1 * time.Second defaultBalanceUpdateWaitDuration = 60 * time.Second inSyncNumBlocksThreshold = 2 @@ -305,6 +306,7 @@ func (ccp *ArchwayChainProcessor) initializeChannelState(ctx context.Context) er } func (ccp *ArchwayChainProcessor) queryCycle(ctx context.Context, persistence *queryCyclePersistence) error { + // TODO : review if redundent remove status, err := ccp.nodeStatusWithRetry(ctx) if err != nil { // don't want to cause ArchwayChainProcessor to quit here, can retry again next cycle. @@ -355,6 +357,8 @@ func (ccp *ArchwayChainProcessor) queryCycle(ctx context.Context, persistence *q chainID := ccp.chainProvider.ChainId() + // TODO review: max block sync + // for i := persistence.latestQueriedBlock + 1; i <= persistence.latestHeight; i++ { var eg errgroup.Group var blockRes *ctypes.ResultBlockResults @@ -451,6 +455,8 @@ func (ccp *ArchwayChainProcessor) queryCycle(ctx context.Context, persistence *q return nil } +// TODO: review add verifier + func (ccp *ArchwayChainProcessor) CollectMetrics(ctx context.Context, persistence *queryCyclePersistence) { ccp.CurrentBlockHeight(ctx, persistence) diff --git a/relayer/chains/icon/cryptoutils/merkle_proof.go b/relayer/chains/icon/cryptoutils/merkle_proof.go index 50909b245..7424dce8b 100644 --- a/relayer/chains/icon/cryptoutils/merkle_proof.go +++ b/relayer/chains/icon/cryptoutils/merkle_proof.go @@ -5,7 +5,6 @@ import ( "math/bits" "github.com/cosmos/relayer/v2/relayer/chains/icon/types" - "github.com/cosmos/relayer/v2/relayer/common" "github.com/icon-project/IBC-Integration/libraries/go/common/icon" ) diff --git a/relayer/chains/icon/event_parser.go b/relayer/chains/icon/event_parser.go index f0ed7f104..05642f887 100644 --- a/relayer/chains/icon/event_parser.go +++ b/relayer/chains/icon/event_parser.go @@ -32,7 +32,9 @@ func (pi *packetInfo) parseAttrs(log *zap.Logger, event types.EventLog) { packetData := event.Indexed[1] var packet icon.Packet if err := proto.Unmarshal(packetData, &packet); err != nil { + log.Error("failed to unmarshal packet") + // TODO: review return if parseAttrs add panic } pi.SourcePort = packet.SourcePort pi.SourceChannel = packet.SourceChannel diff --git a/relayer/chains/icon/icon_chain_processor.go b/relayer/chains/icon/icon_chain_processor.go index 883e9a05c..628150d3d 100644 --- a/relayer/chains/icon/icon_chain_processor.go +++ b/relayer/chains/icon/icon_chain_processor.go @@ -1,6 +1,7 @@ package icon import ( + "bytes" "context" "fmt" "sort" @@ -61,6 +62,14 @@ type IconChainProcessor struct { // metrics to monitor lifetime of processor metrics *processor.PrometheusMetrics + + verifier *Verifier +} + +type Verifier struct { + nextProofContext [][]byte + verifiedHeight int64 + prevNetworkSectionHash []byte } func NewIconChainProcessor(log *zap.Logger, provider *IconProvider, metrics *processor.PrometheusMetrics) *IconChainProcessor { @@ -144,7 +153,7 @@ func (icp *IconChainProcessor) Run(ctx context.Context, initialBlockHistory uint } func (icp *IconChainProcessor) initializeConnectionState(ctx context.Context) error { - // TODO: + // TODO: review ctx, cancel := context.WithTimeout(ctx, queryTimeout) defer cancel() @@ -218,6 +227,7 @@ func (icp *IconChainProcessor) GetLatestHeight() uint64 { return icp.latestBlock.Height } +// TODO: review add verifier func (icp *IconChainProcessor) monitoring(ctx context.Context, persistence *queryCyclePersistence) error { errCh := make(chan error) // error channel @@ -294,11 +304,20 @@ loop: }(ctxMonitorBlock, cancelMonitorBlock) case br := <-btpBlockRespCh: for ; br != nil; processedheight++ { - icp.latestBlockMu.Lock() + // verify BTP Block + err := icp.verifyBlock(ctx, br.Header) + if err != nil { + reconnect() + icp.log.Warn("failed to Verify BTP Block", + zap.Int64("got", br.Height), + zap.Error(err), + ) + break + } + icp.latestBlock = provider.LatestBlock{ Height: uint64(processedheight), } - icp.latestBlockMu.Unlock() ibcMessage := parseIBCMessagesFromEventlog(icp.log, br.EventLogs, uint64(br.Height)) ibcMessageCache := processor.NewIBCMessagesCache() @@ -311,7 +330,7 @@ loop: icp.log.Info("Queried Latest height: ", zap.String("chain id ", icp.chainProvider.ChainId()), zap.Int64("height", br.Height)) - err := icp.handlePathProcessorUpdate(ctx, br.Header, ibcMessageCache, ibcHeaderCache) + err = icp.handlePathProcessorUpdate(ctx, br.Header, ibcMessageCache, ibcHeaderCache) if err != nil { reconnect() icp.log.Warn("Reconnect: error occured during handle block response ", @@ -319,8 +338,14 @@ loop: ) break } + + // TODO: this is temporary adjustment + // if icp.firstTime { + // time.Sleep(4000 * time.Millisecond) + // } else { + // time.Sleep(100 * time.Millisecond) + // } icp.firstTime = false - time.Sleep(100 * time.Millisecond) if br = nil; len(btpBlockRespCh) > 0 { br = <-btpBlockRespCh } @@ -338,11 +363,6 @@ loop: for i := int64(0); bn != nil; i++ { height, err := bn.Height.Value() - // icp.log.Info("for loop when receiving blockNotification", - // zap.Int64("height", height), - // zap.Int64("index", i), - // zap.Int64("processedheight", processedheight)) - if err != nil { return err } else if height != processedheight+i { @@ -419,6 +439,64 @@ loop: } } +func (icp *IconChainProcessor) verifyBlock(ctx context.Context, ibcHeader provider.IBCHeader) error { + header, ok := ibcHeader.(IconIBCHeader) + if !ok { + return fmt.Errorf("Provided Header is not compatible with IBCHeader") + } + if icp.firstTime { + proofContext, err := icp.chainProvider.GetProofContextByHeight(int64(header.MainHeight) - 1) + if err != nil { + return err + } + icp.verifier = &Verifier{ + nextProofContext: proofContext, + verifiedHeight: int64(header.MainHeight) - 1, + } + } + + if !ibcHeader.IsCompleteBlock() { + icp.verifier.nextProofContext = header.Validators + icp.verifier.verifiedHeight = int64(header.Height()) + return nil + } + + // prevNetworkSectionHash would be nil for first block + if icp.verifier.prevNetworkSectionHash != nil && + !bytes.Equal(icp.verifier.prevNetworkSectionHash, header.Header.PrevNetworkSectionHash) { + return fmt.Errorf("failed to match prevNetworkSectionHash") + } + + sigs, err := icp.chainProvider.GetBTPProof(int64(header.MainHeight)) + if err != nil { + return err + } + + decision := types.NewNetworkTypeSectionDecision( + getSrcNetworkId(icp.chainProvider.PCfg.ICONNetworkID), + icp.chainProvider.PCfg.BTPNetworkTypeID, + int64(header.MainHeight), + header.Header.Round, + types.NetworkTypeSection{ + NextProofContextHash: header.Header.NextProofContextHash, + NetworkSectionsRoot: GetNetworkSectionRoot(header.Header), + }) + + valid, err := VerifyBtpProof(decision, sigs, icp.verifier.nextProofContext) + if err != nil { + return err + } + + if !valid { + return fmt.Errorf("failed to Verify block") + } + + icp.verifier.nextProofContext = header.Validators + icp.verifier.verifiedHeight = int64(header.Height()) + icp.verifier.prevNetworkSectionHash = types.NewNetworkSection(header.Header).Hash() + return nil +} + func (icp *IconChainProcessor) handleBTPBlockRequest( request *btpBlockRequest, requestCh chan *btpBlockRequest) { defer func() { @@ -563,9 +641,9 @@ func (icp *IconChainProcessor) handlePathProcessorUpdate(ctx context.Context, // clientState will return the most recent client state if client messages // have already been observed for the clientID, otherwise it will query for it. func (icp *IconChainProcessor) clientState(ctx context.Context, clientID string) (provider.ClientState, error) { - // if state, ok := icp.latestClientState[clientID]; ok { - // return state, nil - // } + if state, ok := icp.latestClientState[clientID]; ok { + return state, nil + } cs, err := icp.chainProvider.QueryClientStateWithoutProof(ctx, int64(icp.latestBlock.Height), clientID) if err != nil { return provider.ClientState{}, err diff --git a/relayer/chains/icon/module/app_module.go b/relayer/chains/icon/module/app_module.go index 3e08ed65e..02690d1ab 100644 --- a/relayer/chains/icon/module/app_module.go +++ b/relayer/chains/icon/module/app_module.go @@ -44,6 +44,10 @@ func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) (*MerkleProofState)(nil), &icon.MerkleProofs{}, ) + registry.RegisterImplementations( + (*exported.ClientMessage)(nil), + &icon.SignedHeader{}, + ) } diff --git a/relayer/chains/icon/provider_test.go b/relayer/chains/icon/provider_test.go index e32e7d2cc..79f6c6a32 100644 --- a/relayer/chains/icon/provider_test.go +++ b/relayer/chains/icon/provider_test.go @@ -48,7 +48,7 @@ func GetMockIconProvider(network_id int, contractAddress string) *IconProvider { BTPNetworkTypeID: 1, IbcHandlerAddress: contractAddress, RPCAddr: "http://localhost:9082/api/v3", - // RPCAddr: "http://localhost:9999", + // RPCAddr: "https://berlin.net.solidwallet.io/api/v3", Timeout: "20s", } log, _ := zap.NewProduction() @@ -431,13 +431,41 @@ func TestHash(t *testing.T) { // assert.Equal(common.Sha3keccak256(b)) } -// goloop rpc sendtx call \ -// --uri http://localhost:9082/api/v3 \ -// --nid 3 \ -// --step_limit 1000000000\ -// --to cxc3c1f693b1616860d9f709d9c85b5f613ea2dbdb \ -// --method sendCallMessage \ -// --param _to=eth \ -// --param _data=0x6e696c696e \ -// --key_store /Users/viveksharmapoudel/keystore/godWallet.json \ -// --key_password gochain +// func TestUpdateClientHeader(t *testing.T) { + +// p := GetMockIconProvider(2, "dddd") + +// height := int64(401) +// header, _ := p.GetBtpHeader(height) +// proofContext, _ := p.GetProofContextByHeight(height - 1) + +// cs, _ := p.MsgUpdateClientHeader(NewIconIBCHeader(header, proofContext, height), clienttypes.Height{}, nil) + +// signedHeader, ok := cs.(*icon.SignedHeader) +// assert.True(t, ok) + +// btpLocalHeader := types.BTPBlockHeader{ +// MainHeight: signedHeader.Header.MainHeight, +// Round: int32(signedHeader.Header.Round), +// NextProofContextHash: signedHeader.Header.NextProofContextHash, +// NetworkSectionToRoot: signedHeader.Header.NetworkSectionToRoot, +// NetworkID: signedHeader.Header.NetworkId, +// UpdateNumber: header.UpdateNumber, +// PrevNetworkSectionHash: signedHeader.Header.PrevNetworkSectionHash, +// MessageCount: signedHeader.Header.MessageCount, +// MessageRoot: signedHeader.Header.MessageRoot, +// // NextProofContext: signedHeader.Header.NextProofContext, +// } +// networkSection := types.NewNetworkSection(&btpLocalHeader) +// fmt.Printf("newtworkSection :%x \n", networkSection.Hash()) +// decision := types.NewNetworkTypeSectionDecision(getSrcNetworkId(3), 1, height, btpLocalHeader.Round, +// types.NetworkTypeSection{ +// NextProofContextHash: btpLocalHeader.NextProofContextHash, +// NetworkSectionsRoot: GetNetworkSectionRoot(&btpLocalHeader), +// }) + +// isValid, err := VerifyBtpProof(decision, signedHeader.Signatures, proofContext) +// assert.NoError(t, err) + +// assert.True(t, isValid) +// } diff --git a/relayer/chains/icon/tx.go b/relayer/chains/icon/tx.go index abea8b860..b01e0379b 100644 --- a/relayer/chains/icon/tx.go +++ b/relayer/chains/icon/tx.go @@ -548,59 +548,6 @@ func (icp *IconProvider) MsgUpdateClient(clientID string, counterpartyHeader ibc return icp.NewIconMessage(updateClientMsg, MethodUpdateClient), nil } -// func (icp *IconProvider) SendMessageIcon(ctx context.Context, msg provider.RelayerMessage) (*types.TransactionResult, bool, error) { -// m := msg.(*IconMessage) -// txParam := &types.TransactionParam{ -// Version: types.NewHexInt(types.JsonrpcApiVersion), -// FromAddress: types.Address(icp.wallet.Address().String()), -// ToAddress: types.Address(icp.PCfg.IbcHandlerAddress), -// NetworkID: types.NewHexInt(icp.PCfg.ICONNetworkID), -// StepLimit: types.NewHexInt(int64(defaultStepLimit)), -// DataType: "call", -// Data: types.CallData{ -// Method: m.Method, -// Params: m.Params, -// }, -// } - -// if err := icp.client.SignTransaction(icp.wallet, txParam); err != nil { -// return nil, false, err -// } -// _, err := icp.client.SendTransaction(txParam) -// if err != nil { -// return nil, false, err -// } - -// txhash, _ := txParam.TxHash.Value() - -// icp.log.Info("Submitted Transaction ", zap.String("chain Id ", icp.ChainId()), -// zap.String("method", m.Method), zap.String("txHash", fmt.Sprintf("0x%x", txhash))) - -// txResParams := &types.TransactionHashParam{ -// Hash: txParam.TxHash, -// } - -// time.Sleep(2 * time.Second) - -// txResult, err := icp.client.GetTransactionResult(txResParams) - -// if err != nil { -// return nil, false, err -// } - -// if txResult.Status != types.NewHexInt(1) { -// return nil, false, fmt.Errorf("Transaction Failed and the transaction Result is 0x%x", txhash) -// } - -// icp.log.Info("Successful Transaction", -// zap.String("chain Id ", icp.ChainId()), -// zap.String("method", m.Method), -// zap.String("Height", string(txResult.BlockHeight)), -// zap.String("txHash", fmt.Sprintf("0x%x", txhash))) - -// return txResult, true, err -// } - func (icp *IconProvider) SendMessage(ctx context.Context, msg provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) { var ( @@ -783,6 +730,7 @@ func (icp *IconProvider) SendIconTransaction( return nil } +// TODO: review try to remove wait for Tx from packet-transfer and only use this for client and connection creation func (icp *IconProvider) WaitForTxResult( asyncCtx context.Context, txHash []byte, diff --git a/relayer/chains/icon/types/types.go b/relayer/chains/icon/types/types.go index 83a509956..0142d2f02 100644 --- a/relayer/chains/icon/types/types.go +++ b/relayer/chains/icon/types/types.go @@ -558,7 +558,7 @@ type ValidatorSignatures struct { type NetworkSection struct { Nid int64 - UpdateNumber int64 + UpdateNumber uint64 Prev []byte MessageCount int64 MessageRoot []byte @@ -567,9 +567,10 @@ type NetworkSection struct { func NewNetworkSection( header *BTPBlockHeader, ) *NetworkSection { + return &NetworkSection{ Nid: int64(header.NetworkID), - UpdateNumber: int64(header.UpdateNumber), + UpdateNumber: uint64(header.UpdateNumber), // Prev: header.PrevNetworkSectionHash, MessageCount: int64(header.MessageCount), MessageRoot: header.MessageRoot, @@ -577,6 +578,52 @@ func NewNetworkSection( } func (h *NetworkSection) Hash() []byte { - return relayer_common.Sha3keccak256(codec.RLP.MustMarshalToBytes(h)) + return relayer_common.Sha3keccak256(h.Encode()) +} +func (h *NetworkSection) Encode() []byte { + return codec.RLP.MustMarshalToBytes(h) +} + +type NetworkTypeSection struct { + NextProofContextHash []byte + NetworkSectionsRoot []byte +} + +type NetworkTypeSectionDecision struct { + SrcNetworkID string + NetworkTypeId int64 + Height int64 + Round int32 + NetworkTypeSectionHash []byte +} + +func NewNetworkTypeSectionDecision(SrcNetworkID string, + NetworkTypeId int64, + Height int64, + Round int32, + networkTypeSection NetworkTypeSection, +) *NetworkTypeSectionDecision { + return &NetworkTypeSectionDecision{ + SrcNetworkID, + NetworkTypeId, + Height, + Round, + (networkTypeSection.Hash()), + } +} + +func (h *NetworkTypeSectionDecision) Encode() []byte { + return codec.RLP.MustMarshalToBytes(h) +} + +func (h *NetworkTypeSectionDecision) Hash() []byte { + return relayer_common.Sha3keccak256(h.Encode()) +} + +func (h *NetworkTypeSection) Encode() []byte { + return codec.RLP.MustMarshalToBytes(h) +} +func (h *NetworkTypeSection) Hash() []byte { + return relayer_common.Sha3keccak256(h.Encode()) } diff --git a/relayer/chains/icon/utils.go b/relayer/chains/icon/utils.go index 2a19fa2a2..12fdf94a5 100644 --- a/relayer/chains/icon/utils.go +++ b/relayer/chains/icon/utils.go @@ -16,10 +16,15 @@ import ( "github.com/icon-project/IBC-Integration/libraries/go/common/icon" icn "github.com/icon-project/IBC-Integration/libraries/go/common/icon" "github.com/icon-project/goloop/common/codec" + "github.com/icon-project/goloop/common/crypto" "github.com/icon-project/goloop/common/db" "github.com/icon-project/goloop/common/trie/ompt" ) +var ( + ethAddressLen = 20 +) + func MptProve(key types.HexInt, proofs [][]byte, hash []byte) ([]byte, error) { db := db.NewMapDB() defer db.Close() @@ -132,3 +137,63 @@ func getIconPacketEncodedBytes(pkt provider.PacketInfo) ([]byte, error) { return proto.Marshal(&iconPkt) } + +func GetNetworkSectionRoot(header *types.BTPBlockHeader) []byte { + networkSection := types.NewNetworkSection(header) + return cryptoutils.CalculateRootFromProof(networkSection.Hash(), header.NetworkSectionToRoot) +} + +func VerifyBtpProof(decision *types.NetworkTypeSectionDecision, proof [][]byte, listValidators [][]byte) (bool, error) { + + requiredVotes := (2 * len(listValidators)) / 3 + if requiredVotes < 1 { + requiredVotes = 1 + } + + numVotes := 0 + validators := make(map[types.HexBytes]struct{}) + for _, val := range listValidators { + validators[types.NewHexBytes(val)] = struct{}{} + } + + for _, raw_sig := range proof { + sig, err := crypto.ParseSignature(raw_sig) + if err != nil { + return false, err + } + pubkey, err := sig.RecoverPublicKey(decision.Hash()) + if err != nil { + continue + } + + address, err := newEthAddressFromPubKey(pubkey.SerializeCompressed()) + if err != nil { + continue + } + if address == nil { + continue + } + if _, ok := validators[types.NewHexBytes(address)]; !ok { + continue + } + delete(validators, types.NewHexBytes(address)) + if numVotes++; numVotes >= requiredVotes { + return true, nil + } + } + + return false, nil + +} + +func newEthAddressFromPubKey(pubKey []byte) ([]byte, error) { + if len(pubKey) == crypto.PublicKeyLenCompressed { + pk, err := crypto.ParsePublicKey(pubKey) + if err != nil { + return nil, err + } + pubKey = pk.SerializeUncompressed() + } + digest := common.Sha3keccak256(pubKey[1:]) + return digest[len(digest)-ethAddressLen:], nil +} diff --git a/relayer/processor/message_processor.go b/relayer/processor/message_processor.go index b6137854e..a466c5539 100644 --- a/relayer/processor/message_processor.go +++ b/relayer/processor/message_processor.go @@ -83,6 +83,7 @@ func (mp *messageProcessor) processMessages( src, dst *pathEndRuntime, ) error { + fmt.Println("inside process Messages") // 2/3 rule enough_time_pass && context change in case of BTPBlock needsClientUpdate, err := mp.shouldUpdateClientNow(ctx, src, dst) if err != nil { @@ -104,19 +105,23 @@ func (mp *messageProcessor) processMessages( // Otherwise, it will be attempted if either 2/3 of the trusting period // or the configured client update threshold duration has passed. func (mp *messageProcessor) shouldUpdateClientNow(ctx context.Context, src, dst *pathEndRuntime) (bool, error) { - + var err error // handle if dst is IconLightClient if ClientIsIcon(dst.clientState) { - header, found := nextIconIBCHeader(src.ibcHeaderCache.Clone(), dst.lastClientUpdateHeight) if !found { - return false, nil + header, err = src.chainProvider.QueryIBCHeader(ctx, int64(src.latestBlock.Height)) + if err != nil { + return false, err + } + if !header.IsCompleteBlock() { + return false, nil + } } if header.ShouldUpdateWithZeroMessage() { return true, nil } - return false, nil }