// Proof generation helper functions
//
// Note: 'Chain' is assumed to contain information about the next chain in the channel path
//
// GetClientID returns the clientID for the next chain in the channel path
func (Chain) GetClientID() (clientID string)
// GetConnectionID returns the connectionID corresponding to the next chain in the channel path
func (Chain) GetConnectionID() (connectionID string)
// GetClientStateHeight returns the client state height for the clientState corresponding to
// the next chain in the channel path
func (Chain) GetClientStateHeight() exported.Height
// QueryStateAtHeight returns the value and proof of a key at the given height along with the height at
// which the proof will succeed.
func (Chain) QueryStateAtHeight(key string, height int64) (value []byte, proof []byte, height exported.Height)
// UpdateClient updates the client state corresponding to the next chain in the channel path
func (*Chain) UpdateClient()
// ProofHeights contains multi-hop proof query height data.
type ProofHeights struct {
proofHeight exported.Height // query the proof at this height
consensusHeight exported.Height // the proof is for the consensusState at this height
}
// ProofData is a generic proof struct.
type ProofData struct {
Key *MerklePath
Value []byte
Proof []byte
}
// MultihopProof defines a set of proofs to verify a multihop message.
// Consensus and Connection proofs are ordered from receiving to sending chain but do not include
// the chain[1] consensus/connection state on chain[0] since it is already known on the receiving chain.
type MultihopProof struct {
KeyProof *ProofData // the key/value proof on the on chain[KeyProofIndex] in the channel path
ConsensusProofs []*ProofData // array of consensus proofs starting with proof of consensusState of chain[1] on chain[2]
ConnectionProofs []*ProofData // array of connection proofs starting with proof of conn[1,2] on chain[2]
}
// QueryMultihopProof generates proof of a key/value at the proofHeight on indexed chain (chain0).
// Chains are provided in order from the sending (source) chain to the receiving (verifying) chain.
func QueryMultihopProof(
chains []*Chain,
key string,
keyHeight exported.Height,
includeKeyValue bool,
) (
multihopProof MultihopProof,
multihopProofHeight exported.Height,
) {
abortTransactionUnless(len(chains) > 1)
// calculate proof heights along channel path
proofHeights := make([]*ProofHeights, len(chains)-1)
abortTransactionUnless(calcProofHeights(chains, 1, keyHeight, proofHeights))
// the consensus state height of the proving chain's counterparty
// this is where multi-hop proof verification begins
multihopProofHeight = abortTransactionUnless(proofHeights[len(proofHeights)-1].consensusHeight.Decrement())
// the key/value proof height is the height of the consensusState on the source chain
keyHeight = abortTransactionUnless(proofHeights[0].consensusHeight.Decrement())
var value []byte
bytes, keyProof := chains[0].QueryStateAtHeight(key, keyHeight)
if includeKeyValue {
value = bytes
}
// assign the key/value proof
multihopProof.KeyProof = &ProofData{
Key: nil, // key to prove constructed during verification
Value: value, // proven values are constructed during verification (except for frozen client proofs)
Proof: keyProof,
}
// query proofs of consensus/connection states on intermediate chains
multihopProof.ConsensusProofs = make([]*ProofData, len(chains)-2)
multihopProof.ConnectionProofs = make([]*ProofData, len(chains)-2)
multihopProof.ConsensusProofs, multihopProof.ConnectionProofs = abortTransactionUnless(
queryIntermediateProofs(
chains,
len(chains)-2,
proofHeights,
multihopProof.ConsensusProofs,
multihopProof.ConnectionProofs)
)
return
}
// calcProofHeights calculates the optimal proof heights to generate a multi-hop proof
// along the channel path and performs client updates as needed.
func calcProofHeights(
chains []*Chain,
chainIdx int,
consensusHeight exported.Height,
proofHeights []*ProofHeights,
) {
var height ProofHeights
chain := chains[chainIdx]
// find minimum consensus height provable on the next chain
// i.e. proofHeight is the minimum height at which the consensusState with
// height=consensusHeight can be proved on the chain (aka processedHeight)
height.proofHeight, height.consensusHeight = abortTransactionUnless(queryMinimumConsensusHeight(chain, consensusHeight, nil))
// if no suitable consensusHeight then update client and use latest chain height/client height
//
// TODO: It could be more efficient to update the client with the missing block height
// rather than the latest block height since it would be less likely to need client updates
// on subsequent chains.
if height.proofHeight == nil {
abortTransactionUnless(chain.UpdateClient())
height.proofHeight = chain.GetLatestHeight()
height.consensusHeight = chain.GetClientStateHeight(chains[chainIdx+1])
}
// stop on the next to last chain
if chainIdx == len(chains)-2 {
proofHeights[chainIdx-1] = &height
return
}
// use the proofHeight as the next consensus height
abortTransactionUnless(calcProofHeights(chains, chainIdx+1, height.proofHeight, proofHeights))
proofHeights[chainIdx-1] = &height
return
}
// queryIntermediateProofs recursively queries intermediate chains in a multi-hop channel path for consensus state
// and connection proofs. It stops at the second to last path since the consensus and connection state on the
// final hop is already known on the destination.
func queryIntermediateProofs(
chains []*Chain,
proofIdx int,
proofHeights []*ProofHeights,
consensusProofs []*ProofData,
connectionProofs []*ProofData,
) {
// no need to query proofs on final chain since the clientState is already known
if proofIdx < 0 {
return
}
chain := chains[proofIdx]
ph := proofHeights[proofIdx]
// query proof of the consensusState
proof := abortTransactionUnless(queryConsensusStateProof(chain, ph.proofHeight, ph.consensusHeight))
consensusProofs[len(p.Paths)-proofIdx-2] = proof
// query proof of the connectionEnd
proof = abortTransactionUnless(queryConnectionProof(chain, ph.proofHeight))
connectionProofs[len(p.Paths)-proofIdx-2] = proof
// continue querying proofs on the next chain in the path
queryIntermediateProofs(chains, proofIdx-1, proofHeights, consensusProofs, connectionProofs)
}
// Query a proof for the counterparty consensus state at the specified height on the given chain.
func queryConsensusStateProof(
chain Chain,
proofHeight exported.Height,
consensusHeight exported.Height,
) *ProofData {
key := host.FullConsensusStateKey(chain.GetClientID(), consensusHeight)
consensusStateBytes, consensusStateProof := chain.QueryStateAtHeight(key, int64(proofHeight.GetRevisionHeight()))
merklePath := abortTransactionUnless(chain.GetMerklePath(string(key)))
return &ProofData{
Key: merklePath,
Value: consensusStateBytes,
Proof: consensusStateProof,
}
}
// Query a proof for the connEnd on the given chain at the specified height.
func queryConnectionProof(
chain Chain,
proofHeight exported.Height,
) *ProofData {
key := host.ConnectionKey(chain.GetConnectionID())
connectionEndBytes, connectionEndProof := chain.QueryStateAtHeight(key, int64(proofHeight.GetRevisionHeight()))
merklePath := abortTransactionUnless(chain.GetMerklePath(string(key)))
return &ProofData{
Key: merklePath,
Value: connectionEndBytes,
Proof: connectionEndProof,
}
}
// queryMinimumConsensusHeight returns the minimum height within the provided
// range at which a valid consensusState exists (processedHeight) and the
// corresponding consensus state height (consensusHeight).
func queryMinimumConsensusHeight(
chain Chain,
minConsensusHeight exported.Height,
limit uint64,
) (
processedHeight exported.Height,
consensusHeight exported.Height
) {
// find the minimum height consensus state
consensusHeight := minConsensusHeight
for i := uint64(0); i < limit; i++ {
key := host.FullClientKey(clientID, ibctm.ProcessedHeightKey(consensusHeight))
consensusStateHeightBytes, _ := abortTransactionUnless(chain.QueryStateAtHeight(key, chain.LastHeader.Header.Height, false))
if consensusStateHeightBytes != nil {
proofHeight := abortTransactionUnless(clienttypes.ParseHeight(string(consensusStateHeightBytes)))
return proofHeight, consensusHeight
}
consensusHeight = consensusHeight.Increment()
}
return nil, nil
}