forked from btcsuite/btcwallet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsync.go
229 lines (199 loc) · 7.16 KB
/
sync.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
217
218
219
220
221
222
223
224
225
226
227
228
229
// Copyright (c) 2014 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package waddrmgr
import (
"sync"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrwallet/walletdb"
)
const (
// maxRecentHashes is the maximum number of hashes to keep in history
// for the purposes of rollbacks. Note that if reorganizations longer
// than this occur, corruption of the wallet may occur.
maxRecentHashes = 1000
)
// BlockStamp defines a block (by height and a unique hash) and is
// used to mark a point in the blockchain that an address manager element is
// synced to.
type BlockStamp struct {
Height int32
Hash chainhash.Hash
}
// syncState houses the sync state of the manager. It consists of the recently
// seen blocks as height, as well as the start and current sync block stamps.
type syncState struct {
// startBlock is the first block that can be safely used to start a
// rescan. It is either the block the manager was created with, or
// the earliest block provided with imported addresses or scripts.
startBlock BlockStamp
// syncedTo is the current block the addresses in the manager are known
// to be synced against.
syncedTo BlockStamp
// recentHeight is the most recently seen sync height.
recentHeight int32
// recentHashes is a list of the last several seen block hashes.
recentHashes []chainhash.Hash
}
// iter returns a BlockIterator that can be used to iterate over the recently
// seen blocks in the sync state.
func (s *syncState) iter(mtx *sync.RWMutex) *BlockIterator {
if s.recentHeight == -1 || len(s.recentHashes) == 0 {
return nil
}
return &BlockIterator{
mtx: mtx,
height: s.recentHeight,
index: len(s.recentHashes) - 1,
syncInfo: s,
}
}
// newSyncState returns a new sync state with the provided parameters.
func newSyncState(startBlock, syncedTo *BlockStamp, recentHeight int32,
recentHashes []chainhash.Hash) *syncState {
return &syncState{
startBlock: *startBlock,
syncedTo: *syncedTo,
recentHeight: recentHeight,
recentHashes: recentHashes,
}
}
// BlockIterator allows for the forwards and backwards iteration of recently
// seen blocks.
type BlockIterator struct {
mtx *sync.RWMutex
height int32
index int
syncInfo *syncState
}
// Next returns the next recently seen block or false if there is not one.
func (it *BlockIterator) Next() bool {
it.mtx.RLock()
defer it.mtx.RUnlock()
if it.index+1 >= len(it.syncInfo.recentHashes) {
return false
}
it.index++
return true
}
// Prev returns the previous recently seen block or false if there is not one.
func (it *BlockIterator) Prev() bool {
it.mtx.RLock()
defer it.mtx.RUnlock()
if it.index-1 < 0 {
return false
}
it.index--
return true
}
// BlockStamp returns the block stamp associated with the recently seen block
// the iterator is currently pointing to.
func (it *BlockIterator) BlockStamp() BlockStamp {
it.mtx.RLock()
defer it.mtx.RUnlock()
return BlockStamp{
Height: it.syncInfo.recentHeight -
int32(len(it.syncInfo.recentHashes)-1-it.index),
Hash: it.syncInfo.recentHashes[it.index],
}
}
// NewIterateRecentBlocks returns an iterator for recently-seen blocks.
// The iterator starts at the most recently-added block, and Prev should
// be used to access earlier blocks.
//
// NOTE: Ideally this should not really be a part of the address manager as it
// is intended for syncing purposes. It is being exposed here for now to go
// with the other syncing code. Ultimately, all syncing code should probably
// go into its own package and share the data store.
func (m *Manager) NewIterateRecentBlocks() *BlockIterator {
m.mtx.RLock()
defer m.mtx.RUnlock()
return m.syncState.iter(&m.mtx)
}
// SetSyncedTo marks the address manager to be in sync with the recently-seen
// block described by the blockstamp. When the provided blockstamp is nil,
// the oldest blockstamp of the block the manager was created at and of all
// imported addresses will be used. This effectively allows the manager to be
// marked as unsynced back to the oldest known point any of the addresses have
// appeared in the block chain.
func (m *Manager) SetSyncedTo(ns walletdb.ReadWriteBucket, bs *BlockStamp) error {
m.mtx.Lock()
defer m.mtx.Unlock()
// Update the recent history.
//
// NOTE: The values in the memory sync state aren't directly modified
// here in case the forthcoming db update fails. The memory sync state
// is updated with these values as needed after the db updates.
recentHeight := m.syncState.recentHeight
recentHashes := m.syncState.recentHashes
if bs == nil {
// Use the stored start blockstamp and reset recent hashes and
// height when the provided blockstamp is nil.
bs = &m.syncState.startBlock
recentHeight = m.syncState.startBlock.Height
recentHashes = nil
} else if bs.Height < recentHeight {
// When the new block stamp height is prior to the most recently
// seen height, a rollback is being performed. Thus, when the
// previous block stamp is already saved, remove anything after
// it. Otherwise, the rollback must be too far in history, so
// clear the recent hashes and set the recent height to the
// current block stamp height.
numHashes := len(recentHashes)
idx := numHashes - 1 - int(recentHeight-bs.Height)
if idx >= 0 && idx < numHashes && recentHashes[idx] == bs.Hash {
// subslice out the removed hashes.
recentHeight = bs.Height
recentHashes = recentHashes[:idx]
} else {
recentHeight = bs.Height
recentHashes = nil
}
} else if bs.Height != recentHeight+1 {
// At this point the new block stamp height is after the most
// recently seen block stamp, so it should be the next height in
// sequence. When this is not the case, the recent history is
// no longer valid, so clear the recent hashes and set the
// recent height to the current block stamp height.
recentHeight = bs.Height
recentHashes = nil
} else {
// The only case left is when the new block stamp height is the
// next height in sequence after the most recently seen block
// stamp, so update it accordingly.
recentHeight = bs.Height
}
// Enforce maximum number of recent hashes.
if len(recentHashes) == maxRecentHashes {
// Shift everything down one position and add the new hash in
// the last position.
copy(recentHashes, recentHashes[1:])
recentHashes[maxRecentHashes-1] = bs.Hash
} else {
recentHashes = append(recentHashes, bs.Hash)
}
// Update the database.
err := putSyncedTo(ns, bs)
if err != nil {
return err
}
err = putRecentBlocks(ns, recentHeight, recentHashes)
if err != nil {
return err
}
// Update memory now that the database is updated.
m.syncState.syncedTo = *bs
m.syncState.recentHashes = recentHashes
m.syncState.recentHeight = recentHeight
return nil
}
// SyncedTo returns details about the block height and hash that the address
// manager is synced through at the very least. The intention is that callers
// can use this information for intelligently initiating rescans to sync back to
// the best chain from the last known good block.
func (m *Manager) SyncedTo() BlockStamp {
m.mtx.Lock()
defer m.mtx.Unlock()
return m.syncState.syncedTo
}