forked from btcsuite/btcutil
-
Notifications
You must be signed in to change notification settings - Fork 0
/
block.go
245 lines (212 loc) · 7.78 KB
/
block.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
// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcutil
import (
"bytes"
"fmt"
"io"
"github.com/btcsuite/btcwire"
)
// OutOfRangeError describes an error due to accessing an element that is out
// of range.
type OutOfRangeError string
// BlockHeightUnknown is the value returned for a block height that is unknown.
// This is typically because the block has not been inserted into the main chain
// yet.
const BlockHeightUnknown = int64(-1)
// Error satisfies the error interface and prints human-readable errors.
func (e OutOfRangeError) Error() string {
return string(e)
}
// Block defines a bitcoin block that provides easier and more efficient
// manipulation of raw blocks. It also memoizes hashes for the block and its
// transactions on their first access so subsequent accesses don't have to
// repeat the relatively expensive hashing operations.
type Block struct {
msgBlock *btcwire.MsgBlock // Underlying MsgBlock
serializedBlock []byte // Serialized bytes for the block
blockSha *btcwire.ShaHash // Cached block hash
blockHeight int64 // Height in the main block chain
transactions []*Tx // Transactions
txnsGenerated bool // ALL wrapped transactions generated
}
// MsgBlock returns the underlying btcwire.MsgBlock for the Block.
func (b *Block) MsgBlock() *btcwire.MsgBlock {
// Return the cached block.
return b.msgBlock
}
// Bytes returns the serialized bytes for the Block. This is equivalent to
// calling Serialize on the underlying btcwire.MsgBlock, however it caches the
// result so subsequent calls are more efficient.
func (b *Block) Bytes() ([]byte, error) {
// Return the cached serialized bytes if it has already been generated.
if len(b.serializedBlock) != 0 {
return b.serializedBlock, nil
}
// Serialize the MsgBlock.
var w bytes.Buffer
err := b.msgBlock.Serialize(&w)
if err != nil {
return nil, err
}
serializedBlock := w.Bytes()
// Cache the serialized bytes and return them.
b.serializedBlock = serializedBlock
return serializedBlock, nil
}
// Sha returns the block identifier hash for the Block. This is equivalent to
// calling BlockSha on the underlying btcwire.MsgBlock, however it caches the
// result so subsequent calls are more efficient.
func (b *Block) Sha() (*btcwire.ShaHash, error) {
// Return the cached block hash if it has already been generated.
if b.blockSha != nil {
return b.blockSha, nil
}
// Generate the block hash. Ignore the error since BlockSha can't
// currently fail.
sha, _ := b.msgBlock.BlockSha()
// Cache the block hash and return it.
b.blockSha = &sha
return &sha, nil
}
// Tx returns a wrapped transaction (btcutil.Tx) for the transaction at the
// specified index in the Block. The supplied index is 0 based. That is to
// say, the first transaction in the block is txNum 0. This is nearly
// equivalent to accessing the raw transaction (btcwire.MsgTx) from the
// underlying btcwire.MsgBlock, however the wrapped transaction has some helpful
// properties such as caching the hash so subsequent calls are more efficient.
func (b *Block) Tx(txNum int) (*Tx, error) {
// Ensure the requested transaction is in range.
numTx := uint64(len(b.msgBlock.Transactions))
if txNum < 0 || uint64(txNum) > numTx {
str := fmt.Sprintf("transaction index %d is out of range - max %d",
txNum, numTx-1)
return nil, OutOfRangeError(str)
}
// Generate slice to hold all of the wrapped transactions if needed.
if len(b.transactions) == 0 {
b.transactions = make([]*Tx, numTx)
}
// Return the wrapped transaction if it has already been generated.
if b.transactions[txNum] != nil {
return b.transactions[txNum], nil
}
// Generate and cache the wrapped transaction and return it.
newTx := NewTx(b.msgBlock.Transactions[txNum])
newTx.SetIndex(txNum)
b.transactions[txNum] = newTx
return newTx, nil
}
// Transactions returns a slice of wrapped transactions (btcutil.Tx) for all
// transactions in the Block. This is nearly equivalent to accessing the raw
// transactions (btcwire.MsgTx) in the underlying btcwire.MsgBlock, however it
// instead provides easy access to wrapped versions (btcutil.Tx) of them.
func (b *Block) Transactions() []*Tx {
// Return transactions if they have ALL already been generated. This
// flag is necessary because the wrapped transactions are lazily
// generated in a sparse fashion.
if b.txnsGenerated {
return b.transactions
}
// Generate slice to hold all of the wrapped transactions if needed.
if len(b.transactions) == 0 {
b.transactions = make([]*Tx, len(b.msgBlock.Transactions))
}
// Generate and cache the wrapped transactions for all that haven't
// already been done.
for i, tx := range b.transactions {
if tx == nil {
newTx := NewTx(b.msgBlock.Transactions[i])
newTx.SetIndex(i)
b.transactions[i] = newTx
}
}
b.txnsGenerated = true
return b.transactions
}
// TxSha returns the hash for the requested transaction number in the Block.
// The supplied index is 0 based. That is to say, the first transaction in the
// block is txNum 0. This is equivalent to calling TxSha on the underlying
// btcwire.MsgTx, however it caches the result so subsequent calls are more
// efficient.
func (b *Block) TxSha(txNum int) (*btcwire.ShaHash, error) {
// Attempt to get a wrapped transaction for the specified index. It
// will be created lazily if needed or simply return the cached version
// if it has already been generated.
tx, err := b.Tx(txNum)
if err != nil {
return nil, err
}
// Defer to the wrapped transaction which will return the cached hash if
// it has already been generated.
return tx.Sha(), nil
}
// TxLoc returns the offsets and lengths of each transaction in a raw block.
// It is used to allow fast indexing into transactions within the raw byte
// stream.
func (b *Block) TxLoc() ([]btcwire.TxLoc, error) {
rawMsg, err := b.Bytes()
if err != nil {
return nil, err
}
rbuf := bytes.NewBuffer(rawMsg)
var mblock btcwire.MsgBlock
txLocs, err := mblock.DeserializeTxLoc(rbuf)
if err != nil {
return nil, err
}
return txLocs, err
}
// Height returns the saved height of the block in the block chain. This value
// will be BlockHeightUnknown if it hasn't already explicitly been set.
func (b *Block) Height() int64 {
return b.blockHeight
}
// SetHeight sets the height of the block in the block chain.
func (b *Block) SetHeight(height int64) {
b.blockHeight = height
}
// NewBlock returns a new instance of a bitcoin block given an underlying
// btcwire.MsgBlock. See Block.
func NewBlock(msgBlock *btcwire.MsgBlock) *Block {
return &Block{
msgBlock: msgBlock,
blockHeight: BlockHeightUnknown,
}
}
// NewBlockFromBytes returns a new instance of a bitcoin block given the
// serialized bytes. See Block.
func NewBlockFromBytes(serializedBlock []byte) (*Block, error) {
br := bytes.NewReader(serializedBlock)
b, err := NewBlockFromReader(br)
if err != nil {
return nil, err
}
b.serializedBlock = serializedBlock
return b, nil
}
// NewBlockFromReader returns a new instance of a bitcoin block given a
// Reader to deserialize the block. See Block.
func NewBlockFromReader(r io.Reader) (*Block, error) {
// Deserialize the bytes into a MsgBlock.
var msgBlock btcwire.MsgBlock
err := msgBlock.Deserialize(r)
if err != nil {
return nil, err
}
b := Block{
msgBlock: &msgBlock,
blockHeight: BlockHeightUnknown,
}
return &b, nil
}
// NewBlockFromBlockAndBytes returns a new instance of a bitcoin block given
// an underlying btcwire.MsgBlock and the serialized bytes for it. See Block.
func NewBlockFromBlockAndBytes(msgBlock *btcwire.MsgBlock, serializedBlock []byte) *Block {
return &Block{
msgBlock: msgBlock,
serializedBlock: serializedBlock,
blockHeight: BlockHeightUnknown,
}
}