@@ -39,24 +39,51 @@ type batchService struct {
3939 batchListener postage.BatchEventListener
4040
4141 checksum hash.Hash // checksum hasher
42- resync bool
42+
43+ // snapshotResumeBlock is the chain block height the store was rebuilt to
44+ // from a postage snapshot. When set, live sync resumes from here.
45+ snapshotResumeBlock uint64
4346}
4447
4548type Interface interface {
4649 postage.EventUpdater
4750}
4851
52+ // Snapshot carries the optional inputs needed to rebuild the batch store from a
53+ // postage snapshot. When passed to New (non-nil), the store is reset and
54+ // replayed from the snapshot before the service is returned, and live sync
55+ // later resumes from the snapshot's block height. New takes ownership of
56+ // Listener and closes it once the snapshot has been replayed.
57+ type Snapshot struct {
58+ // Listener replays the snapshot's events into the batch store.
59+ Listener postage.Listener
60+ // StartBlock is the block height from which the snapshot is replayed (the
61+ // postage contract start block).
62+ StartBlock uint64
63+ // ResumeBlock is the block height the snapshot reached, from which live sync
64+ // resumes once the replay completes.
65+ ResumeBlock uint64
66+ }
67+
4968// New will create a new BatchService.
69+ //
70+ // The batch store is reset here, in the constructor, when a resync was requested
71+ // or a dirty shutdown was detected. A provided snapshot is then replayed onto the
72+ // (possibly reset) store; if that replay fails the store is reset again so live
73+ // sync can rebuild it from the chain. Start never resets the store. The returned
74+ // bool reports whether the snapshot was replayed successfully.
5075func New (
76+ ctx context.Context ,
5177 stateStore storage.StateStorer ,
5278 storer postage.Storer ,
5379 logger log.Logger ,
5480 listener postage.Listener ,
5581 owner []byte ,
5682 batchListener postage.BatchEventListener ,
5783 checksumFunc func () hash.Hash ,
84+ snapshot * Snapshot ,
5885 resync bool ,
59- ) (Interface , error ) {
86+ ) (Interface , bool , error ) {
6087 if checksumFunc == nil {
6188 checksumFunc = sha3 .New256
6289 }
@@ -65,37 +92,113 @@ func New(
6592 sum = checksumFunc ()
6693 )
6794
95+ logger = logger .WithName (loggerName ).Register ()
96+
6897 dirty := false
69- err := stateStore .Get (dirtyDBKey , & dirty )
70- if err != nil && ! errors .Is (err , storage .ErrNotFound ) {
71- return nil , err
98+ if err := stateStore .Get (dirtyDBKey , & dirty ); err != nil && ! errors .Is (err , storage .ErrNotFound ) {
99+ return nil , false , err
100+ }
101+ if dirty {
102+ logger .Warning ("batch service: dirty shutdown detected, resetting batch store" )
72103 }
73104
74105 if resync {
106+ logger .Warning ("batch service: resync requested, resetting batch store" )
75107 if err := stateStore .Delete (checksumDBKey ); err != nil {
76- return nil , err
108+ return nil , false , err
77109 }
78110 } else if ! dirty {
79111 if err := stateStore .Get (checksumDBKey , & b ); err != nil {
80112 if ! errors .Is (err , storage .ErrNotFound ) {
81- return nil , err
113+ return nil , false , err
82114 }
83115 } else {
84116 s , err := hex .DecodeString (b )
85117 if err != nil {
86- return nil , err
118+ return nil , false , err
87119 }
88120 n , err := sum .Write (s )
89121 if err != nil {
90- return nil , err
122+ return nil , false , err
91123 }
92124 if n != len (s ) {
93- return nil , errors .New ("batchstore checksum init" )
125+ return nil , false , errors .New ("batchstore checksum init" )
126+ }
127+ }
128+ }
129+
130+ bs := & batchService {
131+ stateStore : stateStore ,
132+ storer : storer ,
133+ logger : logger ,
134+ listener : listener ,
135+ owner : owner ,
136+ batchListener : batchListener ,
137+ checksum : sum ,
138+ }
139+
140+ // Reset the store once, here, when a resync was requested or a dirty shutdown
141+ // was detected (both already logged above). A snapshot is then replayed onto
142+ // the store; Start does not reset, so this is the only unconditional reset.
143+ if dirty || resync {
144+ if err := bs .reset (); err != nil {
145+ return nil , false , err
146+ }
147+ }
148+
149+ snapshotLoaded := false
150+ if snapshot != nil {
151+ if err := bs .loadSnapshot (ctx , snapshot ); err != nil {
152+ logger .Error (err , "failed to start batch service from snapshot, continuing outside snapshot block..." )
153+ // A partial replay may have written to (and dirtied) the store, so
154+ // reset it again to rebuild cleanly from the chain during live sync.
155+ if err := bs .reset (); err != nil {
156+ return nil , false , err
94157 }
158+ } else {
159+ snapshotLoaded = true
160+ }
161+ }
162+
163+ return bs , snapshotLoaded , nil
164+ }
165+
166+ // reset wipes the batch store so it can be rebuilt from scratch. It runs only
167+ // during New. The reason for the reset is logged by the caller at the point of
168+ // detection, so the intent is recorded even if the reset itself then fails.
169+ func (svc * batchService ) reset () error {
170+ if err := svc .storer .Reset (); err != nil {
171+ return err
172+ }
173+ if err := svc .stateStore .Delete (dirtyDBKey ); err != nil {
174+ return err
175+ }
176+ svc .logger .Warning ("batch service: batch store has been reset. your node will now resync chain data. this might take a while..." )
177+
178+ return nil
179+ }
180+
181+ // loadSnapshot rebuilds the (already reset) store from a postage snapshot by
182+ // replaying its events and records the block height to resume live sync from.
183+ func (svc * batchService ) loadSnapshot (ctx context.Context , snapshot * Snapshot ) error {
184+ defer func () {
185+ if err := snapshot .Listener .Close (); err != nil {
186+ svc .logger .Error (err , "failed to close event listener (snapshot) failure" )
95187 }
188+ }()
189+
190+ startBlock := snapshot .StartBlock
191+ if cs := svc .storer .GetChainState (); cs .Block > startBlock {
192+ startBlock = cs .Block
96193 }
97194
98- return & batchService {stateStore , storer , logger .WithName (loggerName ).Register (), listener , owner , batchListener , sum , resync }, nil
195+ if err := <- snapshot .Listener .Listen (ctx , startBlock + 1 , svc ); err != nil {
196+ return err
197+ }
198+
199+ svc .snapshotResumeBlock = snapshot .ResumeBlock
200+
201+ return nil
99202}
100203
101204// Create will create a new batch with the given ID, owner value and depth and
@@ -242,33 +345,16 @@ func (svc *batchService) TransactionEnd() error {
242345var ErrInterruped = errors .New ("postage sync interrupted" )
243346
244347func (svc * batchService ) Start (ctx context.Context , startBlock uint64 ) (err error ) {
245- dirty := false
246- err = svc .stateStore .Get (dirtyDBKey , & dirty )
247- if err != nil && ! errors .Is (err , storage .ErrNotFound ) {
248- return err
249- }
250-
251- if dirty || svc .resync {
252-
253- if dirty {
254- svc .logger .Warning ("batch service: dirty shutdown detected, resetting batch store" )
255- } else {
256- svc .logger .Warning ("batch service: resync requested, resetting batch store" )
257- }
258-
259- if err := svc .storer .Reset (); err != nil {
260- return err
261- }
262- if err := svc .stateStore .Delete (dirtyDBKey ); err != nil {
263- return err
264- }
265- svc .logger .Warning ("batch service: batch store has been reset. your node will now resync chain data. this might take a while..." )
266- }
267-
348+ // The store reset already happened in New, so Start only drives live sync.
268349 cs := svc .storer .GetChainState ()
269350 if cs .Block > startBlock {
270351 startBlock = cs .Block
271352 }
353+ // When the store was rebuilt from a snapshot, resume live sync from the
354+ // snapshot's block height rather than the requested start block.
355+ if svc .snapshotResumeBlock > startBlock {
356+ startBlock = svc .snapshotResumeBlock
357+ }
272358
273359 syncedChan := svc .listener .Listen (ctx , startBlock + 1 , svc )
274360
0 commit comments