-
Notifications
You must be signed in to change notification settings - Fork 42
/
Copy pathepoch_chain_info_indexer.go
139 lines (119 loc) · 5.48 KB
/
epoch_chain_info_indexer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package keeper
import (
"context"
"fmt"
"cosmossdk.io/store/prefix"
"github.com/cosmos/cosmos-sdk/runtime"
sdk "github.com/cosmos/cosmos-sdk/types"
bbn "github.com/babylonlabs-io/babylon/types"
"github.com/babylonlabs-io/babylon/x/zoneconcierge/types"
)
// GetEpochChainInfo gets the latest chain info of a given epoch for a given chain ID
func (k Keeper) GetEpochChainInfo(ctx context.Context, consumerID string, epochNumber uint64) (*types.ChainInfoWithProof, error) {
if !k.EpochChainInfoExists(ctx, consumerID, epochNumber) {
return nil, types.ErrEpochChainInfoNotFound
}
store := k.epochChainInfoStore(ctx, consumerID)
epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber)
epochChainInfoBytes := store.Get(epochNumberBytes)
var chainInfo types.ChainInfoWithProof
k.cdc.MustUnmarshal(epochChainInfoBytes, &chainInfo)
return &chainInfo, nil
}
func (k Keeper) setEpochChainInfo(ctx context.Context, consumerID string, epochNumber uint64, chainInfo *types.ChainInfoWithProof) {
store := k.epochChainInfoStore(ctx, consumerID)
store.Set(sdk.Uint64ToBigEndian(epochNumber), k.cdc.MustMarshal(chainInfo))
}
// EpochChainInfoExists checks if the latest chain info exists of a given epoch for a given chain ID
func (k Keeper) EpochChainInfoExists(ctx context.Context, consumerID string, epochNumber uint64) bool {
store := k.epochChainInfoStore(ctx, consumerID)
epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber)
return store.Has(epochNumberBytes)
}
// GetEpochHeaders gets the headers timestamped in a given epoch, in the ascending order
func (k Keeper) GetEpochHeaders(ctx context.Context, consumerID string, epochNumber uint64) ([]*types.IndexedHeader, error) {
headers := []*types.IndexedHeader{}
// find the last timestamped header of this chain in the epoch
epochChainInfoWithProof, err := k.GetEpochChainInfo(ctx, consumerID, epochNumber)
if err != nil {
return nil, err
}
epochChainInfo := epochChainInfoWithProof.ChainInfo
// it's possible that this epoch's snapshot is not updated for many epochs
// this implies that this epoch does not timestamp any header for this chain at all
if epochChainInfo.LatestHeader.BabylonEpoch < epochNumber {
return nil, types.ErrEpochHeadersNotFound
}
// now we have the last header in this epoch
headers = append(headers, epochChainInfo.LatestHeader)
// append all previous headers until reaching the previous epoch
canonicalChainStore := k.canonicalChainStore(ctx, consumerID)
lastHeaderKey := sdk.Uint64ToBigEndian(epochChainInfo.LatestHeader.Height)
// NOTE: even in ReverseIterator, start and end should still be specified in ascending order
canonicalChainIter := canonicalChainStore.ReverseIterator(nil, lastHeaderKey)
defer canonicalChainIter.Close()
for ; canonicalChainIter.Valid(); canonicalChainIter.Next() {
var prevHeader types.IndexedHeader
k.cdc.MustUnmarshal(canonicalChainIter.Value(), &prevHeader)
if prevHeader.BabylonEpoch < epochNumber {
// we have reached the previous epoch, break the loop
break
}
headers = append(headers, &prevHeader)
}
// reverse the list so that it remains ascending order
bbn.Reverse(headers)
return headers, nil
}
// recordEpochChainInfo records the chain info for a given epoch number of given chain ID
// where the latest chain info is retrieved from the chain info indexer
func (k Keeper) recordEpochChainInfo(ctx context.Context, consumerID string, epochNumber uint64) {
// get the latest known chain info
chainInfo, err := k.GetChainInfo(ctx, consumerID)
if err != nil {
k.Logger(sdk.UnwrapSDKContext(ctx)).Debug("chain info does not exist yet, nothing to record")
return
}
chainInfoWithProof := &types.ChainInfoWithProof{
ChainInfo: chainInfo,
ProofHeaderInEpoch: nil,
}
// NOTE: we can record epoch chain info without ancestor since IBC connection can be established at any height
k.setEpochChainInfo(ctx, consumerID, epochNumber, chainInfoWithProof)
}
// recordEpochChainInfo records the chain info for a given epoch number of given chain ID
// where the latest chain info is retrieved from the chain info indexer
func (k Keeper) recordEpochChainInfoProofs(ctx context.Context, epochNumber uint64) {
curEpoch := k.GetEpoch(ctx)
consumerIDs := k.GetAllConsumerIDs(ctx)
// save all inclusion proofs
for _, consumerID := range consumerIDs {
// retrieve chain info with empty proof
chainInfo, err := k.GetEpochChainInfo(ctx, consumerID, epochNumber)
if err != nil {
panic(err) // only programming error
}
lastHeaderInEpoch := chainInfo.ChainInfo.LatestHeader
if lastHeaderInEpoch.BabylonEpoch == curEpoch.EpochNumber {
// get proofCZHeaderInEpoch
proofCZHeaderInEpoch, err := k.ProveCZHeaderInEpoch(ctx, lastHeaderInEpoch, curEpoch)
if err != nil {
// only programming error is possible here
panic(fmt.Errorf("failed to generate proofCZHeaderInEpoch for consumer %s: %w", consumerID, err))
}
chainInfo.ProofHeaderInEpoch = proofCZHeaderInEpoch
// set chain info with proof back
k.setEpochChainInfo(ctx, consumerID, epochNumber, chainInfo)
}
}
}
// epochChainInfoStore stores each epoch's latest ChainInfo for a CZ
// prefix: EpochChainInfoKey || consumerID
// key: epochNumber
// value: ChainInfoWithProof
func (k Keeper) epochChainInfoStore(ctx context.Context, consumerID string) prefix.Store {
storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
epochChainInfoStore := prefix.NewStore(storeAdapter, types.EpochChainInfoKey)
consumerIDBytes := []byte(consumerID)
return prefix.NewStore(epochChainInfoStore, consumerIDBytes)
}