diff --git a/go.mod b/go.mod index e9c8066fb..602dbdaf7 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,12 @@ go 1.18 require ( github.com/99designs/keyring v1.2.1 - github.com/ComposableFi/go-substrate-rpc-client/v4 v4.0.1-0.20220830115327-2c45fdcbfba1 - github.com/ComposableFi/ics11-beefy v0.0.0-20220907132902-15f736a8e91e + github.com/ChainSafe/chaindb v0.1.5-0.20220322154826-c0d431995732 + github.com/ChainSafe/gossamer v0.6.1-0.20220406182257-98400b30ca00 + github.com/ComposableFi/go-merkle-trees v0.0.0-20220505132313-e976260288cc + github.com/ComposableFi/go-substrate-rpc-client/v4 v4.0.1-0.20220920084912-1395042133a0 + github.com/ComposableFi/ics11-beefy v0.0.0-20220915123708-0965f4b89a73 + github.com/OneOfOne/xxhash v1.2.8 github.com/avast/retry-go/v4 v4.1.0 github.com/cosmos/cosmos-sdk v0.46.1 github.com/cosmos/ibc-go/v5 v5.0.0 @@ -39,15 +43,11 @@ require ( filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/ChainSafe/chaindb v0.1.5-0.20220322154826-c0d431995732 // indirect - github.com/ChainSafe/go-schnorrkel v0.0.0-20210318173838-ccb5cd955283 // indirect - github.com/ChainSafe/gossamer v0.6.1-0.20220406182257-98400b30ca00 // indirect + github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect github.com/ChainSafe/log15 v1.0.0 // indirect - github.com/ComposableFi/go-merkle-trees v0.0.0-20220505132313-e976260288cc // indirect github.com/ComposableFi/go-subkey/v2 v2.0.0-tm03420 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect - github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/armon/go-metrics v0.4.0 // indirect github.com/aws/aws-sdk-go v1.40.45 // indirect @@ -68,7 +68,7 @@ require ( github.com/cosmos/btcutil v1.0.4 // indirect github.com/cosmos/cosmos-proto v1.0.0-alpha7 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect - github.com/cosmos/iavl v0.19.0 // indirect + github.com/cosmos/iavl v0.19.1 // indirect github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect github.com/cosmos/ledger-go v0.9.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect @@ -87,7 +87,6 @@ require ( github.com/docker/go-units v0.4.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect - github.com/ethereum/go-ethereum v1.10.23 // indirect github.com/evmos/ethermint v0.6.1-0.20220810122651-42abb259cbed // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect @@ -207,4 +206,8 @@ require ( sigs.k8s.io/yaml v1.3.0 // indirect ) +replace github.com/cosmos/ibc-go/v5 => github.com/ComposableFi/ibc-go/v5 v5.0.0-20220915110718-770f27ffee50 + replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 + +replace github.com/ChainSafe/go-schnorrkel v1.0.0 => github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d diff --git a/relayer/chains/substrate/construct.go b/relayer/chains/substrate/construct.go new file mode 100644 index 000000000..c81c2ee2d --- /dev/null +++ b/relayer/chains/substrate/construct.go @@ -0,0 +1,668 @@ +package substrate + +import ( + "bytes" + "encoding/binary" + "fmt" + "sort" + + "github.com/ChainSafe/gossamer/lib/trie" + "github.com/ComposableFi/go-merkle-trees/hasher" + "github.com/ComposableFi/go-merkle-trees/merkle" + "github.com/ComposableFi/go-merkle-trees/mmr" + rpcclienttypes "github.com/ComposableFi/go-substrate-rpc-client/v4/types" + beefyclienttypes "github.com/ComposableFi/ics11-beefy/types" + "github.com/OneOfOne/xxhash" + "github.com/ethereum/go-ethereum/crypto" + "golang.org/x/exp/maps" +) + +type Authorities = [][33]uint8 + +const ( + prefixParas = "Paras" + prefixBeefy = "Beefy" + methodParachains = "Parachains" + methodHeads = "Heads" + methodAuthorities = "Authorities" + methodNextAuthorities = "NextAuthorities" +) + +func (sp *SubstrateProvider) clientState( + commitment rpcclienttypes.SignedCommitment, +) (*beefyclienttypes.ClientState, error) { + blockNumber := uint32(commitment.Commitment.BlockNumber) + authorities, err := sp.beefyAuthorities(blockNumber, methodAuthorities) + if err != nil { + return nil, err + } + + nextAuthorities, err := sp.beefyAuthorities(blockNumber, methodNextAuthorities) + if err != nil { + return nil, err + } + + var authorityLeaves [][]byte + for _, v := range authorities { + authorityLeaves = append(authorityLeaves, crypto.Keccak256(v)) + } + + authorityTree, err := merkle.NewTree(hasher.Keccak256Hasher{}).FromLeaves(authorityLeaves) + if err != nil { + return nil, err + } + + var nextAuthorityLeaves [][]byte + for _, v := range nextAuthorities { + nextAuthorityLeaves = append(nextAuthorityLeaves, crypto.Keccak256(v)) + } + + nextAuthorityTree, err := merkle.NewTree(hasher.Keccak256Hasher{}).FromLeaves(nextAuthorityLeaves) + if err != nil { + return nil, err + } + + var authorityTreeRoot = bytes32(authorityTree.Root()) + var nextAuthorityTreeRoot = bytes32(nextAuthorityTree.Root()) + + blockHash, err := sp.RPCClient.RPC.Chain.GetBlockHash(uint64(blockNumber)) + if err != nil { + return nil, err + } + + headData, err := sp.paraHeadData(blockHash) + if err != nil { + return nil, err + } + + paraHead, err := beefyclienttypes.DecodeParachainHeader(headData) + if err != nil { + return nil, err + } + + return &beefyclienttypes.ClientState{ + MMRRootHash: commitment.Commitment.Payload[0].Value, + LatestBeefyHeight: blockNumber, + BeefyActivationBlock: 0, + Authority: &beefyclienttypes.BeefyAuthoritySet{ + ID: uint64(commitment.Commitment.ValidatorSetID), + Len: uint32(len(authorities)), + AuthorityRoot: &authorityTreeRoot, + }, + NextAuthoritySet: &beefyclienttypes.BeefyAuthoritySet{ + ID: uint64(commitment.Commitment.ValidatorSetID) + 1, + Len: uint32(len(nextAuthorities)), + AuthorityRoot: &nextAuthorityTreeRoot, + }, + ParaID: sp.Config.ParaID, + LatestParaHeight: uint32(paraHead.Number), + RelayChain: beefyclienttypes.RelayChain_KUSAMA, + }, nil +} + +func (sp *SubstrateProvider) fetchParaIds(blockHash rpcclienttypes.Hash) ([]uint32, error) { + // Fetch metadata + meta, err := sp.RPCClient.RPC.State.GetMetadataLatest() + if err != nil { + return nil, err + } + + storageKey, err := rpcclienttypes.CreateStorageKey(meta, prefixParas, methodParachains, nil, nil) + if err != nil { + return nil, err + } + + var paraIds []uint32 + + ok, err := sp.RPCClient.RPC.State.GetStorage(storageKey, ¶Ids, blockHash) + if err != nil { + return nil, err + } + + if !ok { + return nil, fmt.Errorf("%s: storage key %v, paraids %v, block hash %v", ErrBeefyAttributesNotFound, storageKey, paraIds, blockHash) + } + + return paraIds, nil +} + +func (sp *SubstrateProvider) parachainHeaderKey() ([]byte, error) { + keyPrefix := rpcclienttypes.CreateStorageKeyPrefix(prefixParas, methodHeads) + encodedParaId, err := Encode(sp.Config.ParaID) + if err != nil { + return nil, err + } + + twoxhash := xxhash.New64().Sum(encodedParaId) + fullKey := append(append(keyPrefix, twoxhash[:]...), encodedParaId...) + return fullKey, nil +} + +func (sp *SubstrateProvider) paraHeadData(blockHash rpcclienttypes.Hash) ([]byte, error) { + paraKey, err := sp.parachainHeaderKey() + if err != nil { + return nil, err + } + + storage, err := sp.RPCClient.RPC.State.GetStorageRaw(paraKey, blockHash) + if err != nil { + return nil, err + } + + return *storage, nil +} + +func (sp *SubstrateProvider) beefyAuthorities(blockNumber uint32, method string) ([][]byte, error) { + blockHash, err := sp.RPCClient.RPC.Chain.GetBlockHash(uint64(blockNumber)) + if err != nil { + return nil, err + } + + // Fetch metadata + meta, err := sp.RPCClient.RPC.State.GetMetadataLatest() + if err != nil { + return nil, err + } + + storageKey, err := rpcclienttypes.CreateStorageKey(meta, prefixBeefy, method, nil, nil) + if err != nil { + return nil, err + } + + var authorities Authorities + + ok, err := sp.RPCClient.RPC.State.GetStorage(storageKey, &authorities, blockHash) + if err != nil { + return nil, err + } + + if !ok { + return nil, fmt.Errorf("%s: beefy construct not found: storage key %v, authorities %v, block hash %v", + ErrBeefyConstructNotFound, storageKey, authorities, blockHash) + } + + // Convert from ecdsa public key to ethereum address + var authorityEthereumAddresses [][]byte + for _, authority := range authorities { + pub, err := crypto.DecompressPubkey(authority[:]) + if err != nil { + return nil, err + } + ethereumAddress := crypto.PubkeyToAddress(*pub) + if err != nil { + return nil, err + } + authorityEthereumAddresses = append(authorityEthereumAddresses, ethereumAddress[:]) + } + + return authorityEthereumAddresses, nil +} + +func (sp *SubstrateProvider) signedCommitment( + blockHash rpcclienttypes.Hash, +) (rpcclienttypes.SignedCommitment, error) { + signedBlock, err := sp.RelayerRPCClient.RPC.Chain.GetBlock(blockHash) + if err != nil { + return rpcclienttypes.SignedCommitment{}, err + } + + for _, v := range signedBlock.Justifications { + if bytes.Equal(v.ConsensusEngineID[:], []byte("BEEF")) { + versionedFinalityProof := &rpcclienttypes.VersionedFinalityProof{} + + err = rpcclienttypes.Decode(v.EncodedJustification, versionedFinalityProof) + if err != nil { + return rpcclienttypes.SignedCommitment{}, err + } + + return versionedFinalityProof.AsCompactSignedCommitment.Unpack(), nil + } + } + + return rpcclienttypes.SignedCommitment{}, nil +} + +// finalized block returns the finalized block double map that holds block numbers, +// for which our parachain header was included in the mmr leaf, seeing as our parachain +// headers might not make it into every relay chain block. Map> +// It also returns the leaf indices of those blocks +func (sp *SubstrateProvider) getFinalizedBlocks( + blockHash rpcclienttypes.Hash, + previouslyFinalizedBlockHash *rpcclienttypes.Hash, +) (map[uint32]map[uint32][]byte, []uint64, error) { + var finalizedBlocks = make(map[uint32]map[uint32][]byte) + var leafIndices []uint64 + + if previouslyFinalizedBlockHash == nil { + var heads = make(map[uint32][]byte) + headData, err := sp.paraHeadData(blockHash) + if err != nil { + return nil, nil, err + } + + paraHead, err := beefyclienttypes.DecodeParachainHeader(headData) + if err != nil { + return nil, nil, err + } + + heads[sp.Config.ParaID] = headData + finalizedBlocks[uint32(paraHead.Number)] = heads + leafIndices = append(leafIndices, uint64(getLeafIndexForBlockNumber(sp.Config.BeefyActivationBlock, + uint32(paraHead.Number)))) + return finalizedBlocks, leafIndices, nil + } + + paraHeaderKeys, err := sp.parachainHeaderKeys(blockHash) + if err != nil { + return nil, nil, err + } + + changeSet, err := sp.RPCClient.RPC.State.QueryStorage(paraHeaderKeys, *previouslyFinalizedBlockHash, blockHash) + if err != nil { + return nil, nil, err + } + + for _, changes := range changeSet { + header, err := sp.RPCClient.RPC.Chain.GetHeader(changes.Block) + if err != nil { + return nil, nil, err + } + var heads = make(map[uint32][]byte) + + for _, keyValue := range changes.Changes { + if keyValue.HasStorageData { + var paraId uint32 + err = rpcclienttypes.Decode(keyValue.StorageKey[40:], ¶Id) + if err != nil { + return nil, nil, err + } + + heads[paraId] = keyValue.StorageData + } + } + + // check if heads has target id, else skip + if heads[sp.Config.ParaID] == nil { + continue + } + + finalizedBlocks[uint32(header.Number)] = heads + + leafIndices = append(leafIndices, uint64(getLeafIndexForBlockNumber(sp.Config.BeefyActivationBlock, + uint32(header.Number)))) + } + return finalizedBlocks, leafIndices, nil +} + +func (sp *SubstrateProvider) parachainHeaderKeys( + + blockHash rpcclienttypes.Hash, +) ([]rpcclienttypes.StorageKey, error) { + paraIds, err := sp.fetchParaIds(blockHash) + if err != nil { + return nil, err + } + + var paraHeaderKeys []rpcclienttypes.StorageKey + // create full storage key for each known paraId. + keyPrefix := rpcclienttypes.CreateStorageKeyPrefix(prefixParas, methodHeads) + // so we can query all blocks from lastfinalized to latestBeefyHeight + for _, paraId := range paraIds { + encodedParaId, err := Encode(paraId) + if err != nil { + return nil, err + } + twoxhash := xxhash.New64().Sum(encodedParaId) + // full key path in the storage source: https://www.shawntabrizi.com/assets/presentations/substrate-storage-deep-dive.pdf + // xx128("Paras") + xx128("Heads") + xx64(Encode(paraId)) + Encode(paraId) + fullKey := append(append(keyPrefix, twoxhash[:]...), encodedParaId...) + paraHeaderKeys = append(paraHeaderKeys, fullKey) + } + + return paraHeaderKeys, nil +} + +func (sp *SubstrateProvider) constructParachainHeaders( + blockHash rpcclienttypes.Hash, + previouslyFinalizedBlockHash *rpcclienttypes.Hash, +) ([]*beefyclienttypes.ParachainHeader, error) { + var finalizedBlocks = make(map[uint32]map[uint32][]byte) + var leafIndices []uint64 + finalizedBlocks, leafIndices, err := sp.getFinalizedBlocks(blockHash, previouslyFinalizedBlockHash) + if err != nil { + return nil, err + } + + // fetch mmr proofs for leaves containing our target paraId + mmrBatchProof, err := sp.RPCClient.RPC.MMR.GenerateBatchProof(leafIndices, blockHash) + if err != nil { + return nil, err + } + + var parachainHeaders []*beefyclienttypes.ParachainHeader + + var paraHeads = make([][]byte, len(mmrBatchProof.Leaves)) + + for i := 0; i < len(mmrBatchProof.Leaves); i++ { + v := mmrBatchProof.Leaves[i] + leafIndex := mmrBatchProof.Proof.LeafIndex[i] + + paraHeads[i] = v.ParachainHeads[:] + // TODO: The activation block number can be added to the substrate provider and this method as well + var leafBlockNumber = getBlockNumberForLeaf(sp.Config.BeefyActivationBlock, uint32(leafIndex)) + paraHeaders := finalizedBlocks[leafBlockNumber] + + var paraHeadsLeaves [][]byte + // index of our parachain header in the + // parachain heads merkle root + var index uint64 + + count := 0 + + // sort by paraId + sortedParaIds := maps.Keys(paraHeaders) + sort.SliceStable(sortedParaIds, func(i, j int) bool { + return sortedParaIds[i] < sortedParaIds[j] + }) + + for _, paraId := range sortedParaIds { + paraIdScale := make([]byte, 4) + // scale encode para_id + binary.LittleEndian.PutUint32(paraIdScale[:], paraId) + leaf := append(paraIdScale, paraHeaders[paraId]...) + paraHeadsLeaves = append(paraHeadsLeaves, crypto.Keccak256(leaf)) + if paraId == sp.Config.ParaID { + // note index of paraId + index = uint64(count) + } + count++ + } + + tree, err := merkle.NewTree(hasher.Keccak256Hasher{}).FromLeaves(paraHeadsLeaves) + if err != nil { + return nil, err + } + paraHeadsProof := tree.Proof([]uint64{index}) + authorityRoot := bytes32(v.BeefyNextAuthoritySet.Root[:]) + parentHash := bytes32(v.ParentNumberAndHash.Hash[:]) + + parachainHeaderDecoded, err := beefyclienttypes.DecodeParachainHeader(paraHeaders[sp.Config.ParaID]) + if err != nil { + return nil, err + } + + timestampExt, extProof, err := sp.constructExtrinsics(uint32(parachainHeaderDecoded.Number)) + if err != nil { + return nil, err + } + + header := beefyclienttypes.ParachainHeader{ + ParachainHeader: paraHeaders[sp.Config.ParaID], + PartialMMRLeaf: &beefyclienttypes.PartialMMRLeaf{ + Version: beefyclienttypes.U8(v.Version), + ParentNumber: uint32(v.ParentNumberAndHash.ParentNumber), + ParentHash: &parentHash, + BeefyNextAuthoritySet: beefyclienttypes.BeefyAuthoritySet{ + ID: uint64(v.BeefyNextAuthoritySet.ID), + Len: uint32(v.BeefyNextAuthoritySet.Len), + AuthorityRoot: &authorityRoot, + }, + }, + ParachainHeadsProof: paraHeadsProof.ProofHashes(), + HeadsLeafIndex: uint32(index), + HeadsTotalCount: uint32(len(paraHeadsLeaves)), + TimestampExtrinsic: timestampExt, + ExtrinsicProof: extProof, + } + + parachainHeaders = append(parachainHeaders, &header) + } + + return parachainHeaders, nil +} + +func (sp *SubstrateProvider) constructExtrinsics( + blockNumber uint32, +) (timestampExtrinsic []byte, extrinsicProof [][]byte, err error) { + blockHash, err := sp.RPCClient.RPC.Chain.GetBlockHash(uint64(blockNumber)) + if err != nil { + return nil, nil, err + } + + block, err := sp.RPCClient.RPC.Chain.GetBlock(blockHash) + if err != nil { + return nil, nil, err + } + + exts := block.Block.Extrinsics + if len(exts) == 0 { + return nil, nil, nil + } + + timestampExtrinsic, err = Encode(exts[0]) + if err != nil { + return nil, nil, err + } + + t := trie.NewEmptyTrie() + for i := 0; i < len(exts); i++ { + ext, err := Encode(exts[i]) + if err != nil { + return nil, nil, err + } + + key := rpcclienttypes.NewUCompactFromUInt(uint64(i)) + encodedKey, err := Encode(key) + if err != nil { + return nil, nil, err + } + + t.Put(encodedKey, ext) + } + + err = t.Store(sp.Memdb) + if err != nil { + return nil, nil, err + } + + rootHash, err := t.Hash() + if err != nil { + return nil, nil, err + } + + timestampKey := rpcclienttypes.NewUCompactFromUInt(uint64(0)) + encodedTPKey, err := Encode(timestampKey) + if err != nil { + return nil, nil, err + } + extrinsicProof, err = trie.GenerateProof(rootHash.ToBytes(), [][]byte{encodedTPKey}, sp.Memdb) + if err != nil { + return nil, nil, err + } + + return +} + +func (sp *SubstrateProvider) mmrBatchProofs( + blockHash rpcclienttypes.Hash, + previouslyFinalizedBlockHash *rpcclienttypes.Hash, +) (rpcclienttypes.GenerateMmrBatchProofResponse, error) { + var leafIndices []uint64 + _, leafIndices, err := sp.getFinalizedBlocks(blockHash, previouslyFinalizedBlockHash) + if err != nil { + return rpcclienttypes.GenerateMmrBatchProofResponse{}, err + } + + // fetch mmr proofs for leaves containing our target paraId + batchProofs, err := sp.RelayerRPCClient.RPC.MMR.GenerateBatchProof(leafIndices, blockHash) + if err != nil { + return rpcclienttypes.GenerateMmrBatchProofResponse{}, err + } + + return batchProofs, nil +} + +func (sp *SubstrateProvider) mmrUpdateProof( + blockHash rpcclienttypes.Hash, + signedCommitment rpcclienttypes.SignedCommitment, + leafIndex uint64, +) (*beefyclienttypes.MMRUpdateProof, error) { + mmrProof, err := sp.RPCClient.RPC.MMR.GenerateProof( + leafIndex, + blockHash, + ) + if err != nil { + return nil, err + } + + latestLeaf := mmrProof.Leaf + parentHash := bytes32(latestLeaf.ParentNumberAndHash.Hash[:]) + parachainHeads := bytes32(latestLeaf.ParachainHeads[:]) + beefyNextAuthoritySetRoot := bytes32(latestLeaf.BeefyNextAuthoritySet.Root[:]) + commitmentPayload := signedCommitment.Commitment.Payload[0] + + var latestLeafMmrProof = make([][]byte, len(mmrProof.Proof.Items)) + for i := 0; i < len(mmrProof.Proof.Items); i++ { + latestLeafMmrProof[i] = mmrProof.Proof.Items[i][:] + } + + var signatures []*beefyclienttypes.CommitmentSignature + var authorityIndices []uint64 + // luckily for us, this is already sorted and maps to the right authority index in the authority root. + for i, v := range signedCommitment.Signatures { + if v.IsSome() { + _, sig := v.Unwrap() + signatures = append(signatures, &beefyclienttypes.CommitmentSignature{ + Signature: sig[:], + AuthorityIndex: uint32(i), + }) + authorityIndices = append(authorityIndices, uint64(i)) + } + } + + authorities, err := sp.beefyAuthorities(uint32(signedCommitment.Commitment.BlockNumber), methodAuthorities) + if err != nil { + return nil, err + } + + var authorityLeaves [][]byte + for _, v := range authorities { + authorityLeaves = append(authorityLeaves, crypto.Keccak256(v)) + } + authorityTree, err := merkle.NewTree(hasher.Keccak256Hasher{}).FromLeaves(authorityLeaves) + if err != nil { + return nil, err + } + + var payloadId beefyclienttypes.SizedByte2 = commitmentPayload.ID + return &beefyclienttypes.MMRUpdateProof{ + LatestMMRLeaf: &beefyclienttypes.BeefyMMRLeaf{ + Version: beefyclienttypes.U8(latestLeaf.Version), + ParentNumber: uint32(latestLeaf.ParentNumberAndHash.ParentNumber), + ParentHash: &parentHash, + ParachainHeads: ¶chainHeads, + BeefyNextAuthoritySet: beefyclienttypes.BeefyAuthoritySet{ + ID: uint64(latestLeaf.BeefyNextAuthoritySet.ID), + Len: uint32(latestLeaf.BeefyNextAuthoritySet.Len), + AuthorityRoot: &beefyNextAuthoritySetRoot, + }, + }, + LatestMMRLeafIndex: leafIndex, + MMRProof: latestLeafMmrProof, + SignedCommitment: &beefyclienttypes.SignedCommitment{ + Commitment: &beefyclienttypes.Commitment{ + Payload: []*beefyclienttypes.Payload{ + {PayloadID: &payloadId, PayloadData: commitmentPayload.Value}, + }, + BlockNumber: uint32(signedCommitment.Commitment.BlockNumber), + ValidatorSetID: uint64(signedCommitment.Commitment.ValidatorSetID), + }, + Signatures: signatures, + }, + AuthoritiesProof: authorityTree.Proof(authorityIndices).ProofHashes(), + }, nil +} + +func (sp *SubstrateProvider) constructBeefyHeader( + blockHash rpcclienttypes.Hash, + previousFinalizedHash *rpcclienttypes.Hash, +) (*beefyclienttypes.Header, error) { + // assuming blockHash is always the latest beefy block hash + // TODO: check that it is the latest block hash + latestCommitment, err := sp.signedCommitment(blockHash) + if err != nil { + return nil, err + } + + parachainHeads, err := sp.constructParachainHeaders(blockHash, previousFinalizedHash) + if err != nil { + return nil, err + } + + batchProofs, err := sp.mmrBatchProofs(blockHash, previousFinalizedHash) + if err != nil { + return nil, err + } + + leafIndex := getLeafIndexForBlockNumber(sp.Config.BeefyActivationBlock, uint32(latestCommitment.Commitment.BlockNumber)) + blockNumber := uint32(latestCommitment.Commitment.BlockNumber) + mmrProof, err := sp.mmrUpdateProof(blockHash, latestCommitment, + uint64(getLeafIndexForBlockNumber(sp.Config.BeefyActivationBlock, blockNumber))) + if err != nil { + return nil, err + } + + return &beefyclienttypes.Header{ + HeadersWithProof: &beefyclienttypes.ParachainHeadersWithProof{ + Headers: parachainHeads, + MMRProofs: mmrBatchProofItems(batchProofs), + MMRSize: mmr.LeafIndexToMMRSize(uint64(leafIndex)), + }, + MMRUpdateProof: mmrProof, + }, nil +} + +func mmrBatchProofItems(mmrBatchProof rpcclienttypes.GenerateMmrBatchProofResponse) [][]byte { + var proofItems = make([][]byte, len(mmrBatchProof.Proof.Items)) + for i := 0; i < len(mmrBatchProof.Proof.Items); i++ { + proofItems[i] = mmrBatchProof.Proof.Items[i][:] + } + return proofItems +} + +func getBlockNumberForLeaf(beefyActivationBlock, leafIndex uint32) uint32 { + var blockNumber uint32 + + // calculate the leafIndex for this leaf. + if beefyActivationBlock == 0 { + // in this case the leaf index is the same as the block number - 1 (leaf index starts at 0) + blockNumber = leafIndex + 1 + } else { + // in this case the leaf index is activation block - current block number. + blockNumber = beefyActivationBlock + leafIndex + } + + return blockNumber +} + +// GetLeafIndexForBlockNumber given the MmrLeafPartial.ParentNumber & BeefyActivationBlock, +func getLeafIndexForBlockNumber(beefyActivationBlock, blockNumber uint32) uint32 { + var leafIndex uint32 + + // calculate the leafIndex for this leaf. + if beefyActivationBlock == 0 { + // in this case the leaf index is the same as the block number - 1 (leaf index starts at 0) + leafIndex = blockNumber - 1 + } else { + // in this case the leaf index is activation block - current block number. + leafIndex = beefyActivationBlock - (blockNumber + 1) + } + + return leafIndex +} + +func bytes32(bytes []byte) beefyclienttypes.SizedByte32 { + var buffer beefyclienttypes.SizedByte32 + copy(buffer[:], bytes) + return buffer +} diff --git a/relayer/chains/substrate/errors.go b/relayer/chains/substrate/errors.go new file mode 100644 index 000000000..5acdadfaa --- /dev/null +++ b/relayer/chains/substrate/errors.go @@ -0,0 +1,9 @@ +package substrate + +const ( + ErrTextConsensusStateNotFound = "consensus state not found: %s" + ErrTextSubstrateDoesNotHaveQueryForTransactions = "substrate chains do not support transaction querying" + ErrBeefyAttributesNotFound = "beefy authorities not found" + ErrBeefyConstructNotFound = "beefy construct not found" + ErrDifferentTypesOfCallsMixed = "different types of calls can't be mixed" +) diff --git a/relayer/chains/substrate/event_parser.go b/relayer/chains/substrate/event_parser.go index 3c7a3ec7a..264ee7315 100644 --- a/relayer/chains/substrate/event_parser.go +++ b/relayer/chains/substrate/event_parser.go @@ -155,25 +155,25 @@ func genAccumKey(data interface{}) ibcPacketKey { // client info attributes and methods type clientInfo struct { - Height clienttypes.Height - ClientID string - ClientType uint32 - ConsensusHeight clienttypes.Height + height clienttypes.Height + clientID string + clientType uint32 + consensusHeight clienttypes.Height } // ClientState returns the height and id of client func (cl clientInfo) ClientState() provider.ClientState { return provider.ClientState{ - ClientID: cl.ClientID, - ConsensusHeight: cl.ConsensusHeight, + ClientID: cl.clientID, + ConsensusHeight: cl.consensusHeight, } } // MarshalLogObject marshals attributes of client info func (cl *clientInfo) MarshalLogObject(enc zapcore.ObjectEncoder) error { - enc.AddString("client_id", cl.ClientID) - enc.AddUint64("consensus_height", cl.ConsensusHeight.RevisionHeight) - enc.AddUint64("consensus_height_revision", cl.ConsensusHeight.RevisionNumber) + enc.AddString("client_id", cl.clientID) + enc.AddUint64("consensus_height", cl.consensusHeight.RevisionHeight) + enc.AddUint64("consensus_height_revision", cl.consensusHeight.RevisionNumber) return nil } @@ -182,17 +182,17 @@ func (cl *clientInfo) parseAttrs(log *zap.Logger, attributes interface{}) { attrs := attributes.(ibcEventQueryItem) var err error - if cl.Height, err = parseHeight(attrs["height"]); err != nil { + if cl.height, err = parseHeight(attrs["height"]); err != nil { log.Error("error parsing client consensus height: ", zap.Error(err), ) return } - cl.ClientID = attrs["client_id"].(string) - cl.ClientType = attrs["client_type"].(uint32) + cl.clientID = attrs["client_id"].(string) + cl.clientType = attrs["client_type"].(uint32) - if cl.ConsensusHeight, err = parseHeight(attrs["consensus_height"]); err != nil { + if cl.consensusHeight, err = parseHeight(attrs["consensus_height"]); err != nil { log.Error("error parsing client consensus height: ", zap.Error(err), ) @@ -203,15 +203,15 @@ func (cl *clientInfo) parseAttrs(log *zap.Logger, attributes interface{}) { // client update info attributes and methods type clientUpdateInfo struct { - Common clientInfo - Header beefyclienttypes.Header + common clientInfo + header beefyclienttypes.Header } // MarshalLogObject marshals attributes of client update info func (clu *clientUpdateInfo) MarshalLogObject(enc zapcore.ObjectEncoder) error { - enc.AddString("client_id", clu.Common.ClientID) - enc.AddUint64("consensus_height", clu.Common.ConsensusHeight.RevisionHeight) - enc.AddUint64("consensus_height_revision", clu.Common.ConsensusHeight.RevisionNumber) + enc.AddString("client_id", clu.common.clientID) + enc.AddUint64("consensus_height", clu.common.consensusHeight.RevisionHeight) + enc.AddUint64("consensus_height_revision", clu.common.consensusHeight.RevisionNumber) // TODO: include header if return nil } @@ -222,9 +222,9 @@ func (clu *clientUpdateInfo) parseAttrs(log *zap.Logger, attributes interface{}) clientInfo := new(clientInfo) clientInfo.parseAttrs(log, attrs["common"]) - clu.Common = *clientInfo + clu.common = *clientInfo - clu.Header = parseHeader(attrs["header"]) + clu.header = parseHeader(attrs["header"]) } // alias type to the provider types, used for adding parser methods diff --git a/relayer/chains/substrate/key.go b/relayer/chains/substrate/key.go index b9db2319d..8a7c16996 100644 --- a/relayer/chains/substrate/key.go +++ b/relayer/chains/substrate/key.go @@ -9,7 +9,7 @@ import ( ) func (sp *SubstrateProvider) CreateKeystore(path string) error { - keybase, err := keystore.New(sp.PCfg.ChainID, sp.PCfg.KeyringBackend, sp.PCfg.KeyDirectory, sp.Input) + keybase, err := keystore.New(sp.Config.ChainID, sp.Config.KeyringBackend, sp.Config.KeyDirectory, sp.Input) if err != nil { return err } @@ -103,7 +103,7 @@ func (sp *SubstrateProvider) KeyAddOrRestore(keyName string, coinType uint32, mn } } - info, err := sp.Keybase.NewAccount(keyName, mnemonicStr, sp.PCfg.Network) + info, err := sp.Keybase.NewAccount(keyName, mnemonicStr, sp.Config.Network) if err != nil { return nil, err } diff --git a/relayer/chains/substrate/log.go b/relayer/chains/substrate/log.go new file mode 100644 index 000000000..dbcc163a0 --- /dev/null +++ b/relayer/chains/substrate/log.go @@ -0,0 +1,15 @@ +package substrate + +import ( + "github.com/cosmos/relayer/v2/relayer/provider" +) + +// LogFailedTx takes the transaction and the messages to create it and logs the appropriate data +func (cc *SubstrateProvider) LogFailedTx(res *provider.RelayerTxResponse, err error, msgs []provider.RelayerMessage) { + // implement +} + +// LogSuccessTx take the transaction and the messages to create it and logs the appropriate data +func (cc *SubstrateProvider) LogSuccessTx(res *provider.RelayerTxResponse, msgs []provider.RelayerMessage) { + // implement +} diff --git a/relayer/chains/substrate/message_handlers.go b/relayer/chains/substrate/message_handlers.go index 7b33b438d..14f7126c2 100644 --- a/relayer/chains/substrate/message_handlers.go +++ b/relayer/chains/substrate/message_handlers.go @@ -129,7 +129,7 @@ func (ccp *SubstrateChainProcessor) handleConnectionMessage(eventType string, ci func (ccp *SubstrateChainProcessor) handleClientMessage(eventType string, ci clientInfo) { ccp.latestClientState.update(ci) - ccp.logObservedIBCMessage(eventType, zap.String("client_id", ci.ClientID)) + ccp.logObservedIBCMessage(eventType, zap.String("client_id", ci.clientID)) } func (ccp *SubstrateChainProcessor) logObservedIBCMessage(m string, fields ...zap.Field) { diff --git a/relayer/chains/substrate/msg.go b/relayer/chains/substrate/msg.go new file mode 100644 index 000000000..9d0b82fb8 --- /dev/null +++ b/relayer/chains/substrate/msg.go @@ -0,0 +1,30 @@ +package substrate + +import ( + "github.com/cosmos/relayer/v2/relayer/provider" + "github.com/gogo/protobuf/proto" +) + +var _ provider.RelayerMessage = &SubstrateMessage{} + +type Msg interface { + proto.Message +} + +type SubstrateMessage struct { + Msg Msg +} + +func NewSubstrateMessage(msg Msg) provider.RelayerMessage { + return SubstrateMessage{ + Msg: msg, + } +} + +func (cm SubstrateMessage) Type() string { + return "/" + proto.MessageName(cm.Msg) +} + +func (cm SubstrateMessage) MsgBytes() ([]byte, error) { + return proto.Marshal(cm.Msg) +} diff --git a/relayer/chains/substrate/provider.go b/relayer/chains/substrate/provider.go index 80808f362..a16dc3520 100644 --- a/relayer/chains/substrate/provider.go +++ b/relayer/chains/substrate/provider.go @@ -7,6 +7,7 @@ import ( "path" "time" + "github.com/ChainSafe/chaindb" rpcclient "github.com/ComposableFi/go-substrate-rpc-client/v4" beefyclienttypes "github.com/ComposableFi/ics11-beefy/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -28,17 +29,23 @@ var ( ) type SubstrateProviderConfig struct { - Key string `json:"key" yaml:"key"` - ChainName string `json:"-" yaml:"-"` - ChainID string `json:"chain-id" yaml:"chain-id"` - RPCAddr string `json:"rpc-addr" yaml:"rpc-addr"` - RelayRPCAddr string `json:"relay-rpc-addr" yaml:"relay-rpc-addr"` - AccountPrefix string `json:"account-prefix" yaml:"account-prefix"` - KeyringBackend string `json:"keyring-backend" yaml:"keyring-backend"` - KeyDirectory string `json:"key-directory" yaml:"key-directory"` - Debug bool `json:"debug" yaml:"debug"` - Timeout string `json:"timeout" yaml:"timeout"` - Network uint16 `json:"network" yaml:"network"` + Key string `json:"key" yaml:"key"` + ChainName string `json:"-" yaml:"-"` + ChainID string `json:"chain-id" yaml:"chain-id"` + RPCAddr string `json:"rpc-addr" yaml:"rpc-addr"` + RelayRPCAddr string `json:"relay-rpc-addr" yaml:"relay-rpc-addr"` + AccountPrefix string `json:"account-prefix" yaml:"account-prefix"` + KeyringBackend string `json:"keyring-backend" yaml:"keyring-backend"` + KeyDirectory string `json:"key-directory" yaml:"key-directory"` + GasPrices string `json:"gas-prices" yaml:"gas-prices"` + GasAdjustment float64 `json:"gas-adjustment" yaml:"gas-adjustment"` + Debug bool `json:"debug" yaml:"debug"` + Timeout string `json:"timeout" yaml:"timeout"` + OutputFormat string `json:"output-format" yaml:"output-format"` + SignModeStr string `json:"sign-mode" yaml:"sign-mode"` + Network uint16 `json:"network" yaml:"network"` + ParaID uint32 `json:"para-id" yaml:"para-id"` + BeefyActivationBlock uint32 `json:"beefy-activation-block" yaml:"beefy-activation-block"` } func (spc SubstrateProviderConfig) Validate() error { @@ -62,12 +69,21 @@ func (spc SubstrateProviderConfig) NewProvider(log *zap.Logger, homepath string, spc.KeyDirectory = keysDir(homepath, spc.ChainID) } + memdb, err := chaindb.NewBadgerDB(&chaindb.Config{ + InMemory: true, + DataDir: homepath, + }) + if err != nil { + return nil, err + } + sp := &SubstrateProvider{ - log: log, - PCfg: spc, + log: log, + Config: &spc, + Memdb: memdb, } - err := sp.Init() + err = sp.Init() if err != nil { return nil, err } @@ -76,7 +92,7 @@ func (spc SubstrateProviderConfig) NewProvider(log *zap.Logger, homepath string, } func (sp *SubstrateProvider) Init() error { - keybase, err := keystore.New(sp.PCfg.ChainID, sp.PCfg.KeyringBackend, sp.PCfg.KeyDirectory, sp.Input) + keybase, err := keystore.New(sp.Config.ChainID, sp.Config.KeyringBackend, sp.Config.KeyDirectory, sp.Input) if err != nil { return err } @@ -86,11 +102,14 @@ func (sp *SubstrateProvider) Init() error { } type SubstrateProvider struct { - log *zap.Logger - Keybase keystore.Keyring - RPCClient *rpcclient.SubstrateAPI - PCfg SubstrateProviderConfig - Input io.Reader + log *zap.Logger + Config *SubstrateProviderConfig + Keybase keystore.Keyring + Memdb *chaindb.BadgerDB + Input io.Reader + + RPCClient *rpcclient.SubstrateAPI + RelayerRPCClient *rpcclient.SubstrateAPI } type SubstrateIBCHeader struct { @@ -132,11 +151,6 @@ func (sp *SubstrateProvider) QueryLatestHeight(ctx context.Context) (int64, erro panic("implement me") } -func (sp *SubstrateProvider) QueryIBCHeader(ctx context.Context, h int64) (provider.IBCHeader, error) { - //TODO implement me - panic("implement me") -} - func (sp *SubstrateProvider) QuerySendPacket(ctx context.Context, srcChanID, srcPortID string, sequence uint64) (provider.PacketInfo, error) { //TODO implement me panic("implement me") @@ -287,166 +301,6 @@ func (sp *SubstrateProvider) QueryDenomTraces(ctx context.Context, offset, limit panic("implement me") } -func (sp *SubstrateProvider) NewClientState(dstChainID string, dstIBCHeader provider.IBCHeader, dstTrustingPeriod, dstUbdPeriod time.Duration, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour bool) (ibcexported.ClientState, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgCreateClient(clientState ibcexported.ClientState, consensusState ibcexported.ConsensusState) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgUpgradeClient(srcClientId string, consRes *clienttypes.QueryConsensusStateResponse, clientRes *clienttypes.QueryClientStateResponse) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) ValidatePacket(msgTransfer provider.PacketInfo, latestBlock provider.LatestBlock) error { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) PacketCommitment(ctx context.Context, msgTransfer provider.PacketInfo, height uint64) (provider.PacketProof, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) PacketAcknowledgement(ctx context.Context, msgRecvPacket provider.PacketInfo, height uint64) (provider.PacketProof, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) PacketReceipt(ctx context.Context, msgTransfer provider.PacketInfo, height uint64) (provider.PacketProof, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) NextSeqRecv(ctx context.Context, msgTransfer provider.PacketInfo, height uint64) (provider.PacketProof, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgTransfer(dstAddr string, amount sdk.Coin, info provider.PacketInfo) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgRecvPacket(msgTransfer provider.PacketInfo, proof provider.PacketProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgAcknowledgement(msgRecvPacket provider.PacketInfo, proofAcked provider.PacketProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgTimeout(msgTransfer provider.PacketInfo, proofUnreceived provider.PacketProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgTimeoutOnClose(msgTransfer provider.PacketInfo, proofUnreceived provider.PacketProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) ConnectionHandshakeProof(ctx context.Context, msgOpenInit provider.ConnectionInfo, height uint64) (provider.ConnectionProof, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) ConnectionProof(ctx context.Context, msgOpenAck provider.ConnectionInfo, height uint64) (provider.ConnectionProof, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgConnectionOpenInit(info provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgConnectionOpenTry(msgOpenInit provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgConnectionOpenAck(msgOpenTry provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgConnectionOpenConfirm(msgOpenAck provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) ChannelProof(ctx context.Context, msg provider.ChannelInfo, height uint64) (provider.ChannelProof, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgChannelOpenInit(info provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgChannelOpenTry(msgOpenInit provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgChannelOpenAck(msgOpenTry provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgChannelOpenConfirm(msgOpenAck provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgChannelCloseInit(info provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgChannelCloseConfirm(msgCloseInit provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgUpdateClientHeader(latestHeader provider.IBCHeader, trustedHeight clienttypes.Height, trustedHeader provider.IBCHeader) (ibcexported.Header, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) MsgUpdateClient(clientId string, counterpartyHeader ibcexported.Header) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) RelayPacketFromSequence(ctx context.Context, src provider.ChainProvider, srch, dsth, seq uint64, srcChanID, srcPortID string, order chantypes.Order) (provider.RelayerMessage, provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) AcknowledgementFromSequence(ctx context.Context, dst provider.ChainProvider, dsth, seq uint64, dstChanID, dstPortID, srcChanID, srcPortID string) (provider.RelayerMessage, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) SendMessage(ctx context.Context, msg provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) { - //TODO implement me - panic("implement me") -} - -func (sp *SubstrateProvider) SendMessages(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) { - //TODO implement me - panic("implement me") -} - func (sp *SubstrateProvider) ChainName() string { //TODO implement me panic("implement me") diff --git a/relayer/chains/substrate/substrate_chain_processor.go b/relayer/chains/substrate/substrate_chain_processor.go index 744186d6a..1c9a924bc 100644 --- a/relayer/chains/substrate/substrate_chain_processor.go +++ b/relayer/chains/substrate/substrate_chain_processor.go @@ -72,14 +72,14 @@ const ( type latestClientState map[string]provider.ClientState func (l latestClientState) update(clientInfo clientInfo) { - existingClientInfo, ok := l[clientInfo.ClientID] - if ok && clientInfo.ConsensusHeight.LT(existingClientInfo.ConsensusHeight) { + existingClientInfo, ok := l[clientInfo.clientID] + if ok && clientInfo.consensusHeight.LT(existingClientInfo.ConsensusHeight) { // height is less than latest, so no-op return } // update latest if no existing state or provided consensus height is newer - l[clientInfo.ClientID] = clientInfo.ClientState() + l[clientInfo.clientID] = clientInfo.ClientState() } // Provider returns the ChainProvider, which provides the methods for querying, assembling IBC messages, and sending transactions. diff --git a/relayer/chains/substrate/tx.go b/relayer/chains/substrate/tx.go new file mode 100644 index 000000000..90e0d83dd --- /dev/null +++ b/relayer/chains/substrate/tx.go @@ -0,0 +1,1031 @@ +package substrate + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/ChainSafe/gossamer/lib/common" + rpcclienttypes "github.com/ComposableFi/go-substrate-rpc-client/v4/types" + beefyclienttypes "github.com/ComposableFi/ics11-beefy/types" + "github.com/avast/retry-go/v4" + sdk "github.com/cosmos/cosmos-sdk/types" + transfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + conntypes "github.com/cosmos/ibc-go/v5/modules/core/03-connection/types" + chantypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + ibcexported "github.com/cosmos/ibc-go/v5/modules/core/exported" + "github.com/cosmos/relayer/v2/relayer/provider" + "github.com/gogo/protobuf/proto" +) + +// Variables used for retries +var ( + rtyAttNum = uint(5) + rtyAtt = retry.Attempts(rtyAttNum) + rtyDel = retry.Delay(time.Millisecond * 400) + rtyErr = retry.LastErrorOnly(true) +) + +// Default IBC settings +var ( + defaultChainPrefix = commitmenttypes.NewMerklePrefix([]byte("ibc")) + defaultDelayPeriod = uint64(0) +) + +const ( + callIbcDeliver = "Ibc.deliver" + callIbcDeliverPerm = "Ibc.deliver_permissioned" + callSudo = "Sudo.sudo" +) + +// storage key prefix and methods +const ( + prefixSystem = "System" + methodAccount = "Account" +) + +type Any struct { + TypeUrl []byte `json:"type_url,omitempty"` + Value []byte `json:"value,omitempty"` +} + +// SendMessage attempts to sign, encode & send a RelayerMessage +// This is used extensively in the relayer as an extension of the Provider interface +func (sp *SubstrateProvider) SendMessage(ctx context.Context, msg provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) { + return sp.SendMessages(ctx, []provider.RelayerMessage{msg}, memo) +} + +// SendMessages attempts to sign, encode, & send a slice of RelayerMessages +// This is used extensively in the relayer as an extension of the Provider interface +// +// NOTE: An error is returned if there was an issue sending the transaction. A successfully sent, but failed +// transaction will not return an error. If a transaction is successfully sent, the result of the execution +// of that transaction will be logged. A boolean indicating if a transaction was successfully +// sent and executed successfully is returned. +func (sp *SubstrateProvider) SendMessages(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) { + meta, err := sp.RPCClient.RPC.State.GetMetadataLatest() + if err != nil { + return nil, false, err + } + + call, anyMsgs, err := sp.buildCallParams(msgs) + if err != nil { + return nil, false, err + } + + c, err := rpcclienttypes.NewCall(meta, call, anyMsgs) + if err != nil { + return nil, false, err + } + + sc, err := rpcclienttypes.NewCall(meta, callSudo, c) + if err != nil { + return nil, false, err + } + + // Create the extrinsic + ext := rpcclienttypes.NewExtrinsic(sc) + + genesisHash, err := sp.RPCClient.RPC.Chain.GetBlockHash(0) + if err != nil { + return nil, false, err + } + + rv, err := sp.RPCClient.RPC.State.GetRuntimeVersionLatest() + if err != nil { + return nil, false, err + } + + info, err := sp.Keybase.Key(sp.Key()) + if err != nil { + return nil, false, err + } + + key, err := rpcclienttypes.CreateStorageKey(meta, prefixSystem, methodAccount, info.GetPublicKey(), nil) + if err != nil { + return nil, false, err + } + + var accountInfo rpcclienttypes.AccountInfo + ok, err := sp.RPCClient.RPC.State.GetStorageLatest(key, &accountInfo) + if err != nil || !ok { + return nil, false, err + } + + nonce := uint32(accountInfo.Nonce) + + o := rpcclienttypes.SignatureOptions{ + BlockHash: genesisHash, + Era: rpcclienttypes.ExtrinsicEra{IsMortalEra: false}, + GenesisHash: genesisHash, + Nonce: rpcclienttypes.NewUCompactFromUInt(uint64(nonce)), + SpecVersion: rv.SpecVersion, + Tip: rpcclienttypes.NewUCompactFromUInt(0), + TransactionVersion: rv.TransactionVersion, + } + + err = ext.Sign(info.GetKeyringPair(), o) + if err != nil { + return nil, false, err + } + + // Send the extrinsic + sub, err := sp.RPCClient.RPC.Author.SubmitAndWatchExtrinsic(ext) + if err != nil { + return nil, false, err + } + + var status rpcclienttypes.ExtrinsicStatus + defer sub.Unsubscribe() + for { + status = <-sub.Chan() + // TODO: add zap log for waiting on transaction + if status.IsInBlock { + break + } + } + + encodedExt, err := rpcclienttypes.Encode(ext) + if err != nil { + return nil, false, err + } + + var extHash [32]byte + extHash, err = common.Blake2bHash(encodedExt) + if err != nil { + return nil, false, err + } + + events, err := sp.fetchAndBuildEvents(ctx, status.AsInBlock, extHash) + if err != nil { + return nil, false, err + } + + rlyRes := &provider.RelayerTxResponse{ + // TODO: pass in a proper block height + TxHash: fmt.Sprintf("0x%x", extHash[:]), + Code: 0, // if the process is reached this line, so there is no error + Events: events, + } + + return rlyRes, true, nil +} + +// MsgCreateClient creates an sdk.Msg to update the client on src with consensus state from dst +func (sp *SubstrateProvider) MsgCreateClient( + clientState ibcexported.ClientState, + consensusState ibcexported.ConsensusState, +) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + + anyClientState, err := clienttypes.PackClientState(clientState) + if err != nil { + return nil, err + } + + anyConsensusState, err := clienttypes.PackConsensusState(consensusState) + if err != nil { + return nil, err + } + + msg := &clienttypes.MsgCreateClient{ + ClientState: anyClientState, + ConsensusState: anyConsensusState, + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// MsgUpdateClient constructs update client message into substrate message +func (sp *SubstrateProvider) MsgUpdateClient(srcClientId string, dstHeader ibcexported.Header) (provider.RelayerMessage, error) { + acc, err := sp.Address() + if err != nil { + return nil, err + } + + anyHeader, err := clienttypes.PackHeader(dstHeader) + if err != nil { + return nil, err + } + + msg := &clienttypes.MsgUpdateClient{ + ClientId: srcClientId, + Header: anyHeader, + Signer: acc, + } + + return NewSubstrateMessage(msg), nil +} + +// MsgUpgradeClient constructs upgrade client message into substrate message +func (sp *SubstrateProvider) MsgUpgradeClient(srcClientId string, consRes *clienttypes.QueryConsensusStateResponse, clientRes *clienttypes.QueryClientStateResponse) (provider.RelayerMessage, error) { + var ( + acc string + err error + ) + if acc, err = sp.Address(); err != nil { + return nil, err + } + return NewSubstrateMessage(&clienttypes.MsgUpgradeClient{ClientId: srcClientId, ClientState: clientRes.ClientState, + ConsensusState: consRes.ConsensusState, ProofUpgradeClient: consRes.GetProof(), + ProofUpgradeConsensusState: consRes.ConsensusState.Value, Signer: acc}), nil +} + +// MsgTransfer creates a new transfer message +func (sp *SubstrateProvider) MsgTransfer( + dstAddr string, + amount sdk.Coin, + info provider.PacketInfo, +) (provider.RelayerMessage, error) { + acc, err := sp.Address() + if err != nil { + return nil, err + } + msg := &transfertypes.MsgTransfer{ + SourcePort: info.SourcePort, + SourceChannel: info.SourceChannel, + Token: amount, + Sender: acc, + Receiver: dstAddr, + TimeoutTimestamp: info.TimeoutTimestamp, + } + + // If the timeoutHeight is 0 then we don't need to explicitly set it on the MsgTransfer + if info.TimeoutHeight.RevisionHeight != 0 { + msg.TimeoutHeight = info.TimeoutHeight + } + + return NewSubstrateMessage(msg), nil +} + +// ValidatePacket validates transfer message +func (sp *SubstrateProvider) ValidatePacket(msgTransfer provider.PacketInfo, latest provider.LatestBlock) error { + if msgTransfer.Sequence == 0 { + return errors.New("refusing to relay packet with sequence: 0") + } + + if len(msgTransfer.Data) == 0 { + return errors.New("refusing to relay packet with empty data") + } + + // This should not be possible, as it violates IBC spec + if msgTransfer.TimeoutHeight.IsZero() && msgTransfer.TimeoutTimestamp == 0 { + return errors.New("refusing to relay packet without a timeout (height or timestamp must be set)") + } + + revision := clienttypes.ParseChainID(sp.Config.ChainID) + latestClientTypesHeight := clienttypes.NewHeight(revision, latest.Height) + if !msgTransfer.TimeoutHeight.IsZero() && latestClientTypesHeight.GTE(msgTransfer.TimeoutHeight) { + return provider.NewTimeoutHeightError(latest.Height, msgTransfer.TimeoutHeight.RevisionHeight) + } + latestTimestamp := uint64(latest.Time.UnixNano()) + if msgTransfer.TimeoutTimestamp > 0 && latestTimestamp > msgTransfer.TimeoutTimestamp { + return provider.NewTimeoutTimestampError(latestTimestamp, msgTransfer.TimeoutTimestamp) + } + + return nil +} + +// PacketCommitment constructs packet proof from packet at certain height +func (sp *SubstrateProvider) PacketCommitment( + ctx context.Context, + msgTransfer provider.PacketInfo, + height uint64, +) (provider.PacketProof, error) { + comRes, err := sp.RPCClient.RPC.IBC.QueryPacketCommitment(ctx, int64(height), msgTransfer.SourceChannel, msgTransfer.SourcePort) + if err != nil { + return provider.PacketProof{}, err + } + // check if packet commitment exists + if len(comRes.Commitment) == 0 { + return provider.PacketProof{}, chantypes.ErrPacketCommitmentNotFound + } + + return provider.PacketProof{ + Proof: comRes.Proof, + ProofHeight: comRes.ProofHeight, + }, nil +} + +// MsgRecvPacket constructs receive packet message +func (sp *SubstrateProvider) MsgRecvPacket( + msgTransfer provider.PacketInfo, + proof provider.PacketProof, +) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + msg := &chantypes.MsgRecvPacket{ + Packet: msgTransfer.Packet(), + ProofCommitment: proof.Proof, + ProofHeight: proof.ProofHeight, + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// PacketAcknowledgement constructs packet proof from receive packet +func (sp *SubstrateProvider) PacketAcknowledgement( + ctx context.Context, + msgRecvPacket provider.PacketInfo, + height uint64, +) (provider.PacketProof, error) { + ackRes, err := sp.RPCClient.RPC.IBC.QueryPacketAcknowledgement(ctx, uint32(height), msgRecvPacket.DestChannel, msgRecvPacket.DestPort, msgRecvPacket.Sequence) + if err != nil { + return provider.PacketProof{}, fmt.Errorf("error querying beefy proof for packet acknowledgement: %w", err) + } + if len(ackRes.Acknowledgement) == 0 { + return provider.PacketProof{}, chantypes.ErrInvalidAcknowledgement + } + return provider.PacketProof{ + Proof: ackRes.Proof, + ProofHeight: ackRes.ProofHeight, + }, nil +} + +// MsgAcknowledgement constructs ack message +func (sp *SubstrateProvider) MsgAcknowledgement( + msgRecvPacket provider.PacketInfo, + proof provider.PacketProof, +) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + msg := &chantypes.MsgAcknowledgement{ + Packet: msgRecvPacket.Packet(), + Acknowledgement: msgRecvPacket.Ack, + ProofAcked: proof.Proof, + ProofHeight: proof.ProofHeight, + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// PacketReceipt returns packet proof of the receipt +func (sp *SubstrateProvider) PacketReceipt( + ctx context.Context, + msgTransfer provider.PacketInfo, + height uint64, +) (provider.PacketProof, error) { + recRes, err := sp.RPCClient.RPC.IBC.QueryPacketReceipt(ctx, uint32(height), msgTransfer.DestChannel, msgTransfer.DestPort, msgTransfer.Sequence) + if err != nil { + return provider.PacketProof{}, err + } + return provider.PacketProof{ + Proof: recRes.Proof, + ProofHeight: recRes.ProofHeight, + }, nil +} + +// NextSeqRecv queries for the appropriate beefy proof required to prove the next expected packet sequence number +// for a given counterparty channel. This is used in ORDERED channels to ensure packets are being delivered in the +// exact same order as they were sent over the wire. +func (sp *SubstrateProvider) NextSeqRecv( + ctx context.Context, + msgTransfer provider.PacketInfo, + height uint64, +) (provider.PacketProof, error) { + recvRes, err := sp.RPCClient.RPC.IBC.QueryNextSeqRecv(ctx, uint32(height), msgTransfer.DestChannel, msgTransfer.DestPort) + if err != nil { + return provider.PacketProof{}, err + } + return provider.PacketProof{ + Proof: recvRes.Proof, + ProofHeight: recvRes.ProofHeight, + }, nil +} + +// MsgTimeout constructs timeout message from packet +func (sp *SubstrateProvider) MsgTimeout(msgTransfer provider.PacketInfo, proof provider.PacketProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + assembled := &chantypes.MsgTimeout{ + Packet: msgTransfer.Packet(), + ProofUnreceived: proof.Proof, + ProofHeight: proof.ProofHeight, + NextSequenceRecv: msgTransfer.Sequence, + Signer: signer, + } + + return NewSubstrateMessage(assembled), nil +} + +// MsgTimeoutOnClose constructs message from packet and the proof +func (sp *SubstrateProvider) MsgTimeoutOnClose(msgTransfer provider.PacketInfo, proof provider.PacketProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + assembled := &chantypes.MsgTimeoutOnClose{ + Packet: msgTransfer.Packet(), + ProofUnreceived: proof.Proof, + ProofHeight: proof.ProofHeight, + NextSequenceRecv: msgTransfer.Sequence, + Signer: signer, + } + + return NewSubstrateMessage(assembled), nil +} + +// MsgConnectionOpenInit constructs connection open init message drom proof and connection info +func (sp *SubstrateProvider) MsgConnectionOpenInit(info provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + msg := &conntypes.MsgConnectionOpenInit{ + ClientId: info.ClientID, + Counterparty: conntypes.Counterparty{ + ClientId: info.CounterpartyClientID, + ConnectionId: "", + Prefix: defaultChainPrefix, + }, + Version: nil, + DelayPeriod: defaultDelayPeriod, + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// ConnectionHandshakeProof returns connection proof from consensus state and proof +func (sp *SubstrateProvider) ConnectionHandshakeProof( + ctx context.Context, + msgOpenInit provider.ConnectionInfo, + height uint64, +) (provider.ConnectionProof, error) { + clientState, clientStateProof, consensusStateProof, connStateProof, proofHeight, err := sp.GenerateConnHandshakeProof(ctx, int64(height), msgOpenInit.ClientID, msgOpenInit.ConnID) + if err != nil { + return provider.ConnectionProof{}, err + } + + if len(connStateProof) == 0 { + // It is possible that we have asked for a proof too early. + // If the connection state proof is empty, there is no point in returning the next message. + // We are not using (*conntypes.MsgConnectionOpenTry).ValidateBasic here because + // that chokes on cross-chain bech32 details in ibc-go. + return provider.ConnectionProof{}, fmt.Errorf("received invalid zero-length connection state proof") + } + + return provider.ConnectionProof{ + ClientState: clientState, + ClientStateProof: clientStateProof, + ConsensusStateProof: consensusStateProof, + ConnectionStateProof: connStateProof, + ProofHeight: proofHeight.(clienttypes.Height), + }, nil +} + +// MsgConnectionOpenTry constructs connection open try from connection info and proof +func (sp *SubstrateProvider) MsgConnectionOpenTry(msgOpenInit provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + + csAny, err := clienttypes.PackClientState(proof.ClientState) + if err != nil { + return nil, err + } + + counterparty := conntypes.Counterparty{ + ClientId: msgOpenInit.ClientID, + ConnectionId: msgOpenInit.ConnID, + Prefix: defaultChainPrefix, + } + + msg := &conntypes.MsgConnectionOpenTry{ + ClientId: msgOpenInit.CounterpartyClientID, + PreviousConnectionId: msgOpenInit.CounterpartyConnID, + ClientState: csAny, + Counterparty: counterparty, + DelayPeriod: defaultDelayPeriod, + CounterpartyVersions: conntypes.ExportedVersionsToProto(conntypes.GetCompatibleVersions()), + ProofHeight: proof.ProofHeight, + ProofInit: proof.ConnectionStateProof, + ProofClient: proof.ClientStateProof, + ProofConsensus: proof.ConsensusStateProof, + ConsensusHeight: proof.ClientState.GetLatestHeight().(clienttypes.Height), + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// MsgConnectionOpenAck constructs connection open ack message from connection info and proof +func (sp *SubstrateProvider) MsgConnectionOpenAck(msgOpenTry provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + + csAny, err := clienttypes.PackClientState(proof.ClientState) + if err != nil { + return nil, err + } + + msg := &conntypes.MsgConnectionOpenAck{ + ConnectionId: msgOpenTry.CounterpartyConnID, + CounterpartyConnectionId: msgOpenTry.ConnID, + Version: conntypes.DefaultIBCVersion, + ClientState: csAny, + ProofHeight: clienttypes.Height{ + RevisionNumber: proof.ProofHeight.GetRevisionNumber(), + RevisionHeight: proof.ProofHeight.GetRevisionHeight(), + }, + ProofTry: proof.ConnectionStateProof, + ProofClient: proof.ClientStateProof, + ProofConsensus: proof.ConsensusStateProof, + ConsensusHeight: proof.ClientState.GetLatestHeight().(clienttypes.Height), + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// ConnectionProof returns connection proof from connection state +func (sp *SubstrateProvider) ConnectionProof( + ctx context.Context, + msgOpenAck provider.ConnectionInfo, + height uint64, +) (provider.ConnectionProof, error) { + connState, err := sp.QueryConnection(ctx, int64(height), msgOpenAck.ConnID) + if err != nil { + return provider.ConnectionProof{}, err + } + + return provider.ConnectionProof{ + ConnectionStateProof: connState.Proof, + ProofHeight: connState.ProofHeight, + }, nil +} + +// MsgConnectionOpenConfirm constructs connection open confirm from open ack message and connection proof +func (sp *SubstrateProvider) MsgConnectionOpenConfirm(msgOpenAck provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + msg := &conntypes.MsgConnectionOpenConfirm{ + ConnectionId: msgOpenAck.CounterpartyConnID, + ProofAck: proof.ConnectionStateProof, + ProofHeight: proof.ProofHeight, + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// MsgChannelOpenInit constructs channel open init message from provider info and proof +func (sp *SubstrateProvider) MsgChannelOpenInit(info provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + msg := &chantypes.MsgChannelOpenInit{ + PortId: info.PortID, + Channel: chantypes.Channel{ + State: chantypes.INIT, + Ordering: info.Order, + Counterparty: chantypes.Counterparty{ + PortId: info.CounterpartyPortID, + ChannelId: "", + }, + ConnectionHops: []string{info.ConnID}, + Version: info.Version, + }, + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// ChannelProof returns proof of channel from channel info +func (sp *SubstrateProvider) ChannelProof( + ctx context.Context, + msg provider.ChannelInfo, + height uint64, +) (provider.ChannelProof, error) { + channelRes, err := sp.QueryChannel(ctx, int64(height), msg.ChannelID, msg.PortID) + if err != nil { + return provider.ChannelProof{}, err + } + return provider.ChannelProof{ + Proof: channelRes.Proof, + ProofHeight: channelRes.ProofHeight, + Version: channelRes.Channel.Version, + Ordering: channelRes.Channel.Ordering, + }, nil +} + +// MsgChannelOpenTry constructs channel open try from channel info and proof +func (sp *SubstrateProvider) MsgChannelOpenTry(msgOpenInit provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + msg := &chantypes.MsgChannelOpenTry{ + PortId: msgOpenInit.CounterpartyPortID, + PreviousChannelId: msgOpenInit.CounterpartyChannelID, + Channel: chantypes.Channel{ + State: chantypes.TRYOPEN, + Ordering: proof.Ordering, + Counterparty: chantypes.Counterparty{ + PortId: msgOpenInit.PortID, + ChannelId: msgOpenInit.ChannelID, + }, + ConnectionHops: []string{msgOpenInit.CounterpartyConnID}, + // In the future, may need to separate this from the CounterpartyVersion. + // https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#definitions + // Using same version as counterparty for now. + Version: proof.Version, + }, + CounterpartyVersion: proof.Version, + ProofInit: proof.Proof, + ProofHeight: proof.ProofHeight, + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// MsgChannelOpenAck constructs channel open acknowledgement from channel info and proof +func (sp *SubstrateProvider) MsgChannelOpenAck(msgOpenTry provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + msg := &chantypes.MsgChannelOpenAck{ + PortId: msgOpenTry.CounterpartyPortID, + ChannelId: msgOpenTry.CounterpartyChannelID, + CounterpartyChannelId: msgOpenTry.ChannelID, + CounterpartyVersion: proof.Version, + ProofTry: proof.Proof, + ProofHeight: proof.ProofHeight, + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// MsgChannelOpenConfirm constructs channel open confirm message from channel info and proof +func (sp *SubstrateProvider) MsgChannelOpenConfirm(msgOpenAck provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + msg := &chantypes.MsgChannelOpenConfirm{ + PortId: msgOpenAck.CounterpartyPortID, + ChannelId: msgOpenAck.CounterpartyChannelID, + ProofAck: proof.Proof, + ProofHeight: proof.ProofHeight, + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// MsgChannelCloseInit constructs message channel close initialization message drom info and proof +func (sp *SubstrateProvider) MsgChannelCloseInit(info provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + msg := &chantypes.MsgChannelCloseInit{ + PortId: info.PortID, + ChannelId: info.ChannelID, + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// MsgChannelCloseConfirm constructs channel close confirmation message form channel info and proof +func (sp *SubstrateProvider) MsgChannelCloseConfirm(msgCloseInit provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) { + signer, err := sp.Address() + if err != nil { + return nil, err + } + msg := &chantypes.MsgChannelCloseConfirm{ + PortId: msgCloseInit.CounterpartyPortID, + ChannelId: msgCloseInit.CounterpartyChannelID, + ProofInit: proof.Proof, + ProofHeight: proof.ProofHeight, + Signer: signer, + } + + return NewSubstrateMessage(msg), nil +} + +// MsgUpdateClientHeader constructs update client header message from ibc header and trusted header and height +func (sp *SubstrateProvider) MsgUpdateClientHeader(latestHeader provider.IBCHeader, trustedHeight clienttypes.Height, trustedHeader provider.IBCHeader) (ibcexported.Header, error) { + _, ok := trustedHeader.(SubstrateIBCHeader) + if !ok { + return nil, fmt.Errorf("unsupported IBC trusted header type, expected: SubstrateIBCHeader, actual: %T", trustedHeader) + } + + latestSubstrateHeader, ok := latestHeader.(SubstrateIBCHeader) + if !ok { + return nil, fmt.Errorf("unsupported IBC header type, expected: SubstrateIBCHeader, actual: %T", latestHeader) + } + + return &beefyclienttypes.Header{ + HeadersWithProof: latestSubstrateHeader.SignedHeader.HeadersWithProof, + MMRUpdateProof: latestSubstrateHeader.SignedHeader.MMRUpdateProof, + }, nil +} + +// RelayPacketFromSequence relays a packet with a given seq on src and returns recvPacket msgs, timeoutPacketmsgs and error +func (sp *SubstrateProvider) RelayPacketFromSequence( + ctx context.Context, + src provider.ChainProvider, + srch, dsth, seq uint64, + srcChanID, srcPortID string, + order chantypes.Order, +) (provider.RelayerMessage, provider.RelayerMessage, error) { + msgTransfer, err := src.QuerySendPacket(ctx, srcChanID, srcPortID, seq) + if err != nil { + return nil, nil, err + } + + dstTime, err := sp.BlockTime(ctx, int64(dsth)) + if err != nil { + return nil, nil, err + } + + if err := sp.ValidatePacket(msgTransfer, provider.LatestBlock{ + Height: dsth, + Time: dstTime, + }); err != nil { + switch err.(type) { + case *provider.TimeoutHeightError, *provider.TimeoutTimestampError, *provider.TimeoutOnCloseError: + var pp provider.PacketProof + switch order { + case chantypes.UNORDERED: + pp, err = sp.PacketReceipt(ctx, msgTransfer, dsth) + if err != nil { + return nil, nil, err + } + case chantypes.ORDERED: + pp, err = sp.NextSeqRecv(ctx, msgTransfer, dsth) + if err != nil { + return nil, nil, err + } + } + if _, ok := err.(*provider.TimeoutOnCloseError); ok { + timeout, err := src.MsgTimeoutOnClose(msgTransfer, pp) + if err != nil { + return nil, nil, err + } + return nil, timeout, nil + } else { + timeout, err := src.MsgTimeout(msgTransfer, pp) + if err != nil { + return nil, nil, err + } + return nil, timeout, nil + } + default: + return nil, nil, err + } + } + + pp, err := src.PacketCommitment(ctx, msgTransfer, srch) + if err != nil { + return nil, nil, err + } + + packet, err := sp.MsgRecvPacket(msgTransfer, pp) + if err != nil { + return nil, nil, err + } + + return packet, nil, nil +} + +// AcknowledgementFromSequence relays an acknowledgement with a given seq on src, source is the sending chain, destination is the receiving chain +func (sp *SubstrateProvider) AcknowledgementFromSequence(ctx context.Context, dst provider.ChainProvider, dsth, seq uint64, dstChanId, dstPortId, srcChanId, srcPortId string) (provider.RelayerMessage, error) { + msgRecvPacket, err := dst.QueryRecvPacket(ctx, dstChanId, dstPortId, seq) + if err != nil { + return nil, err + } + + pp, err := dst.PacketAcknowledgement(ctx, msgRecvPacket, dsth) + if err != nil { + return nil, err + } + msg, err := sp.MsgAcknowledgement(msgRecvPacket, pp) + if err != nil { + return nil, err + } + return msg, nil +} + +// QueryIBCHeader returns the IBC compatible block header (SubstrateIBCHeader) at a specific height. +func (sp *SubstrateProvider) QueryIBCHeader(ctx context.Context, h int64) (provider.IBCHeader, error) { + if h <= 0 { + return nil, fmt.Errorf("must pass in valid height, %d not valid", h) + } + + latestBeefyBlockHash, err := sp.RPCClient.RPC.Beefy.GetFinalizedHead() + if err != nil { + return nil, err + } + + latestBeefyHeight, err := sp.RPCClient.RPC.Chain.GetBlock(latestBeefyBlockHash) + if err != nil { + return nil, err + } + + if h > int64(latestBeefyHeight.Block.Header.Number) { + return nil, fmt.Errorf("queried block is not finalized") + } + + blockHash, err := sp.RPCClient.RPC.Chain.GetBlockHash(uint64(h)) + if err != nil { + return nil, err + } + + header, err := sp.constructBeefyHeader(latestBeefyBlockHash, &blockHash) + if err != nil { + return nil, err + } + + return SubstrateIBCHeader{ + height: uint64(h), + SignedHeader: header, + }, nil +} + +// InjectTrustedFields injects the necessary trusted fields for a header to update a light +// client stored on the destination chain, using the information provided by the source +// chain. +// TrustedHeight is the latest height of the IBC client on dst +// TrustedValidators is the validator set of srcChain at the TrustedHeight +// InjectTrustedFields returns a copy of the header with TrustedFields modified +func (sp *SubstrateProvider) InjectTrustedFields(ctx context.Context, header ibcexported.Header, dst provider.ChainProvider, dstClientId string) (ibcexported.Header, error) { + // make copy of header stored in mop + h, ok := header.(*beefyclienttypes.Header) + if !ok { + return nil, fmt.Errorf("trying to inject fields into non-beefy headers") + } + + // retrieve dst client from src chain + // this is the client that will be updated + cs, err := dst.QueryClientState(ctx, int64(h.GetHeight().GetRevisionHeight()), dstClientId) + if err != nil { + return nil, err + } + + // inject TrustedHeight as latest height stored on dst client + h.MMRUpdateProof.SignedCommitment.Commitment.BlockNumber = uint32(cs.GetLatestHeight().(clienttypes.Height).RevisionHeight) + + // NOTE: We need to get validators from the source chain at height: trustedHeight+1 + // since the last trusted validators for a header at height h is the NextValidators + // at h+1 committed to in header h by NextValidatorsHash + + // place where we need to fix the upstream query proof issue? + var trustedValidatorSetID uint64 + if err := retry.Do(func() error { + ibcHeader, err := sp.QueryIBCHeader(ctx, int64(h.MMRUpdateProof.SignedCommitment.Commitment.BlockNumber+1)) + if err != nil { + return err + } + + trustedValidatorSetID = ibcHeader.(SubstrateIBCHeader).SignedHeader.MMRUpdateProof.SignedCommitment.Commitment.ValidatorSetID + return err + }, retry.Context(ctx), rtyAtt, rtyDel, rtyErr); err != nil { + return nil, fmt.Errorf( + "failed to get trusted header, please ensure header at the height %d has not been pruned by the connected node: %w", + h.MMRUpdateProof.SignedCommitment.Commitment.BlockNumber, err, + ) + } + + h.MMRUpdateProof.SignedCommitment.Commitment.ValidatorSetID = trustedValidatorSetID + + return h, nil +} + +// NewClientState creates a new beefy client state tracking the dst chain. +func (sp *SubstrateProvider) NewClientState( + dstChainID string, + dstUpdateHeader provider.IBCHeader, + dstTrustingPeriod, + dstUbdPeriod time.Duration, + allowUpdateAfterExpiry, + allowUpdateAfterMisbehaviour bool, +) (ibcexported.ClientState, error) { + substrateHeader, ok := dstUpdateHeader.(SubstrateIBCHeader) + if !ok { + return nil, fmt.Errorf("got data of type %T but wanted substrate.SubstrateIBCHeader \n", dstUpdateHeader) + } + + // TODO: this won't work because we need the height passed to GetBlockHash to be the previously finalized beefy height + // from the relayer. However, the height from substrate.Height() is the height of the first parachain from the beefy header. + blockHash, err := sp.RelayerRPCClient.RPC.Chain.GetBlockHash(substrateHeader.Height()) + if err != nil { + return nil, err + } + + commitment, err := sp.signedCommitment(blockHash) + if err != nil { + return nil, err + } + + cs, err := sp.clientState(commitment) + if err != nil { + return nil, err + } + + return cs, nil +} + +// returns call method and messages of relayer +func (sp *SubstrateProvider) buildCallParams(msgs []provider.RelayerMessage) (call string, anyMsgs []Any, err error) { + var msgTypeCall = func(msgType string) string { + switch msgType { + case "/" + string(proto.MessageName(&clienttypes.MsgCreateClient{})): + return callIbcDeliverPerm + default: + return callIbcDeliver + } + } + + call = msgTypeCall(msgs[0].Type()) + for i := 0; i < len(msgs); i++ { + msg := msgs[i] + + if call != msgTypeCall(msg.Type()) { + return "", nil, + fmt.Errorf("%s: %s", ErrDifferentTypesOfCallsMixed, msg) + } + + msgBytes, err := msg.MsgBytes() + if err != nil { + return "", nil, err + } + + anyMsgs = append(anyMsgs, Any{ + TypeUrl: []byte(msg.Type()), + Value: msgBytes, + }) + } + + return +} + +func (sp *SubstrateProvider) fetchAndBuildEvents( + ctx context.Context, + blockHash rpcclienttypes.Hash, + extHash rpcclienttypes.Hash, +) (relayerEvents []provider.RelayerEvent, err error) { + + blockHashHex := blockHash.Hex() + eventResult, err := sp.RPCClient.RPC.IBC.QueryIbcEvents(ctx, []rpcclienttypes.BlockNumberOrHash{{Hash: &blockHashHex}}) + if err != nil { + return nil, err + } + + events := parseRelayEventsFromEvents(eventResult) + return events, nil +} + +func parseRelayEventsFromEvents(eventResult rpcclienttypes.IBCEventsQueryResult) []provider.RelayerEvent { + var events []provider.RelayerEvent + + if eventResult == nil { + return events + } + + for _, e := range eventResult { + + for key, attributes := range e { + stringAttrs := make(map[string]string) + + attrs := attributes.(ibcEventQueryItem) + for attrKey, a := range attrs { + stringAttrs[attrKey] = fmt.Sprint(a) + } + + events = append(events, provider.RelayerEvent{ + EventType: key, + Attributes: stringAttrs, + }) + } + + } + return events +} diff --git a/relayer/chains/substrate/utils.go b/relayer/chains/substrate/utils.go index cbe4ff1d7..d4d925ecf 100644 --- a/relayer/chains/substrate/utils.go +++ b/relayer/chains/substrate/utils.go @@ -1,11 +1,37 @@ package substrate import ( + "bytes" + + "github.com/ComposableFi/go-substrate-rpc-client/v4/scale" clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" conntypes "github.com/cosmos/ibc-go/v5/modules/core/03-connection/types" chantypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types" ) +// Encode scale encodes a data type and returns the scale encoded data as a byte type. +func Encode(data any) ([]byte, error) { + var buf bytes.Buffer + enc := scale.NewEncoder(&buf) + err := enc.Encode(data) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// Decode decodes an encoded type to a target type. It takes encoded bytes and target interface as arguments and +// returns decoded data as the target type. +func Decode(source []byte, target any) error { + dec := scale.NewDecoder(bytes.NewReader(source)) + err := dec.Decode(target) + if err != nil { + return err + } + return nil + +} + func intoIBCEventType(substrateType SubstrateEventType) string { switch substrateType { case CreateClient: