/
blockchain_create.go
437 lines (322 loc) · 10.6 KB
/
blockchain_create.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
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
package nodemanager
import (
"crypto/ecdsa"
"errors"
"fmt"
"github.com/gelembjuk/oursql/lib/net"
"github.com/gelembjuk/oursql/lib/nodeclient"
"github.com/gelembjuk/oursql/lib/remoteclient"
"github.com/gelembjuk/oursql/lib/utils"
"github.com/gelembjuk/oursql/node/blockchain"
"github.com/gelembjuk/oursql/node/consensus"
"github.com/gelembjuk/oursql/node/structures"
"github.com/gelembjuk/oursql/node/transactions"
)
type makeBlockchain struct {
Logger *utils.LoggerMan
MinterAddress string
PubKey []byte
PrivateKey ecdsa.PrivateKey
BC *NodeBlockchain
DBConn *Database
consensusConfig *consensus.ConsensusConfig
}
// Blockchain DB manager object
func (n *makeBlockchain) getBCManager() *blockchain.Blockchain {
bcm, _ := blockchain.NewBlockchainManager(n.DBConn.DB(), n.Logger)
return bcm
}
// Init SQL transactions manager
func (n *makeBlockchain) getSQLQueryManager() (consensus.SQLTransactionsInterface, error) {
return consensus.NewSQLQueryManager(n.consensusConfig, n.DBConn.DB(), n.Logger, n.PubKey, n.PrivateKey)
}
// Transactions manager object
func (n *makeBlockchain) getTransactionsManager() transactions.TransactionsManagerInterface {
return transactions.NewManager(n.DBConn.DB(), n.Logger, n.consensusConfig.GetInfoForTransactions())
}
// Init block maker object. It is used to make new blocks
func (n *makeBlockchain) getBlockMakeManager() consensus.BlockMakerInterface {
return consensus.NewBlockMakerManager(n.consensusConfig, n.MinterAddress, n.DBConn.DB(), n.Logger)
}
// Create new blockchain, add genesis block witha given text
func (n *makeBlockchain) CreateBlockchain(genesisCoinbaseData string, initOnNotEmpty bool) error {
// firstly, check if DB is accesible
n.Logger.Trace.Printf("Check DB connection is fine")
tables, err := n.DBConn.GetAllTables()
if err != nil {
return err
}
tables = n.filterTablesForManaged(tables)
// check if a DB is empty or not. load list of existent tables
if len(tables) > 0 && !initOnNotEmpty {
return errors.New("The DB is not empty. It is not allowed to init on non-empty DB")
}
genesisBlock, err := n.prepareGenesisBlock(n.MinterAddress, genesisCoinbaseData)
if err != nil {
return err
}
Minter := n.getBlockMakeManager()
//n.Logger.Trace.Printf("Complete genesis block proof of work\n")
Minter.SetPreparedBlock(genesisBlock)
genesisBlock, err = Minter.CompleteBlock()
if err != nil {
return err
}
n.Logger.Trace.Printf("Block ready. Init block chain file\n")
err = n.addFirstBlock(genesisBlock)
if err != nil {
return err
}
n.Logger.Trace.Printf("Blockchain ready! Now looks existent tables\n")
if len(tables) > 0 {
countTables, countRows, err := n.addExistentTables(tables)
if err != nil {
return err
}
n.Logger.Trace.Printf("Existent tables added fine. %d tables and %d total rows", countTables, countRows)
}
return nil
}
// Creates new blockchain DB from given list of blocks
// This would be used when new empty node started and syncs with other nodes
func (n *makeBlockchain) InitBlockchainFromOther(addr net.NodeAddr, nodeclient *nodeclient.NodeClient) (bool, error) {
n.Logger.Trace.Printf("Check DB connection is fine")
err := n.DBConn.CheckConnection()
if err != nil {
// no sence to continue
return false, err
}
n.Logger.Trace.Printf("Try to init blockchain from %s:%d", addr.Host, addr.Port)
err = n.importBlockchainConsensusInfo(addr, nodeclient)
if err != nil {
return false, err
}
result, err := nodeclient.SendGetFirstBlocks(addr)
if err != nil {
return false, err
}
if len(result.Blocks) == 0 {
return false, errors.New("No blocks found on taht node")
}
firstblockbytes := result.Blocks[0]
block, err := structures.NewBlockFromBytes(firstblockbytes)
if err != nil {
return false, err
}
n.Logger.Trace.Printf("Importing first block hash %x", block.Hash)
// make blockchain with single block
err = n.addFirstBlock(block)
if err != nil {
return false, errors.New(fmt.Sprintf("Create DB abd add first block: %", err.Error()))
}
defer n.DBConn.CloseConnection()
MH := block.Height
TXMan := n.getTransactionsManager()
if len(result.Blocks) > 1 {
// add all blocks
skip := true
for _, blockdata := range result.Blocks {
if skip {
skip = false
continue
}
// add this block
block, err := structures.NewBlockFromBytes(blockdata)
if err != nil {
return false, err
}
_, err = n.BC.AddBlock(block)
if err != nil {
return false, err
}
TXMan.BlockAdded(block, true)
MH = block.Height
}
}
return MH == result.Height, nil
}
// Import blockchain consensus info. Config file, plugin etc
func (n *makeBlockchain) importBlockchainConsensusInfo(fromnode net.NodeAddr, nodeclient *nodeclient.NodeClient) error {
// load consensus info
result, err := nodeclient.SendGetConsensusData(fromnode)
if err != nil {
n.Logger.Error.Printf("Failed to import consensus data %s", err.Error())
return err
}
//n.Logger.Trace.Printf("Loaded consensus file with len %d and contents %s", len(result.ConfigFile), string(result.ConfigFile))
return n.consensusConfig.UpdateConfig(result.ConfigFile)
}
// BUilds a genesis block. It is used only to start new blockchain
func (n *makeBlockchain) prepareGenesisBlock(address, genesisCoinbaseData string) (*structures.Block, error) {
if address == "" {
return nil, errors.New("Geneisis block wallet address missed")
}
w := remoteclient.Wallet{}
if !w.ValidateAddress(address) {
return nil, errors.New("Address is not valid")
}
if genesisCoinbaseData == "" {
return nil, errors.New("Geneisis block text missed")
}
cbtx, errc := structures.NewCoinbaseTransaction(address, genesisCoinbaseData, n.consensusConfig.CoinsForBlockMade)
if errc != nil {
return nil, errors.New(fmt.Sprintf("Error creating coinbase TX %s", errc.Error()))
}
genesis := &structures.Block{}
genesis.PrepareNewBlock([]structures.Transaction{*cbtx}, []byte{}, 0)
return genesis, nil
}
// Create new blockchain from given genesis block
func (n *makeBlockchain) addFirstBlock(genesis *structures.Block) error {
n.Logger.Trace.Println("Init DB")
n.DBConn.CloseConnection() // close in case if it was opened before
err := n.DBConn.InitDatabase()
if err != nil {
n.Logger.Error.Printf("Can not init DB: %s", err.Error())
return err
}
bcdb, err := n.DBConn.DB().GetBlockchainObject()
if err != nil {
n.Logger.Error.Printf("Can not create conn object: %s", err.Error())
return err
}
blockdata, err := genesis.Serialize()
if err != nil {
return err
}
err = bcdb.PutBlockOnTop(genesis.Hash, blockdata)
if err != nil {
return err
}
err = bcdb.SaveFirstHash(genesis.Hash)
if err != nil {
return err
}
// add first rec to chain list
err = bcdb.AddToChain(genesis.Hash, []byte{})
if err != nil {
return err
}
n.getTransactionsManager().BlockAdded(genesis, true)
return err
}
// return only tables that should be managed with blockchain. skip tables ignored in consensus config
func (n *makeBlockchain) filterTablesForManaged(tables []string) []string {
managedTables := []string{}
for _, table := range tables {
managed := true
for _, t := range n.consensusConfig.UnmanagedTables {
if table == t {
managed = false
break
}
}
if managed {
managedTables = append(managedTables, table)
}
}
return managedTables
}
// Add existent tables to blockchain
func (n *makeBlockchain) addExistentTables(tables []string) (countTales int, countRows int, err error) {
sqlslist := []string{}
counts := map[string]int{}
// we need to know total count to keep minimal values of transactions per block
totalcount := 0
totalloaded := 0
// get counts of rows per table
for _, table := range tables {
counts[table], err = n.DBConn.DB().QM().ExecuteSQLCountInTable(table)
if err != nil {
return
}
totalcount = totalcount + 1 // table create SQL
totalcount = totalcount + counts[table]
}
limit := 1000
nextBlockHeigh := 1
var sqls []string
for _, table := range tables {
offset := 0
for offset <= counts[table] {
limitL := limit - len(sqlslist)
if limitL < 1 {
limitL = limit
}
sqls, err = n.DBConn.DB().QM().ExecuteSQLTableDump(table, limitL, offset)
if err != nil {
return
}
sqlslist = append(sqlslist, sqls...)
totalloaded = totalloaded + len(sqls)
n.Logger.Trace.Printf("SQLs in list %d, limit %d, totalcount %d, total loaded %d", len(sqlslist), limitL, totalcount, totalloaded)
offset = offset + len(sqls)
// check if it is time to make new block
if len(sqlslist) >= limit || totalcount == totalloaded {
n.Logger.Trace.Printf("check 1 passed")
// if not more minimum than nothing to do
// we should not do new block if remaining part of rows is less than required for next block after current
if totalcount-totalloaded > nextBlockHeigh+1 || totalcount == totalloaded {
n.Logger.Trace.Printf("check 2 passed")
// there are more records to load and it will be anough for next block
err = n.makeNewInitialBlock(sqlslist, nextBlockHeigh)
if err != nil {
return
}
sqlslist = []string{}
nextBlockHeigh = nextBlockHeigh + 1
}
}
}
}
if len(sqlslist) > 0 {
n.Logger.Trace.Printf("Do final block")
// make final block
err = n.makeNewInitialBlock(sqlslist, nextBlockHeigh)
if err != nil {
return
}
}
return len(tables), totalcount, nil
}
//Make new initial block from prepared SQLs
func (n *makeBlockchain) makeNewInitialBlock(sqls []string, nextBlockHeigh int) error {
n.Logger.Trace.Printf("DO next block with %d transactions in t")
n.consensusConfig.ExtendRulesApplyStartHeigh(nextBlockHeigh)
qm, err := n.getSQLQueryManager()
if err != nil {
return err
}
for _, sql := range sqls {
_, err := qm.NewQueryByNodeInit(sql, n.PubKey, n.PrivateKey)
if err != nil {
return err
}
}
// make new block
Minter := consensus.NewBlockMakerManager(n.consensusConfig, n.MinterAddress, n.DBConn.DB(), n.Logger)
prepres, err := Minter.PrepareNewBlock()
if err != nil {
return err
}
// close it while doing the proof of work
n.DBConn.CloseConnection()
// and close it again in the end of function
defer n.DBConn.CloseConnection()
if prepres != consensus.BlockPrepare_Done {
return errors.New("Block an not be done. Somethign went wrong")
}
block, err := Minter.CompleteBlock()
if err != nil {
n.Logger.Trace.Printf("Block completion error. %s", err)
return err
}
// add new block to local blockchain. this will check a block again
_, err = n.BC.AddBlock(block)
if err != nil {
n.Logger.Trace.Printf("Block add error. %s", err)
return err
}
n.getTransactionsManager().BlockAdded(block, true)
return nil
}