-
Notifications
You must be signed in to change notification settings - Fork 46
/
store.go
356 lines (303 loc) · 9.99 KB
/
store.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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
package app
import (
"encoding/json"
"fmt"
"strings"
"github.com/iov-one/weave"
"github.com/iov-one/weave/errors"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
)
// StoreApp contains a data store and all info needed
// to perform queries and handshakes.
//
// It should be embedded in another struct for CheckTx,
// DeliverTx and initializing state from the genesis.
// Errors on ABCI steps handled as panics
// I'm sorry Alex, but there is no other way :(
// https://github.com/tendermint/tendermint/abci/issues/165#issuecomment-353704015
// "Regarding errors in general, for messages that don't take
// user input like Flush, Info, InitChain, BeginBlock, EndBlock,
// and Commit.... There is no way to handle these errors gracefully,
// so we might as well panic."
type StoreApp struct {
logger log.Logger
// name is what is returned from abci.Info
name string
// Database state (committed, check, deliver....)
store *CommitStore
// Code to initialize from a genesis file
initializer weave.Initializer
// How to handle queries
queryRouter weave.QueryRouter
// chainID is loaded from db in initialization
// saved once in parseGenesis
chainID string
// cached validator changes from DeliverTx
pending weave.ValidatorUpdates
// baseContext contains context info that is valid for
// lifetime of this app (eg. chainID)
baseContext weave.Context
// blockContext contains context info that is valid for the
// current block (eg. height, header), reset on BeginBlock
blockContext weave.Context
}
// NewStoreApp initializes this app into a ready state with some defaults
//
// panics if unable to properly load the state from the given store
// TODO: is this correct? nothing else to do really....
func NewStoreApp(name string, store weave.CommitKVStore,
queryRouter weave.QueryRouter, baseContext weave.Context) *StoreApp {
s := &StoreApp{
name: name,
// note: panics if trouble initializing from store
store: NewCommitStore(store),
queryRouter: queryRouter,
baseContext: baseContext,
}
s = s.WithLogger(log.NewNopLogger())
// load the chainID from the db
s.chainID = mustLoadChainID(s.DeliverStore())
if s.chainID != "" {
s.baseContext = weave.WithChainID(s.baseContext, s.chainID)
}
// get the most recent height
info, err := s.store.CommitInfo()
if err != nil {
panic(err)
}
s.blockContext = weave.WithHeight(s.baseContext, info.Version)
return s
}
// GetChainID returns the current chainID
func (s *StoreApp) GetChainID() string {
return s.chainID
}
// WithInit is used to set the init function we call
func (s *StoreApp) WithInit(init weave.Initializer) *StoreApp {
s.initializer = init
return s
}
// parseAppState is called from InitChain, the first time the chain
// starts, and not on restarts.
func (s *StoreApp) parseAppState(data []byte, params weave.GenesisParams, chainID string, init weave.Initializer) error {
if s.chainID != "" {
return fmt.Errorf("appState previously loaded for chain: %s", s.chainID)
}
if len(data) == 0 {
return fmt.Errorf("app_state not set in genesis.json, please initialize application before launching the blockchain")
}
var appState weave.Options
err := json.Unmarshal(data, &appState)
if err != nil {
return errors.Wrap(errors.ErrState, err.Error())
}
err = s.storeChainID(chainID)
if err != nil {
return err
}
return init.FromGenesis(appState, params, s.DeliverStore())
}
// store chainID and update context
func (s *StoreApp) storeChainID(chainID string) error {
// set the chainID
s.chainID = chainID
err := saveChainID(s.DeliverStore(), s.chainID)
if err != nil {
return err
}
// and update the context
s.baseContext = weave.WithChainID(s.baseContext, s.chainID)
return nil
}
// WithLogger sets the logger on the StoreApp and returns it,
// to make it easy to chain in initialization
//
// also sets baseContext logger
func (s *StoreApp) WithLogger(logger log.Logger) *StoreApp {
s.baseContext = weave.WithLogger(s.baseContext, logger)
s.logger = logger
return s
}
// Logger returns the application base logger
func (s *StoreApp) Logger() log.Logger {
return s.logger
}
// BlockContext returns the block context for public use
func (s *StoreApp) BlockContext() weave.Context {
return s.blockContext
}
// DeliverStore returns the current DeliverTx cache for methods
func (s *StoreApp) DeliverStore() weave.CacheableKVStore {
return s.store.deliver
}
// CheckStore returns the current CheckTx cache for methods
func (s *StoreApp) CheckStore() weave.CacheableKVStore {
return s.store.check
}
//----------------------- ABCI ---------------------
// Info implements abci.Application. It returns the height and hash,
// as well as the abci name and version.
//
// The height is the block that holds the transactions, not the apphash itself.
func (s *StoreApp) Info(req abci.RequestInfo) abci.ResponseInfo {
info, err := s.store.CommitInfo()
if err != nil {
// sorry, nothing else we can return to server
panic(err)
}
s.logger.Info("Info synced",
"height", info.Version,
"hash", fmt.Sprintf("%X", info.Hash))
return abci.ResponseInfo{
Data: s.name,
LastBlockHeight: info.Version,
LastBlockAppHash: info.Hash,
}
}
// SetOption - ABCI
// TODO: not implemented (ABCI spec still unclear....)
func (s *StoreApp) SetOption(res abci.RequestSetOption) abci.ResponseSetOption {
return abci.ResponseSetOption{Log: "Not Implemented"}
}
/*
Query gets data from the app store.
A query request has the following elements:
* Path - the type of query
* Data - what to query, interpreted based on Path
* Height - the block height to query (if 0 most recent)
* Prove - if true, also return a proof
Path may be "/", "/<bucket>", or "/<bucket>/<index>"
It may be followed by "?prefix" to make a prefix query.
Soon we will support "?range" for powerful range queries
Key and Value in Results are always serialized ResultSet
objects, able to support 0 to N values. They must be the
same size. This makes things a little more difficult for
simple queries, but provides a consistent interface.
*/
func (s *StoreApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
// find the handler
path, mod := splitPath(reqQuery.Path)
qh := s.queryRouter.Handler(path)
if qh == nil {
code, _ := errors.ABCIInfo(errors.ErrNotFound, false)
resQuery.Code = code
resQuery.Log = fmt.Sprintf("Unexpected Query path: %v", reqQuery.Path)
return
}
// TODO: support historical queries by getting old read-only
// height := reqQuery.Height
// if height == 0 {
// withProof := s.CommittedHeight() - 1
// if tree.Tree.VersionExists(uint64(withProof)) {
// height = withProof
// } else {
// height = s.CommittedHeight()
// }
// }
info, err := s.store.CommitInfo()
if err != nil {
return queryError(err)
}
resQuery.Height = info.Version
// TODO: better version handling!
db := s.store.committed.CacheWrap()
// make the query
models, err := qh.Query(db, mod, reqQuery.Data)
if err != nil {
return queryError(err)
}
// set the info as ResultSets....
resQuery.Key, err = ResultsFromKeys(models).Marshal()
if err != nil {
return queryError(err)
}
resQuery.Value, err = ResultsFromValues(models).Marshal()
if err != nil {
return queryError(err)
}
// TODO: support proofs given this info....
// if reqQuery.Prove {
// value, proof, err := tree.GetVersionedWithProof(key, height)
// if err != nil {
// resQuery.Log = err.Error()
// break
// }
// resQuery.Value = value
// resQuery.Proof = proof.Bytes()
return resQuery
}
// splitPath splits out the real path along with the query
// modifier (everything after the ?)
func splitPath(path string) (string, string) {
var mod string
chunks := strings.SplitN(path, "?", 2)
if len(chunks) == 2 {
path = chunks[0]
mod = chunks[1]
}
return path, mod
}
func queryError(err error) abci.ResponseQuery {
code, log := errors.ABCIInfo(err, false)
return abci.ResponseQuery{
Log: log,
Code: code,
}
}
// Commit implements abci.Application
func (s *StoreApp) Commit() (res abci.ResponseCommit) {
commitID, err := s.store.Commit()
if err != nil {
// abci interface doesn't allow returning errors here, so just die
panic(err)
}
s.logger.Debug("Commit synced",
"height", commitID.Version,
"hash", fmt.Sprintf("%X", commitID.Hash),
)
return abci.ResponseCommit{Data: commitID.Hash}
}
// InitChain implements ABCI
// Note: in tendermint 0.17, the genesis file is passed
// in here, we should use this to trigger reading the genesis now
// TODO: investigate validators and consensusParams in response
func (s *StoreApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) {
err := s.parseAppState(req.AppStateBytes, weave.FromInitChain(req), req.ChainId, s.initializer)
if err != nil {
// Read comment on type header
panic(err)
}
return abci.ResponseInitChain{}
}
// BeginBlock implements ABCI
// Sets up blockContext
// TODO: investigate response tags as of 0.11 abci
func (s *StoreApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
// set the begin block context
ctx := weave.WithHeader(s.baseContext, req.Header)
ctx = weave.WithHeight(ctx, req.Header.GetHeight())
ctx = weave.WithCommitInfo(ctx, req.LastCommitInfo)
now := req.Header.GetTime()
if now.IsZero() {
panic("current time not found in the block header")
}
ctx = weave.WithBlockTime(ctx, now)
s.blockContext = ctx
return res
}
// EndBlock - ABCI
// Returns a list of all validator changes made in this block
// TODO: investigate response tags as of 0.11 abci
func (s *StoreApp) EndBlock(_ abci.RequestEndBlock) (res abci.ResponseEndBlock) {
res.ValidatorUpdates = weave.ValidatorUpdatesToABCI(s.pending)
s.pending = weave.ValidatorUpdates{}
return
}
// AddValChange is meant to be called by apps on DeliverTx
// results, this is added to the cache for the endblock changeset
func (s *StoreApp) AddValChange(diffs []weave.ValidatorUpdate) {
s.pending.ValidatorUpdates = append(s.pending.ValidatorUpdates, diffs...)
// ensures multiple updates for one validator are combined into one slot
s.pending = s.pending.Deduplicate(false)
}