-
Notifications
You must be signed in to change notification settings - Fork 24
/
sync_check.go
216 lines (175 loc) · 7.43 KB
/
sync_check.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package sync
import (
"context"
"github.com/Conflux-Chain/confura/store"
"github.com/Conflux-Chain/confura/store/mysql"
citypes "github.com/Conflux-Chain/confura/types"
"github.com/Conflux-Chain/confura/util"
sdk "github.com/Conflux-Chain/go-conflux-sdk"
"github.com/Conflux-Chain/go-conflux-sdk/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Epoch reverted handler function injected support for flexibility and testability
type epochRevertedChecker func(cfx sdk.ClientOperator, s store.StackOperable, epochNo uint64) (res bool, err error)
type firstRevertedEpochSearcher func(cfx sdk.ClientOperator, s store.StackOperable, epochRange citypes.RangeUint64) (uint64, error)
type epochRevertedPruner func(s store.StackOperable, er citypes.RangeUint64) error
// Ensure epoch data in store valid such as not reverted etc.,
func ensureStoreEpochDataOk(cfx sdk.ClientOperator, s store.StackOperable) error {
var maxEpoch uint64
var err error
if ms, ok := s.(*mysql.MysqlStore); ok {
maxEpoch, ok, err = ms.MaxEpoch()
if err == nil && !ok { // no epoch data existed yet
return nil
}
} else {
ss := s.(store.Store)
// Get the latest confirmed sync epoch number from store
_, maxEpoch, err = ss.GetGlobalEpochRange()
// If there is no epoch data in store yet, nothing needs to be done
if ss.IsRecordNotFound(err) {
return nil
}
}
// Otherwise return err
if err != nil {
return errors.WithMessage(err, "failed to read block epoch range from store")
}
epochRange := citypes.RangeUint64{From: 1, To: maxEpoch}
logrus.WithField("epochRange", epochRange).Debug("Ensuring epoch data within range ok...")
// Epoch reverted handler
searcher := func(cfx sdk.ClientOperator, s store.StackOperable, epochRange citypes.RangeUint64) (uint64, error) {
return findFirstRevertedEpochInRange(cfx, s, epochRange, checkIfEpochIsReverted)
}
return ensureEpochRangeNotRerverted(cfx, s, epochRange, searcher, pruneRevertedEpochData)
}
// Ensure epoch within the specified range not reverted or prune the reverted epoch data
func ensureEpochRangeNotRerverted(
cfx sdk.ClientOperator, s store.StackOperable, epochRange citypes.RangeUint64,
searcher firstRevertedEpochSearcher, pruner epochRevertedPruner,
) error {
logger := logrus.WithField("epochRange", epochRange)
logger.Debug("Ensuring epoch data within range not reverted...")
// Handle 200 epochs per window for each loop
var winSize, winStart, winEnd, matched uint64 = 200, epochRange.To, epochRange.To, 0
for winStart <= winEnd && winEnd > 0 {
// Find the first reverted epoch within epoch range (winStart, winEnd)
searchRange := citypes.RangeUint64{From: winStart, To: winEnd}
firstRevertedEpoch, err := searcher(cfx, s, searchRange)
if err != nil {
logrus.WithField("epochRange", searchRange).WithError(err).
Error("Failed to find the first reverted epoch within range")
return err
} else if firstRevertedEpoch != 0 { // updated matched reverted epoch
matched = firstRevertedEpoch
}
// The first reverted epoch found is not the start epoch within the searching range
// or no reverted epoch found at all or all epochs are searched by
if firstRevertedEpoch > winStart || firstRevertedEpoch == 0 || winStart <= 1 {
break
}
// Update winEnd and winStart
winEnd = winStart - 1 // decrease winEnd to the left one of winStart
winStart = epochRange.From // set winStart to the first epoch of the epoch range
if winEnd >= winSize { // if winEnd can cover winSize, calculate the winStart
winStart = util.MaxUint64(winEnd-winSize+1, epochRange.From) // also make sure winStart be within the epoch range
}
}
// Prune reverted epoch data
if matched != 0 && matched >= epochRange.From && matched <= epochRange.To {
logger.WithField("matched", matched).Debug("Found the first reverted epoch within range")
pruneEpochRange := citypes.RangeUint64{From: matched, To: epochRange.To}
if err := pruner(s, pruneEpochRange); err != nil {
logger.WithField("epochFrom", matched).Error("Failed to prune reverted epoch within range")
return errors.WithMessage(err, "failed to prune reverted epoch data")
}
logger.WithField("pruneEpochRange", pruneEpochRange).Info("Pruned dirty data to ensure epoch data validity")
}
return nil
}
// Find the first reverted epoch number from a specified epoch range
func findFirstRevertedEpochInRange(
cfx sdk.ClientOperator, s store.StackOperable, er citypes.RangeUint64, checker epochRevertedChecker,
) (uint64, error) {
// Find the first reverted sync epoch with binary probing
start, mid, end, matched := er.From, er.To, er.To, uint64(0)
for start <= end && mid >= er.From && mid <= er.To {
reverted, err := checker(cfx, s, mid)
if err != nil {
logrus.WithError(err).WithField("epoch", mid).Error("Failed to check epoch reverted")
return 0, errors.WithMessage(err, "failed to check epoch reverted")
}
if reverted {
logrus.WithFields(logrus.Fields{
"epochRange": citypes.RangeUint64{From: start, To: end},
}).WithField("matched", mid).Debug("Found a reverted epoch within range")
matched, end = mid, mid-1
} else {
start = mid + 1
}
if start <= end { // in case of overflow
mid = start + (end-start)>>1
}
}
logrus.WithField("epochRange", er).WithField("matched", matched).Debug("Got the final reverted epoch within range")
return matched, nil
}
// Check if the epoch data in store is reverted
func checkIfEpochIsReverted(
cfx sdk.ClientOperator, s store.StackOperable, epochNo uint64,
) (res bool, err error) {
if ms, ok := s.(*mysql.MysqlStore); ok {
pivotHash, ok, err := ms.PivotHash(epochNo)
if err != nil {
return false, errors.WithMessage(err, "failed to get epoch pivot hash")
}
if !ok {
return false, nil
}
// Fetch the epoch pivot block from blockchain
epBlock, err := cfx.GetBlockSummaryByEpoch(types.NewEpochNumberUint64(epochNo))
if err != nil {
return false, errors.WithMessagef(err, "failed to get pivot block for epoch %v from blockchain", epochNo)
}
return types.Hash(pivotHash) != epBlock.BlockHeader.Hash, nil
}
ss := s.(store.Store)
// Get the sync epoch block from store
sBlock, err := ss.GetBlockSummaryByEpoch(context.Background(), epochNo)
if err != nil {
// Epoch data not found in store, take it as not reverted
if ss.IsRecordNotFound(errors.Cause(err)) {
return false, nil
}
return false, errors.WithMessagef(err, "failed to get pivot block for epoch %v from store", epochNo)
}
// Fetch the epoch pivot block from blockchain
epBlock, err := cfx.GetBlockSummaryByEpoch(types.NewEpochNumberUint64(epochNo))
if err != nil {
return false, errors.WithMessagef(err, "failed to get pivot block for epoch %v from blockchain", epochNo)
}
// Check if block epoch number matched or not
if sBlock.CfxBlockSummary.EpochNumber == nil || sBlock.CfxBlockSummary.EpochNumber.ToInt().Uint64() != epochNo {
return true, nil
}
// Compare block hash to see if the epoch is reverted
return sBlock.CfxBlockSummary.BlockHeader.Hash != epBlock.BlockHeader.Hash, nil
}
// Remove reverted epoch data (blocks, trxs, and logs) from store
func pruneRevertedEpochData(s store.StackOperable, er citypes.RangeUint64) error {
numsEpochs := er.To - er.From + 1
// Delete at most 200 records per round
maxDelete := uint64(200)
rounds := numsEpochs / maxDelete
if numsEpochs%maxDelete != 0 {
rounds++
}
for i := rounds; i > 0; i-- {
// Remove reverted epoch data from store
if err := s.Popn(er.From + (i-1)*maxDelete); err != nil {
return err
}
}
return nil
}