-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathblockchain.go
600 lines (523 loc) · 13.7 KB
/
blockchain.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
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
package blc
import (
"bytes"
"context"
"fmt"
"github.com/pkg/errors"
"github.com/treeforest/easyblc/dao"
"github.com/treeforest/easyblc/script"
log "github.com/treeforest/logger"
"math/big"
"os"
"sync"
"time"
)
const (
COIN = 100000000 // 1个币 = 100,000,000 聪
)
type BlockChain struct {
dao *dao.DAO // data access object
utxoSet *UTXOSet // utxo set
txPool *TxPool // transaction pool
latestBlock *Block // 最新区块
locker sync.RWMutex
}
func GetBlockChain(dbPath string) *BlockChain {
if dao.IsNotExistDB(dbPath) {
return &BlockChain{
dao: dao.New(dbPath),
utxoSet: NewUTXOSet(),
txPool: NewTxPool(),
latestBlock: nil,
}
}
return MustGetExistBlockChain(dbPath)
}
func MustGetExistBlockChain(dbPath string) *BlockChain {
if dao.IsNotExistDB(dbPath) {
log.Fatal("block database not exist")
}
d, err := dao.Load(dbPath)
if err != nil {
log.Warnf("load block database failed: ", err)
return &BlockChain{
dao: d,
utxoSet: NewUTXOSet(),
txPool: NewTxPool(),
latestBlock: nil,
}
}
blc := &BlockChain{
dao: d,
txPool: NewTxPool(),
}
err = blc.updateUTXOSet()
if err != nil {
log.Warn("update utxo set failed:", err)
}
err = blc.loadLatestBlock()
if err != nil {
log.Fatal("load latest block failed:", err)
}
return blc
}
func CreateBlockChainWithGenesisBlock(dbPath, address string) *BlockChain {
if dao.IsNotExistDB(dbPath) {
if err := os.MkdirAll(dbPath, 7777); err != nil {
log.Fatal("block database not exist: ", err)
}
}
d := dao.New(dbPath)
blc := &BlockChain{
dao: d,
utxoSet: NewUTXOSet(),
txPool: NewTxPool(),
latestBlock: nil,
}
blc.MineGenesisBlock(context.Background(), address)
return blc
}
func (chain *BlockChain) MineGenesisBlock(ctx context.Context, address string) {
if !IsValidAddress(address) {
log.Fatalf("invalid address: %s", address)
}
reward := chain.GetBlockSubsidy(0)
coinbaseTx, err := NewCoinbaseTransaction(reward, address, []byte("挖矿不容易,且挖且珍惜"))
if err != nil {
log.Fatal("create coinbase transaction failed:", err)
}
block, succ := CreateGenesisBlock(ctx, []Transaction{*coinbaseTx})
if !succ {
log.Warn("create genesis block failed")
return
}
err = chain.AddBlock(block)
if err != nil {
log.Error("add genesis block to chain failed:", err)
}
}
func (chain *BlockChain) loadLatestBlock() error {
data, err := chain.dao.GetBlock(chain.dao.GetLatestBlockHash())
if err != nil {
return fmt.Errorf("get latest block failed: %v", err)
}
var block Block
err = block.Unmarshal(data)
if err != nil {
return fmt.Errorf("block unmarshal failed: %v", err)
}
chain.locker.Lock()
chain.latestBlock = &block
chain.locker.Unlock()
return nil
}
func (chain *BlockChain) AddBlock(block *Block) error {
var preBlock *Block = nil
if block.Height > 0 {
var err error
preBlock, err = chain.GetAncestor(block.Height - 1)
if err != nil {
return errors.WithStack(err)
}
}
// 验证区块合法性
if ok := chain.VerifyBlock(preBlock, block); !ok {
return fmt.Errorf("invalid block")
}
blockBytes, err := block.Marshal()
if err != nil {
return fmt.Errorf("block marshal failed:%v", err)
}
err = chain.dao.AddBlock(block.Height, block.Hash, blockBytes)
if err != nil {
return fmt.Errorf("add block to db failed:%v", err)
}
chain.locker.Lock()
chain.latestBlock = block
chain.locker.Unlock()
// 更新交易池/UTXO
for _, tx := range block.Transactions {
chain.txPool.Remove(tx.Hash) // 从交易池中移除交易
for _, vin := range tx.Vins {
// 移除UTXO
chain.utxoSet.Remove(vin.TxId, int(vin.Vout))
}
}
return nil
}
func (chain *BlockChain) VerifyBlock(preBlock, block *Block) bool {
if block == nil {
return false
}
// 若是初始区块
if block.Height == 0 {
if block.PreHash != [32]byte{} {
log.Warn("invalid prehash with genesis block")
return false
}
if block.Version != 1 {
log.Warn("invalid version with genesis block")
return false
}
// TODO: nBits 检查
return true
}
if preBlock == nil {
return false
}
// 1、检查hash
hashBytes := block.CalculateHash()
if hashBytes != block.Hash {
log.Warn("block hash error, required:%s actual:%s", hashBytes, block.Hash)
return false
}
// 2、检查难度目标值
nBits, err := chain.GetWorkRequired(preBlock, block)
if err != nil {
log.Warn("get work required failed:", err)
return false
}
if nBits != block.Bits {
log.Warn("bits not equal, required:%d actual:%d", nBits, block.Bits)
return false
}
// 3、检查工作量证明
target := UnCompact(block.Bits)
hashInt := new(big.Int).SetBytes(hashBytes[:])
if target.Cmp(hashInt) != 1 {
log.Warn("nonce error")
return false
}
// 4、检查前preHash
if block.PreHash != preBlock.Hash {
return false
}
// 5、检查时间戳,需要小于父区块的时间戳
if block.Time < preBlock.Time {
log.Warn("invalid time, [%d] [%d]", block.Time, preBlock.Time)
return false
}
// 6、检查coinbase交易
// 6.1 至少得有一个coinbase交易
if len(block.Transactions) < 1 {
log.Warn("invalid transactions")
return false
}
// 6.2 第一个交易应该是coinbase交易
coinbaseTx := block.Transactions[0]
if _, ok := coinbaseTx.IsCoinbase(); !ok {
log.Warn("first tx is not coinbase")
return false
}
// 6.3 检查coinbase哈希值
requiredCoinbaseHash, err := coinbaseTx.CalculateHash()
if err != nil {
log.Errorf("calculate coinbase tx hash: %v", err)
return false
}
if coinbaseTx.Hash != requiredCoinbaseHash {
log.Warnf("invalid coinbase txhash, [%x] [%x]", coinbaseTx.Hash, requiredCoinbaseHash)
return false
}
// 7. 检查矿工费(交易费+区块奖励)
// 7.1 获得区块奖励
reward := chain.GetBlockSubsidy(block.Height)
full, ok := block.Transactions[0].IsCoinbase()
if !ok {
log.Warn("first transaction is not coinbase tx")
return false
}
// 7.2 获得交易费
fee := full - reward
if fee < 0 {
log.Warn("invalid fee")
return false
}
// 7.3 解析出输入输出金额
var inputAmount, outputAmount uint64 = 0, 0
for i := 1; i < len(block.Transactions); i++ {
tx := block.Transactions[i]
if _, ok = tx.IsCoinbase(); ok {
// coinbase 交易必须放在第一个交易
log.Warn("invalid coinbase transaction")
return false
}
// 交易的哈希
requiredTxHash, _ := tx.CalculateHash()
if tx.Hash != requiredTxHash {
log.Warnf("invalid txhash, [%x] [%x]", tx.Hash, requiredTxHash)
return false
}
// TODO: 交易时间检查
// 交易的输入输出数量
if len(tx.Vins) == 0 || len(tx.Vouts) == 0 {
log.Warn("invalid vins or vouts")
return false
}
// 交易输入
for _, vin := range tx.Vins {
if vin.IsCoinbase() {
log.Warn("invalid coinbase tx input")
return false
}
txOut := chain.UTXOSet().Get(vin.TxId, int(vin.Vout))
// 交易输入是否合法
if txOut == nil {
log.Warn("invalid tx input") // 没找到对应的交易输出
return false
}
// 输入脚本是否合法
ok = script.Verify(vin.TxId, vin.ScriptSig, txOut.ScriptPubKey)
if !ok {
log.Warn("invalid scriptSig")
return false
}
// 记录输入金额
inputAmount += txOut.Value
}
for _, vout := range tx.Vouts {
if script.IsValidScriptPubKey(vout.ScriptPubKey) == false {
log.Warn("invalid script pub key")
return false
}
outputAmount += vout.Value
}
}
// 7.4 矿工费用 = 交易输入金额 - 交易输出金额
if inputAmount-outputAmount != fee {
log.Warn("invalid inputAmount outputAmount")
return false
}
// 8、检查默克尔树 merkle tree
if !bytes.Equal(block.CalculateMerkleRoot(), block.MerkelRoot) {
log.Warn("invalid merkel root")
return false
}
return true
}
var once sync.Once
func (chain *BlockChain) Close() {
once.Do(func() {
if err := chain.txPool.Close(); err != nil {
log.Fatal("close tx pool failed: ", err)
}
if err := chain.dao.Close(); err != nil {
log.Fatal("close database failed: ", err)
}
})
}
// GetAncestor 获取指定高度区块
func (chain *BlockChain) GetAncestor(height uint64) (*Block, error) {
data, err := chain.dao.GetBlockByHeight(height)
if err != nil {
return nil, errors.New("invalid height")
}
var block Block
err = block.Unmarshal(data)
if err != nil {
return nil, fmt.Errorf("unmarshal block failed:%v", err)
}
return &block, nil
}
// GetLatestBlock 获取最新区块
func (chain *BlockChain) GetLatestBlock() *Block {
chain.locker.RLock()
defer chain.locker.RUnlock()
if chain.latestBlock == nil {
return nil
}
block := *chain.latestBlock
return &block
}
// GetBlockIterator 返回区块迭代器
func (chain *BlockChain) GetBlockIterator() *BlockIterator {
return NewBlockIterator(chain.dao)
}
// Traverse 遍历区块链,若返回false,则终止遍历
func (chain *BlockChain) Traverse(fn func(block *Block) bool) error {
it := chain.GetBlockIterator()
for {
b, err := it.Next()
if err != nil {
return fmt.Errorf("get next block error[%v]", err)
}
if b == nil {
break
}
if fn(b) == false {
return nil
}
}
return nil
}
// IsValidTx 验证交易合法性,并返回矿工费
func (chain *BlockChain) IsValidTx(tx *Transaction) (uint64, bool) {
var inputAmount, outputAmount, fee uint64
// 验证交易哈希
hash, err := tx.CalculateHash()
if err != nil {
log.Debug("calculate tx hash failed:", err)
return 0, false
}
if tx.Hash != hash {
log.Debugf("transaction hash error, dst[%x] src[%x]", hash, tx.Hash)
return 0, false
}
// 判断输入是否合法
for _, vin := range tx.Vins {
if vin.IsCoinbase() {
continue
}
output := chain.utxoSet.Get(vin.TxId, int(vin.Vout))
if output == nil {
log.Debug("invalid transaction input")
return 0, false
}
// 是否为有效地输入脚本格式
ok := script.IsValidScriptSig(vin.ScriptSig)
if !ok {
log.Debug("invalid scriptsig")
return 0, false
}
// 验证锁定脚本与解锁脚本 P2PKH
ok = script.Verify(tx.Hash, vin.ScriptSig, output.ScriptPubKey)
if !ok {
log.Debug("P2PKH verify failed")
return 0, false
}
inputAmount += output.Value
}
for _, vout := range tx.Vouts {
// 是否为有效的地址
if !IsValidAddress(vout.Address) {
log.Debug("invalid output address")
return 0, false
}
// 是否为有效地输出脚本
if !script.IsValidScriptPubKey(vout.ScriptPubKey) {
log.Debug("invalid scriptpubkey")
return 0, false
}
outputAmount += vout.Value
}
if inputAmount < outputAmount {
// 输入金额小于输出金额
log.Debug("inputAmount < outputAmount")
return 0, false
}
fee = inputAmount - outputAmount
return fee, true
}
// RemoveBlockFrom 移除区块高度及往后的区块,当产生分叉,需要合并链时调用
func (chain *BlockChain) RemoveBlockFrom(start uint64) error {
if chain.GetLatestBlock() == nil {
if start == 0 {
return nil
}
return errors.New("no blocks")
}
height := chain.GetLatestBlock().Height
if start < 0 || start > height {
return nil
}
for ; height >= start; height-- {
// TODO: 回收交易 或者 通过网络同步
// 移除区块
_ = chain.dao.RemoveLatestBlock()
}
// 重新设置 latest block
err := chain.loadLatestBlock()
if err != nil {
return fmt.Errorf("local latest block failed:%v", err)
}
// 重置utxo结合
err = chain.resetUTXOSet()
if err != nil {
return fmt.Errorf("reset utxo set failed:%v", err)
}
return nil
}
func (chain *BlockChain) GetBlockHash(height uint64) ([]byte, error) {
return chain.dao.GetBlockHash(height)
}
func (chain *BlockChain) GetBlock(height uint64) (*Block, error) {
if chain.GetLatestBlock() == nil {
return nil, errors.New("blockchain is null")
}
if height < 0 || height > chain.GetLatestBlock().Height {
return nil, errors.New("invalid height")
}
var block Block
data, err := chain.dao.GetBlockByHeight(height)
if err != nil {
return nil, fmt.Errorf("get block by height failed: %v", err)
}
err = block.Unmarshal(data)
if err != nil {
return nil, fmt.Errorf("unmarshal blokc data failed: %v", err)
}
return &block, nil
}
// GetBlockSubsidy 获取区块奖励
func (chain *BlockChain) GetBlockSubsidy(height uint64) uint64 {
var halvings = height / 210000
if halvings > 64 {
return 0
}
subsidy := uint64(50 * COIN)
subsidy = subsidy >> halvings // 每210000个区块奖励减半
return subsidy
}
// MineBlock 挖矿
func (chain *BlockChain) MineBlock(ctx context.Context, address string) error {
last := chain.GetLatestBlock()
if last == nil {
return errors.New("latest block is nil")
}
// 从交易池中取出交易, 并计算奖励:挖矿奖励+矿工费
txs, minerFee := chain.ConsumeTxsFromTxPool(16)
reward := chain.GetBlockSubsidy(chain.GetLatestBlock().Height + 1)
var (
block *Block
succ bool // 挖矿是否成功
)
for {
// 构造创币交易
coinbaseTx, err := NewCoinbaseTransaction(reward+minerFee, address, []byte(time.Now().String()))
if err != nil {
log.Fatal("create coinbase transaction failed:", err)
}
requiredBits := chain.GetNextWorkRequired()
block, succ = NewBlock(ctx, requiredBits, last.Height+1, last.Hash, append([]Transaction{*coinbaseTx}, txs...))
// test
//tmp, _ := json.MarshalIndent(block, "", "\t")
//log.Debug(string(tmp))
select {
case <-ctx.Done():
// 取消挖矿
return nil
default:
select {
case <-ctx.Done():
return nil
default:
}
}
if !succ {
// 挖矿失败,重新构建coinbase交易,继续挖矿
continue
}
// 挖矿成功
err = chain.AddBlock(block)
if err != nil {
return fmt.Errorf("add block to chain failed: %v", err)
}
return nil
}
}
func (chain *BlockChain) GetTxPool() *TxPool {
return chain.txPool
}
func (chain *BlockChain) UTXOSet() *UTXOSet {
return chain.utxoSet
}