1 package block
2 3 import (
4 "bytes"
5 "fmt"
6 "github.com/p9c/p9/pkg/util"
7 "io"
8 9 "github.com/p9c/p9/pkg/chainhash"
10 "github.com/p9c/p9/pkg/wire"
11 )
12 13 // OutOfRangeError describes an error due to accessing an element that is out of range.
14 type OutOfRangeError string
15 16 // BlockHeightUnknown is the value returned for a block height that is unknown. This is typically because the block has
17 // not been inserted into the main chain yet.
18 const BlockHeightUnknown = int32(-1)
19 20 // Error satisfies the error interface and prints human-readable errors.
21 func (e OutOfRangeError) Error() string {
22 return string(e)
23 }
24 25 // SetBlockBytes sets the internal serialized block byte buffer to the passed buffer. It is used to inject errors and is
26 // only available to the test package.
27 func (b *Block) SetBlockBytes(buf []byte) {
28 b.serializedBlock = buf
29 }
30 31 // Block defines a bitcoin block that provides easier and more efficient manipulation of raw blocks. It also memorizes
32 // hashes for the block and its transactions on their first access so subsequent accesses don't have to repeat the
33 // relatively expensive hashing operations.
34 type Block struct {
35 msgBlock *wire.Block // Underlying WireBlock
36 serializedBlock []byte // Serialized bytes for the block
37 serializedBlockNoWitness []byte // Serialized bytes for block w/o witness data
38 blockHash *chainhash.Hash // Cached block hash
39 blockHeight int32 // Height in the main block chain
40 transactions []*util.Tx // Transactions
41 txnsGenerated bool // ALL wrapped transactions generated
42 }
43 44 // WireBlock returns the underlying wire.Block for the Block.
45 func (b *Block) WireBlock() *wire.Block {
46 // Return the cached block.
47 return b.msgBlock
48 }
49 50 // Bytes returns the serialized bytes for the Block.
51 // This is equivalent to calling Serialize on the underlying wire.Block,
52 // however it caches the result so subsequent calls are more efficient.
53 func (b *Block) Bytes() ([]byte, error) {
54 // Return the cached serialized bytes if it has already been generated.
55 if len(b.serializedBlock) != 0 {
56 return b.serializedBlock, nil
57 }
58 // Serialize the Block.
59 w := bytes.NewBuffer(make([]byte, 0, b.msgBlock.SerializeSize()))
60 e := b.msgBlock.Serialize(w)
61 if e != nil {
62 return nil, e
63 }
64 serializedBlock := w.Bytes()
65 // Cache the serialized bytes and return them.
66 b.serializedBlock = serializedBlock
67 return serializedBlock, nil
68 }
69 70 // BytesNoWitness returns the serialized bytes for the block with transactions
71 // encoded without any witness data.
72 func (b *Block) BytesNoWitness() ([]byte, error) {
73 // Return the cached serialized bytes if it has already been generated.
74 if len(b.serializedBlockNoWitness) != 0 {
75 return b.serializedBlockNoWitness, nil
76 }
77 // Serialize the Block.
78 var w bytes.Buffer
79 e := b.msgBlock.SerializeNoWitness(&w)
80 if e != nil {
81 return nil, e
82 }
83 serializedBlock := w.Bytes()
84 // Cache the serialized bytes and return them.
85 b.serializedBlockNoWitness = serializedBlock
86 return serializedBlock, nil
87 }
88 89 // Hash returns the block identifier hash for the Block. This is equivalent to calling BlockHash on the underlying
90 // wire.Block, however it caches the result so subsequent calls are more efficient.
91 func (b *Block) Hash() *chainhash.Hash {
92 // Return the cached block hash if it has already been generated.
93 if b.blockHash != nil {
94 return b.blockHash
95 }
96 // Cache the block hash and return it.
97 hash := b.msgBlock.BlockHash()
98 b.blockHash = &hash
99 return &hash
100 }
101 102 // Tx returns a wrapped transaction (util.Tx) for the transaction at the specified index in the Block. The supplied
103 // index is 0 based. That is to say, the first transaction in the block is txNum 0. This is nearly equivalent to
104 // accessing the raw transaction (wire.MsgTx) from the underlying wire.Block, however the wrapped transaction has
105 // some helpful properties such as caching the hash so subsequent calls are more efficient.
106 func (b *Block) Tx(txNum int) (*util.Tx, error) {
107 // Ensure the requested transaction is in range.
108 numTx := uint64(len(b.msgBlock.Transactions))
109 if txNum < 0 || uint64(txNum) > numTx {
110 str := fmt.Sprintf(
111 "transaction index %d is out of range - max %d",
112 txNum, numTx-1,
113 )
114 return nil, OutOfRangeError(str)
115 }
116 // Generate slice to hold all of the wrapped transactions if needed.
117 if len(b.transactions) == 0 {
118 b.transactions = make([]*util.Tx, numTx)
119 }
120 // Return the wrapped transaction if it has already been generated.
121 if b.transactions[txNum] != nil {
122 return b.transactions[txNum], nil
123 }
124 // Generate and cache the wrapped transaction and return it.
125 newTx := util.NewTx(b.msgBlock.Transactions[txNum])
126 newTx.SetIndex(txNum)
127 b.transactions[txNum] = newTx
128 return newTx, nil
129 }
130 131 // Transactions returns a slice of wrapped transactions (util.Tx) for all transactions in the Block. This is nearly
132 // equivalent to accessing the raw transactions (wire.MsgTx) in the underlying wire.Block, however it instead
133 // provides easy access to wrapped versions (util.Tx) of them.
134 func (b *Block) Transactions() []*util.Tx {
135 // Return transactions if they have ALL already been generated. This flag is necessary because the wrapped
136 // transactions are lazily generated in a sparse fashion.
137 if b.txnsGenerated {
138 return b.transactions
139 }
140 // Generate slice to hold all of the wrapped transactions if needed.
141 if len(b.transactions) == 0 {
142 b.transactions = make([]*util.Tx, len(b.msgBlock.Transactions))
143 }
144 // Generate and cache the wrapped transactions for all that haven't already been done.
145 for i, tx := range b.transactions {
146 if tx == nil {
147 newTx := util.NewTx(b.msgBlock.Transactions[i])
148 newTx.SetIndex(i)
149 b.transactions[i] = newTx
150 }
151 }
152 b.txnsGenerated = true
153 return b.transactions
154 }
155 156 // TxHash returns the hash for the requested transaction number in the Block. The supplied index is 0 based. That is to
157 // say, the first transaction in the block is txNum 0. This is equivalent to calling TxHash on the underlying
158 // wire.MsgTx, however it caches the result so subsequent calls are more efficient.
159 func (b *Block) TxHash(txNum int) (*chainhash.Hash, error) {
160 // Attempt to get a wrapped transaction for the specified index. It will be created lazily if needed or simply
161 // return the cached version if it has already been generated.
162 tx, e := b.Tx(txNum)
163 if e != nil {
164 return nil, e
165 }
166 // Defer to the wrapped transaction which will return the cached hash if it has already been generated.
167 return tx.Hash(), nil
168 }
169 170 // TxLoc returns the offsets and lengths of each transaction in a raw block. It is used to allow fast indexing into
171 // transactions within the raw byte stream.
172 func (b *Block) TxLoc() ([]wire.TxLoc, error) {
173 rawMsg, e := b.Bytes()
174 if e != nil {
175 return nil, e
176 }
177 rbuf := bytes.NewBuffer(rawMsg)
178 var mblock wire.Block
179 txLocs, e := mblock.DeserializeTxLoc(rbuf)
180 if e != nil {
181 return nil, e
182 }
183 return txLocs, e
184 }
185 186 // Height returns the saved height of the block in the block chain. This value will be BlockHeightUnknown if it hasn't
187 // already explicitly been set.
188 func (b *Block) Height() int32 {
189 return b.blockHeight
190 }
191 192 // SetHeight sets the height of the block in the block chain.
193 func (b *Block) SetHeight(height int32) {
194 b.blockHeight = height
195 }
196 197 // NewBlock returns a new instance of a bitcoin block given an underlying wire.Block. See Block.
198 func NewBlock(msgBlock *wire.Block) *Block {
199 return &Block{
200 msgBlock: msgBlock,
201 blockHeight: BlockHeightUnknown,
202 }
203 }
204 205 // NewFromBytes returns a new instance of a bitcoin block given the serialized bytes. See Block.
206 func NewFromBytes(serializedBlock []byte) (*Block, error) {
207 br := bytes.NewReader(serializedBlock)
208 b, e := NewFromReader(br)
209 if e != nil {
210 return nil, e
211 }
212 b.serializedBlock = serializedBlock
213 return b, nil
214 }
215 216 // NewFromReader returns a new instance of a bitcoin block given a Reader to deserialize the block. See Block.
217 func NewFromReader(r io.Reader) (*Block, error) {
218 // Deserialize the bytes into a Block.
219 var msgBlock wire.Block
220 e := msgBlock.Deserialize(r)
221 if e != nil {
222 return nil, e
223 }
224 b := Block{
225 msgBlock: &msgBlock,
226 blockHeight: BlockHeightUnknown,
227 }
228 return &b, nil
229 }
230 231 // NewFromBlockAndBytes returns a new instance of a bitcoin block given an underlying wire.Block and the
232 // serialized bytes for it. See Block.
233 func NewFromBlockAndBytes(msgBlock *wire.Block, serializedBlock []byte) *Block {
234 return &Block{
235 msgBlock: msgBlock,
236 serializedBlock: serializedBlock,
237 blockHeight: BlockHeightUnknown,
238 }
239 }
240