-
Notifications
You must be signed in to change notification settings - Fork 47
/
loader.go
233 lines (186 loc) · 5.32 KB
/
loader.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
230
231
232
233
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT License was not distributed with this
// file, you can obtain one at https://opensource.org/licenses/MIT.
//
// Copyright (c) DUSK NETWORK. All rights reserved.
package chain
import (
"bytes"
"errors"
"fmt"
"github.com/dusk-network/dusk-blockchain/pkg/core/data/block"
"github.com/dusk-network/dusk-blockchain/pkg/core/database"
"github.com/dusk-network/dusk-blockchain/pkg/core/verifiers"
)
const (
// SanityCheckHeight is the suggested amount of blocks to check when
// calling Loader.SanityCheckBlockchain.
SanityCheckHeight uint64 = 10
)
// DBLoader performs database prefetching and sanityChecks at node startup.
type DBLoader struct {
db database.DB
// Unsure if the genesis block needs to be here.
genesis *block.Block
}
// SanityCheckBlock will verify whether we have not seed the block before
// (duplicate), perform a check on the block header and verifies the coinbase
// transactions. It leaves the bulk of transaction verification to the executor
// Return nil if the sanity check passes.
func (l *DBLoader) SanityCheckBlock(prevBlock block.Block, blk block.Block) error {
// 1. Check that we have not seen this block before
err := l.db.View(func(t database.Transaction) error {
_, err := t.FetchBlockExists(blk.Header.Hash)
return err
})
if err != database.ErrBlockNotFound {
if err == nil {
err = errors.New("block already exists")
}
return err
}
if err := verifiers.CheckBlockHeader(prevBlock, blk); err != nil {
return err
}
return nil
}
// NewDBLoader returns a Loader which gets the Chain Tip from the DB.
func NewDBLoader(db database.DB, genesis *block.Block) *DBLoader {
return &DBLoader{db: db, genesis: genesis}
}
// Height returns the height of the blockchain stored in the DB.
func (l *DBLoader) Height() (uint64, error) {
var height uint64
err := l.db.View(func(t database.Transaction) error {
var err error
height, err = t.FetchCurrentHeight()
return err
})
return height, err
}
// BlockAt returns the block stored at a given height.
func (l *DBLoader) BlockAt(searchingHeight uint64) (block.Block, error) {
var blk *block.Block
err := l.db.View(func(t database.Transaction) error {
hash, err := t.FetchBlockHashByHeight(searchingHeight)
if err != nil {
return err
}
blk, err = t.FetchBlock(hash)
return err
})
if err != nil {
return block.Block{}, err
}
return *blk, err
}
// Clear the underlying DB.
func (l *DBLoader) Clear() error {
return l.db.Update(func(t database.Transaction) error {
return t.ClearDatabase()
})
}
// Close the underlying DB usign the drivers.
func (l *DBLoader) Close(driver string) error {
log.Info("Close database")
drvr, err := database.From(driver)
if err != nil {
return err
}
return drvr.Close()
}
// SanityCheckBlockchain checks the head and the tail of the blockchain to avoid
// inconsistencies and a faulty bootstrap.
func (l *DBLoader) SanityCheckBlockchain(startAt, firstBlocksAmount uint64) error {
var height uint64
// Verify first N blocks
err := l.db.View(func(t database.Transaction) error {
h, err := t.FetchBlockHashByHeight(startAt)
if err != nil {
return err
}
prevHeader, err := t.FetchBlockHeader(h)
if err != nil {
return err
}
for height = startAt + 1; height <= firstBlocksAmount; height++ {
hash, err := t.FetchBlockHashByHeight(height)
if err == database.ErrBlockNotFound {
// we reach the tip
return nil
}
if err != nil {
return err
}
header, err := t.FetchBlockHeader(hash)
if err != nil {
return err
}
if !bytes.Equal(header.PrevBlockHash, prevHeader.Hash) {
return fmt.Errorf("invalid block hash at height %d", height)
}
prevHeader = header
}
return nil
})
if err != nil {
return err
}
// TODO: Verify last blocks
return nil
}
// LoadTip returns the tip of the chain.
func (l *DBLoader) LoadTip() (*block.Block, []byte, error) {
var tip *block.Block
var persistedHash []byte
err := l.db.Update(func(t database.Transaction) error {
s, err := t.FetchRegistry()
if err != nil {
// TODO: maybe log the error here and diversify between empty
// results and actual errors
// Store Genesis Block, if a modern node runs
err = t.StoreBlock(l.genesis, true)
if err != nil {
return err
}
tip = l.genesis
persistedHash = l.genesis.Header.Hash
return nil
}
// Reconstruct chain tip
h, err := t.FetchBlockHeader(s.TipHash)
if err != nil {
return err
}
txs, err := t.FetchBlockTxs(s.TipHash)
if err != nil {
return err
}
tip = &block.Block{Header: h, Txs: txs}
persistedHash = s.PersistedHash
return nil
})
if err != nil {
return nil, nil, err
}
// Verify chain state. There shouldn't be any blocks higher than chainTip
err = l.db.View(func(t database.Transaction) error {
nextHeight := tip.Header.Height + 1
hash, e := t.FetchBlockHashByHeight(nextHeight)
// Check if error is nil and the hash is set
if e == nil && len(hash) > 0 {
err = fmt.Errorf("state points at %d height but the tip is higher", tip.Header.Height)
log.WithError(err).Warn("loader failed")
}
// TODO: this throws ErrBlockNotFound in tests. Should we propagate the
// error?
//if e != nil {
// return e
//}
return nil
})
if err != nil {
return nil, nil, err
}
return tip, persistedHash, nil
}