@@ -32,6 +32,10 @@ type UpdateLatestResult struct {
32
32
UpdatedSegment * ChainSegment
33
33
}
34
34
35
+ // capNumPollBlocks is a pipeline function
36
+ // that restricts the number of blocks to be
37
+ // polled, e.g. during filling gaps between
38
+ // two chain-segments.
35
39
func capNumPollBlocks (num int ) int {
36
40
if num > MaxNumPollBlocks {
37
41
return MaxNumPollBlocks
@@ -45,8 +49,6 @@ type ChainSegment struct {
45
49
chain []* types.Header
46
50
}
47
51
48
- // assumes reverse order, and that it is
49
- // a connected chain-segment.
50
52
func NewChainSegment (chain ... * types.Header ) * ChainSegment {
51
53
bc := & ChainSegment {
52
54
chain : chain ,
@@ -91,16 +93,31 @@ func (bc *ChainSegment) Copy() *ChainSegment {
91
93
return NewChainSegment (bc .chain ... )
92
94
}
93
95
96
+ // UpdateLatest incorporates a new chainsegment `update` into it's existing
97
+ // chain-segment.
98
+ // For this it backtracks the new chain-segment until it finds the common ancestor
99
+ // with it's current chain-segment. If there is no ancestor because of a block-number
100
+ // gap between the old segments "latest" block and the new segments "earliest" block,
101
+ // it will incrementally batch-augment the 'update' chain-segment with blocks older than
102
+ // it's "earliest" block, and call the UpdateLatest latest method recursively
103
+ // until the algorithm finds a common ancestor.
104
+ // The outcome of this process is an `UpdateLatestResult`, which
105
+ // communicates to the caller what part of the previous chain-segment had to be removed,
106
+ // and what part of the `update` chain-segment was appended to the previous chain-segment
107
+ // after removal of out-of-date blocks, in addition to the full newly updated chain-segment.
108
+ // This is a pointer method that updates the internal state of it's chain-segment!
94
109
func (bc * ChainSegment ) UpdateLatest (ctx context.Context , c client.Sync , update * ChainSegment ) (UpdateLatestResult , error ) {
95
110
update = update .Copy ()
96
111
if bc .Len () == 0 {
112
+ // We can't compare anything - instead of silently absorbing the
113
+ // whole new segment, communicate this to the caller with a specific error.
97
114
return UpdateLatestResult {}, ErrEmpty
98
115
}
99
116
100
117
if bc .Earliest ().Number .Cmp (update .Earliest ().Number ) == 1 {
101
- // We don't reach so far in the past.
102
- // This only happens when the cache of used blocks in
103
- // doesn't reach so far .
118
+ // We don't reach so far in the past for the old chain-segment .
119
+ // This happens when there is a large reorg, while the chain-segment
120
+ // of the cache is still small .
104
121
return UpdateLatestResult {}, fmt .Errorf (
105
122
"segment earliest=%d, update earliest=%d: %w" ,
106
123
bc .Earliest ().Number .Int64 (), update .Earliest ().Number .Int64 (),
@@ -143,12 +160,22 @@ func (bc *ChainSegment) UpdateLatest(ctx context.Context, c client.Sync, update
143
160
return bc .UpdateLatest (ctx , c , update )
144
161
}
145
162
// implicit case - overlap > 0:
163
+ // now we can compare the segments and find the common ancestor
164
+ // Return the segment of the overlap from the current segment
165
+ // and compute the diff of the whole new update segment.
146
166
removed , updated := bc .GetLatest (overlap ).DiffLeftAligned (update )
167
+ // don't copy, but use the method's struct,
168
+ // that way we modify in-place
147
169
full := bc
148
170
if removed != nil {
171
+ // cut the reorged section that has to be removed
172
+ // so that we only have the "left" section up until the
173
+ // common ancestor
149
174
full = full .GetEarliest (full .Len () - removed .Len ())
150
175
}
151
176
if updated != nil {
177
+ // and now append the update section
178
+ // to the right, effectively removing the reorged section
152
179
full .AddRight (updated )
153
180
}
154
181
return UpdateLatestResult {
@@ -158,11 +185,28 @@ func (bc *ChainSegment) UpdateLatest(ctx context.Context, c client.Sync, update
158
185
}, nil
159
186
}
160
187
188
+ // AddRight adds the `add` chain-segment to the "right" of the
189
+ // original chain-segment, and thus assumes that the `add` segments
190
+ // Earliest() block is the child-block of the original segments
191
+ // Latest() block. This condition is *not* checked,
192
+ // so callers have to guarantee for it.
161
193
func (bc * ChainSegment ) AddRight (add * ChainSegment ) * ChainSegment {
162
194
bc .chain = append (bc .chain , add .chain ... )
163
195
return bc
164
196
}
165
197
198
+ // DiffLeftAligned compares the ChainSegment to another chain-segment that
199
+ // starts at the same Earliest() block-number.
200
+ // It walks both segments from earliest to latest header simultaneously
201
+ // and compares the block-hashes. As soon as there is a mismatch
202
+ // in block-hashes, a consecutive difference from that point on is assumed.
203
+ // All diff blocks from the `other` chain-segment will be appended to the returned `update`
204
+ // chain-segment, and all diff blocks from the original chain-segment
205
+ // will be appended to the `remove` chain-segment.
206
+ // If there is no overlap in the diff, but the `other` chain-segment is longer than
207
+ // the original segment, the `remove` segment will be nil, and the `update` segment
208
+ // will consist of the non-overlapping blocks of the `other` segment.
209
+ // If both segments are identical, both `update` and `remove` segments will be nil.
166
210
func (bc * ChainSegment ) DiffLeftAligned (other * ChainSegment ) (remove , update * ChainSegment ) {
167
211
// 1) assumes both segments start at the same block height (earliest block at index 0 with same blocknum)
168
212
// 2) assumes the other.Len() >= bc.Len()
0 commit comments