/
blocks.go
150 lines (138 loc) · 3.96 KB
/
blocks.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
package bitcoin
import (
"errors"
"fmt"
"math"
"strconv"
"github.com/btcsuite/btcutil"
"github.com/xn3cr0nx/bitgodine/internal/block"
"github.com/xn3cr0nx/bitgodine/internal/errorx"
"github.com/xn3cr0nx/bitgodine/internal/storage/kv"
"github.com/xn3cr0nx/bitgodine/internal/tx"
"github.com/xn3cr0nx/bitgodine/pkg/buffer"
"github.com/xn3cr0nx/bitgodine/pkg/logger"
)
// Block composition to enhance btcutil.Block with other receivers
type Block struct {
btcutil.Block
}
// Store prepares the block struct and and call StoreBlock to store it
func (b *Block) Store(db kv.DB, height int32) (err error) {
b.SetHeight(height)
if height%100 == 0 {
logger.Info("Parser Blocks", "Block "+strconv.Itoa(int(b.Height())), logger.Params{"hash": b.Hash().String(), "height": b.Height()})
}
logger.Debug("Parser Blocks", "Storing block", logger.Params{"hash": b.Hash().String(), "height": height})
transactions, err := PrepareTransactions(db, b.Transactions())
if err != nil {
return
}
status := []tx.Status{
{
Confirmed: true,
BlockHeight: b.Height(),
BlockHash: b.Hash().String(),
BlockTime: b.MsgBlock().Header.Timestamp,
},
}
txsRefs := make([]string, len(transactions))
for i, transaction := range transactions {
// TODO: check this assignment, something is wrong here, stored object doesn't have status
transaction.Status = status
txsRefs[i] = transaction.TxID
}
size, err := b.Bytes()
if err != nil {
return
}
weight, err := b.BytesNoWitness()
if err != nil {
return
}
blk := block.Block{
ID: b.Hash().String(),
Height: b.Height(),
Version: b.MsgBlock().Header.Version,
Timestamp: b.MsgBlock().Header.Timestamp,
Bits: b.MsgBlock().Header.Bits,
Nonce: b.MsgBlock().Header.Nonce,
MerkleRoot: b.MsgBlock().Header.MerkleRoot.String(),
Transactions: txsRefs,
TxCount: len(txsRefs),
Size: len(size),
Weight: len(weight),
Previousblockhash: b.MsgBlock().Header.PrevBlock.String(),
}
err = block.NewService(db, nil).StoreBlock(&blk, transactions)
return
}
// CheckBlock checks if block is correctly initialized just checking hash and height fields have some value
func (b *Block) CheckBlock() bool {
return b != nil && b.MsgBlock() != nil && b.Hash() != nil
}
// CoinbaseValue returns the value the block should receive from a coinbase transaction based on number of halving happened due to block height
func CoinbaseValue(height int32) int64 {
return int64(5000000000 / math.Pow(2, float64(height/int32(210000))))
}
// ExtractBlockFromFile reads and remove magic bytes and size from file and returns Block through btcutil.NewBlockFromBytes
func ExtractBlockFromFile(file *[]uint8) (blk *Block, err error) {
for len(*file) > 0 && (*file)[0] == 0 {
*file = (*file)[1:]
}
if len(*file) == 0 {
err = ErrEmptySliceParse
return
}
blockMagic, err := buffer.ReadUint32(file)
if err != nil {
return
}
switch blockMagic {
case 0x00:
err = ErrIncompleteBlockParse
return
case 0xd9b4bef9:
size, e := buffer.ReadUint32(file)
if e != nil {
return nil, e
}
if size < 80 {
err = ErrBlockParse
return
}
block, e := buffer.ReadSlice(file, uint(size))
if e != nil {
return nil, e
}
res, e := btcutil.NewBlockFromBytes(block)
if e != nil {
err = fmt.Errorf("%s: %w", ErrBlockFromBytes.Error(), e)
return
}
blk = &Block{Block: *res}
return
default:
err = ErrMagicBytesMatching
return
}
}
// FileKey key for kv stored parsed files counter
const FileKey = "file"
// StoreFileParsed set file stored so far
func StoreFileParsed(db kv.DB, file int) (err error) {
f := strconv.Itoa(file)
err = db.Store(FileKey, []byte(f))
return
}
// GetFileParsed returnes the file parsed so far
func GetFileParsed(db kv.DB) (file int, err error) {
f, err := db.Read(FileKey)
if err != nil {
if errors.Is(err, errorx.ErrKeyNotFound) {
return 0, nil
}
return
}
file, err = strconv.Atoi(string(f))
return
}