1 package mempool
2 3 import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "fmt"
8 "github.com/p9c/p9/pkg/amt"
9 "github.com/p9c/p9/pkg/block"
10 "io"
11 "math"
12 "math/rand"
13 "sort"
14 "strings"
15 "sync"
16 17 "github.com/p9c/p9/pkg/chainhash"
18 "github.com/p9c/p9/pkg/mining"
19 "github.com/p9c/p9/pkg/util"
20 )
21 22 // DUOPerKilobyte is number with units of parallelcoins per kilobyte.
23 type DUOPerKilobyte float64
24 25 // FeeEstimator manages the data necessary to create fee estimations. It is safe for concurrent access.
26 type FeeEstimator struct {
27 maxRollback uint32
28 binSize int32
29 // The maximum number of replacements that can be made in a single bin per block. Default is
30 // estimateFeeMaxReplacements
31 maxReplacements int32
32 // The minimum number of blocks that can be registered with the fee estimator before it will provide answers.
33 minRegisteredBlocks uint32
34 // The last known height.
35 lastKnownHeight int32
36 // The number of blocks that have been registered.
37 numBlocksRegistered uint32
38 mtx sync.RWMutex
39 observed map[chainhash.Hash]*observedTransaction
40 bin [estimateFeeDepth][]*observedTransaction
41 // The cached estimates.
42 cached []SatoshiPerByte
43 // Transactions that have been removed from the bins. This allows us to revert in case of an orphaned block.
44 dropped []*registeredBlock
45 }
46 47 // FeeEstimatorState represents a saved FeeEstimator that can be restored with data from an earlier session of the
48 // program.
49 type FeeEstimatorState []byte
50 51 // SatoshiPerByte is number with units of satoshis per byte.
52 type SatoshiPerByte float64
53 54 // estimateFeeSet is a set of txs that can that is sorted by the fee per kb rate.
55 type estimateFeeSet struct {
56 feeRate []SatoshiPerByte
57 bin [estimateFeeDepth]uint32
58 }
59 60 // observedTransaction represents an observed transaction and some additional data required for the fee estimation
61 // algorithm.
62 type observedTransaction struct {
63 // A transaction hash.
64 hash chainhash.Hash
65 // The fee per byte of the transaction in satoshis.
66 feeRate SatoshiPerByte
67 // The block height when it was observed.
68 observed int32
69 // The height of the block in which it was mined. If the transaction has not yet been mined, it is zero.
70 mined int32
71 }
72 73 // observedTxSet is a set of txs that can that is sorted by hash. It exists for serialization purposes so that a
74 // serialized state always comes out the same.
75 type observedTxSet []*observedTransaction
76 77 // registeredBlock has the hash of a block and the list of transactions it mined which had been previously observed by
78 // the FeeEstimator. It is used if Rollback is called to reverse the effect of registering a block.
79 type registeredBlock struct {
80 hash chainhash.Hash
81 transactions []*observedTransaction
82 }
83 84 // TODO incorporate Alex Morcos' modifications to Gavin's initial model
85 // https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2014-October/006824.html
86 const (
87 // estimateFeeDepth is the maximum number of blocks before a transaction is confirmed that we want to track.
88 estimateFeeDepth = 25
89 // estimateFeeBinSize is the number of txs stored in each bin.
90 estimateFeeBinSize = 100
91 // estimateFeeMaxReplacements is the max number of replacements that can be made by the txs found in a given block.
92 estimateFeeMaxReplacements = 10
93 // DefaultEstimateFeeMaxRollback is the default number of rollbacks allowed by the fee estimator for orphaned
94 // blocks.
95 DefaultEstimateFeeMaxRollback = 2
96 // DefaultEstimateFeeMinRegisteredBlocks is the default minimum number of blocks which must be observed by the fee
97 // estimator before will provide fee estimations.
98 DefaultEstimateFeeMinRegisteredBlocks = 3
99 bytePerKb = 1000
100 duoPerSatoshi = 1e-8
101 )
102 103 // In case the format for the serialized version of the FeeEstimator changes, we use a version number. If the version
104 // number changes, it does not make sense to try to upgrade a previous version to a new version. Instead, just start fee
105 // estimation over.
106 const estimateFeeSaveVersion = 1
107 108 var (
109 // EstimateFeeDatabaseKey is the key that we use to store the fee estimator in the database.
110 EstimateFeeDatabaseKey = []byte("estimatefee")
111 )
112 113 // EstimateFee estimates the fee per byte to have a tx confirmed a given number of blocks from now.
114 func (ef *FeeEstimator) EstimateFee(numBlocks uint32) (DUOPerKilobyte, error) {
115 ef.mtx.Lock()
116 defer ef.mtx.Unlock()
117 // If the number of registered blocks is below the minimum, return an error.
118 if ef.numBlocksRegistered < ef.minRegisteredBlocks {
119 return -1, errors.New("not enough blocks have been observed")
120 }
121 if numBlocks == 0 {
122 return -1, errors.New("cannot confirm transaction in zero blocks")
123 }
124 if numBlocks > estimateFeeDepth {
125 return -1, fmt.Errorf(
126 "can only estimate fees for up to %d blocks from now",
127 estimateFeeBinSize,
128 )
129 }
130 // If there are no cached results, generate them.
131 if ef.cached == nil {
132 ef.cached = ef.estimates()
133 }
134 return ef.cached[int(numBlocks)-1].ToBtcPerKb(), nil
135 }
136 137 func // LastKnownHeight returns the height of the last block which was
138 // registered.
139 (ef *FeeEstimator) LastKnownHeight() int32 {
140 ef.mtx.Lock()
141 defer ef.mtx.Unlock()
142 return ef.lastKnownHeight
143 }
144 145 // ObserveTransaction is called when a new transaction is observed in the mempool.
146 func (ef *FeeEstimator) ObserveTransaction(
147 t *TxDesc,
148 ) {
149 ef.mtx.Lock()
150 defer ef.mtx.Unlock()
151 // If we haven't seen a block yet we don't know when this one arrived, so we ignore it.
152 if ef.lastKnownHeight == mining.UnminedHeight {
153 return
154 }
155 hash := *t.Tx.Hash()
156 if _, ok := ef.observed[hash]; !ok {
157 size := uint32(GetTxVirtualSize(t.Tx))
158 ef.observed[hash] = &observedTransaction{
159 hash: hash,
160 feeRate: NewSatoshiPerByte(amt.Amount(t.Fee), size),
161 observed: t.Height,
162 mined: mining.UnminedHeight,
163 }
164 }
165 }
166 167 // RegisterBlock informs the fee estimator of a new block to take into account.
168 func (ef *FeeEstimator) RegisterBlock(
169 block *block.Block,
170 ) (e error) {
171 ef.mtx.Lock()
172 defer ef.mtx.Unlock()
173 // The previous sorted list is invalid, so delete it.
174 ef.cached = nil
175 height := block.Height()
176 if height != ef.lastKnownHeight+1 && ef.lastKnownHeight != mining.UnminedHeight {
177 return fmt.Errorf(
178 "intermediate block not recorded; current height is %d; new height is %d",
179 ef.lastKnownHeight, height,
180 )
181 }
182 // Update the last known height.
183 ef.lastKnownHeight = height
184 ef.numBlocksRegistered++
185 // Randomly order txs in block.
186 transactions := make(map[*util.Tx]struct{})
187 for _, t := range block.Transactions() {
188 transactions[t] = struct{}{}
189 }
190 // Count the number of replacements we make per bin so that we don't replace too many.
191 var replacementCounts [estimateFeeDepth]int
192 // Keep track of which txs were dropped in case of an orphan block.
193 dropped := ®isteredBlock{
194 hash: *block.Hash(),
195 transactions: make([]*observedTransaction, 0, 100),
196 }
197 // Go through the txs in the block.
198 for t := range transactions {
199 hash := *t.Hash()
200 // Have we observed this tx in the mempool?
201 o, ok := ef.observed[hash]
202 if !ok {
203 continue
204 }
205 // Put the observed tx in the appropriate bin.
206 blocksToConfirm := height - o.observed - 1
207 // This shouldn't happen if the fee estimator works correctly, but return an error if it does.
208 if o.mined != mining.UnminedHeight {
209 E.Ln("Estimate fee: transaction ", hash, " has already been mined")
210 return errors.New("transaction has already been mined")
211 }
212 // This shouldn't happen but check just in case to avoid an out-of -bounds array index later.
213 if blocksToConfirm >= estimateFeeDepth {
214 continue
215 }
216 // Make sure we do not replace too many transactions per min.
217 if replacementCounts[blocksToConfirm] == int(ef.maxReplacements) {
218 continue
219 }
220 o.mined = height
221 replacementCounts[blocksToConfirm]++
222 bin := ef.bin[blocksToConfirm]
223 // Remove a random element and replace it with this new tx.
224 if len(bin) == int(ef.binSize) {
225 // Don't drop transactions we have just added from this same block.
226 l := int(ef.binSize) - replacementCounts[blocksToConfirm]
227 drop := rand.Intn(l)
228 dropped.transactions = append(dropped.transactions, bin[drop])
229 bin[drop] = bin[l-1]
230 bin[l-1] = o
231 } else {
232 bin = append(bin, o)
233 }
234 ef.bin[blocksToConfirm] = bin
235 }
236 // Go through the mempool for txs that have been in too long.
237 for hash, o := range ef.observed {
238 if o.mined == mining.UnminedHeight && height-o.observed >= estimateFeeDepth {
239 delete(ef.observed, hash)
240 }
241 }
242 // Add dropped list to history.
243 if ef.maxRollback == 0 {
244 return nil
245 }
246 if uint32(len(ef.dropped)) == ef.maxRollback {
247 ef.dropped = append(ef.dropped[1:], dropped)
248 } else {
249 ef.dropped = append(ef.dropped, dropped)
250 }
251 return nil
252 }
253 254 // Rollback unregisters a recently registered block from the FeeEstimator. This can be used to reverse the effect of an
255 // orphaned block on the fee estimator. The maximum number of rollbacks allowed is given by maxRollbacks. Note: not
256 // everything can be rolled back because some transactions are deleted if they have been observed too long ago. That
257 // means the result of Rollback won't always be exactly the same as if the last block had not happened, but it should be
258 // close enough.
259 func (ef *FeeEstimator) Rollback(hash *chainhash.Hash) (e error) {
260 ef.mtx.Lock()
261 defer ef.mtx.Unlock()
262 // Find this block in the stack of recent registered blocks.
263 var n int
264 for n = 1; n <= len(ef.dropped); n++ {
265 if ef.dropped[len(ef.dropped)-n].hash.IsEqual(hash) {
266 break
267 }
268 }
269 if n > len(ef.dropped) {
270 return errors.New("no such block was recently registered")
271 }
272 for i := 0; i < n; i++ {
273 ef.rollback()
274 }
275 return nil
276 }
277 278 // Save records the current state of the FeeEstimator to a []byte that can be restored later.
279 func (ef *FeeEstimator) Save() FeeEstimatorState {
280 ef.mtx.Lock()
281 defer ef.mtx.Unlock()
282 // TODO figure out what the capacity should be.
283 w := bytes.NewBuffer(make([]byte, 0))
284 e := binary.Write(
285 w, binary.BigEndian, uint32(estimateFeeSaveVersion),
286 )
287 if e != nil {
288 F.Ln("failed to write fee estimates", e)
289 }
290 // Insert basic parameters.
291 e = binary.Write(w, binary.BigEndian, &ef.maxRollback)
292 if e != nil {
293 F.Ln("failed to write fee estimates", e)
294 }
295 e = binary.Write(w, binary.BigEndian, &ef.binSize)
296 if e != nil {
297 F.Ln("failed to write fee estimates", e)
298 }
299 e = binary.Write(w, binary.BigEndian, &ef.maxReplacements)
300 if e != nil {
301 F.Ln("failed to write fee estimates", e)
302 }
303 e = binary.Write(w, binary.BigEndian, &ef.minRegisteredBlocks)
304 if e != nil {
305 F.Ln("failed to write fee estimates", e)
306 }
307 e = binary.Write(w, binary.BigEndian, &ef.lastKnownHeight)
308 if e != nil {
309 F.Ln("failed to write fee estimates", e)
310 }
311 e = binary.Write(w, binary.BigEndian, &ef.numBlocksRegistered)
312 if e != nil {
313 F.Ln("failed to write fee estimates", e)
314 }
315 // Put all the observed transactions in a sorted list.
316 var txCount uint32
317 ots := make([]*observedTransaction, len(ef.observed))
318 for hash := range ef.observed {
319 ots[txCount] = ef.observed[hash]
320 txCount++
321 }
322 sort.Sort(observedTxSet(ots))
323 txCount = 0
324 observed := make(map[*observedTransaction]uint32)
325 e = binary.Write(w, binary.BigEndian, uint32(len(ef.observed)))
326 if e != nil {
327 F.Ln("failed to write:", e)
328 }
329 for _, ot := range ots {
330 ot.Serialize(w)
331 observed[ot] = txCount
332 txCount++
333 }
334 // Save all the right bins.
335 for _, list := range ef.bin {
336 e = binary.Write(w, binary.BigEndian, uint32(len(list)))
337 if e != nil {
338 F.Ln("failed to write:", e)
339 }
340 for _, o := range list {
341 e = binary.Write(w, binary.BigEndian, observed[o])
342 if e != nil {
343 F.Ln("failed to write:", e)
344 }
345 }
346 }
347 // Dropped transactions.
348 e = binary.Write(w, binary.BigEndian, uint32(len(ef.dropped)))
349 if e != nil {
350 F.Ln("failed to write:", e)
351 }
352 for _, registered := range ef.dropped {
353 registered.serialize(w, observed)
354 }
355 // Commit the tx and return.
356 return w.Bytes()
357 }
358 359 // estimates returns the set of all fee estimates from 1 to estimateFeeDepth confirmations from now.
360 func (ef *FeeEstimator) estimates() []SatoshiPerByte {
361 set := ef.newEstimateFeeSet()
362 estimates := make([]SatoshiPerByte, estimateFeeDepth)
363 for i := 0; i < estimateFeeDepth; i++ {
364 estimates[i] = set.estimateFee(i + 1)
365 }
366 return estimates
367 }
368 369 // newEstimateFeeSet creates a temporary data structure that can be used to find all fee estimates.
370 func (ef *FeeEstimator) newEstimateFeeSet() *estimateFeeSet {
371 set := &estimateFeeSet{}
372 capacity := 0
373 for i, b := range ef.bin {
374 l := len(b)
375 set.bin[i] = uint32(l)
376 capacity += l
377 }
378 set.feeRate = make([]SatoshiPerByte, capacity)
379 i := 0
380 for _, b := range ef.bin {
381 for _, o := range b {
382 set.feeRate[i] = o.feeRate
383 i++
384 }
385 }
386 sort.Sort(set)
387 return set
388 }
389 390 // rollback rolls back the effect of the last block in the stack of registered blocks.
391 func (ef *FeeEstimator) rollback() {
392 // The previous sorted list is invalid, so delete it.
393 ef.cached = nil
394 // pop the last list of dropped txs from the stack.
395 last := len(ef.dropped) - 1
396 if last == -1 {
397 // Cannot really happen because the exported calling function only rolls back a block already known to be in the
398 // list of dropped transactions.
399 return
400 }
401 dropped := ef.dropped[last]
402 // where we are in each bin as we replace txs?
403 var replacementCounters [estimateFeeDepth]int
404 // Go through the txs in the dropped block.
405 for _, o := range dropped.transactions {
406 // Which bin was this tx in?
407 blocksToConfirm := o.mined - o.observed - 1
408 bin := ef.bin[blocksToConfirm]
409 var counter = replacementCounters[blocksToConfirm]
410 // Continue to go through that bin where we left off.
411 for {
412 if counter >= len(bin) {
413 // Panic, as we have entered an unrecoverable invalid state.
414 panic(
415 errors.New(
416 "illegal state: cannot rollback dropped transaction",
417 ),
418 )
419 }
420 prev := bin[counter]
421 if prev.mined == ef.lastKnownHeight {
422 prev.mined = mining.UnminedHeight
423 bin[counter] = o
424 counter++
425 break
426 }
427 counter++
428 }
429 replacementCounters[blocksToConfirm] = counter
430 }
431 // Continue going through bins to find other txs to remove which did not replace any other when they were entered.
432 for i, j := range replacementCounters {
433 for {
434 l := len(ef.bin[i])
435 if j >= l {
436 break
437 }
438 prev := ef.bin[i][j]
439 if prev.mined == ef.lastKnownHeight {
440 prev.mined = mining.UnminedHeight
441 newBin := append(ef.bin[i][0:j], ef.bin[i][j+1:l]...)
442 // TODO This line should prevent an unintentional memory leak
443 // but it causes a panic when it is uncommented.
444 // ef.bin[i][j] = nil
445 ef.bin[i] = newBin
446 continue
447 }
448 j++
449 }
450 }
451 ef.dropped = ef.dropped[0:last]
452 // The number of blocks the fee estimator has seen is decremented.
453 ef.numBlocksRegistered--
454 ef.lastKnownHeight--
455 }
456 func (b *estimateFeeSet) Len() int { return len(b.feeRate) }
457 func (b *estimateFeeSet) Less(i, j int) bool { return b.feeRate[i] > b.feeRate[j] }
458 func (b *estimateFeeSet) Swap(i, j int) {
459 b.feeRate[i], b.feeRate[j] = b.feeRate[j], b.feeRate[i]
460 }
461 462 // estimateFee returns the estimated fee for a transaction to confirm in confirmations blocks from now, given the data
463 // set we have collected.
464 func (b *estimateFeeSet) estimateFee(confirmations int) SatoshiPerByte {
465 if confirmations <= 0 {
466 return SatoshiPerByte(math.Inf(1))
467 }
468 if confirmations > estimateFeeDepth {
469 return 0
470 }
471 // We don't have any transactions!
472 if len(b.feeRate) == 0 {
473 return 0
474 }
475 var min, max int
476 for i := 0; i < confirmations-1; i++ {
477 min += int(b.bin[i])
478 }
479 max = min + int(b.bin[confirmations-1]) - 1
480 if max < min {
481 max = min
482 }
483 feeIndex := (min + max) / 2
484 if feeIndex >= len(b.feeRate) {
485 feeIndex = len(b.feeRate) - 1
486 }
487 return b.feeRate[feeIndex]
488 }
489 func (o *observedTransaction) Serialize(w io.Writer) {
490 e := binary.Write(w, binary.BigEndian, o.hash)
491 if e != nil {
492 F.Ln("failed to serialize observed transaction:", e)
493 }
494 e = binary.Write(w, binary.BigEndian, o.feeRate)
495 if e != nil {
496 F.Ln("failed to serialize observed transaction:", e)
497 }
498 e = binary.Write(w, binary.BigEndian, o.observed)
499 if e != nil {
500 F.Ln("failed to serialize observed transaction:", e)
501 }
502 e = binary.Write(w, binary.BigEndian, o.mined)
503 if e != nil {
504 F.Ln("failed to serialize observed transaction:", e)
505 }
506 }
507 508 func (rb *registeredBlock) serialize(
509 w io.Writer,
510 txs map[*observedTransaction]uint32,
511 ) {
512 e := binary.Write(w, binary.BigEndian, rb.hash)
513 if e != nil {
514 F.Ln("failed to write:", e)
515 }
516 e = binary.Write(w, binary.BigEndian, uint32(len(rb.transactions)))
517 if e != nil {
518 F.Ln("failed to write:", e)
519 }
520 for _, o := range rb.transactions {
521 e = binary.Write(w, binary.BigEndian, txs[o])
522 if e != nil {
523 F.Ln("failed to write:", e)
524 }
525 }
526 }
527 528 // Fee returns the fee for a transaction of a given size for the given fee rate.
529 func (rate SatoshiPerByte) Fee(size uint32) amt.Amount {
530 // If our rate is the error value, return that.
531 if rate == SatoshiPerByte(-1) {
532 return amt.Amount(-1)
533 }
534 return amt.Amount(float64(rate) * float64(size))
535 }
536 537 // ToBtcPerKb returns a float value that represents the given SatoshiPerByte converted to satoshis per kb.
538 func (rate SatoshiPerByte) ToBtcPerKb() DUOPerKilobyte {
539 // If our rate is the error value, return that.
540 if rate == SatoshiPerByte(-1.0) {
541 return -1.0
542 }
543 return DUOPerKilobyte(float64(rate) * bytePerKb * duoPerSatoshi)
544 }
545 546 func (q observedTxSet) Len() int { return len(q) }
547 func (q observedTxSet) Less(i, j int) bool {
548 return strings.Compare(q[i].hash.String(), q[j].hash.String()) < 0
549 }
550 func (q observedTxSet) Swap(i, j int) { q[i], q[j] = q[j], q[i] }
551 552 // NewFeeEstimator creates a FeeEstimator for which at most maxRollback blocks can be unregistered and which returns an
553 // error unless minRegisteredBlocks have been registered with it.
554 func NewFeeEstimator(maxRollback, minRegisteredBlocks uint32) *FeeEstimator {
555 return &FeeEstimator{
556 maxRollback: maxRollback,
557 minRegisteredBlocks: minRegisteredBlocks,
558 lastKnownHeight: mining.UnminedHeight,
559 binSize: estimateFeeBinSize,
560 maxReplacements: estimateFeeMaxReplacements,
561 observed: make(map[chainhash.Hash]*observedTransaction),
562 dropped: make([]*registeredBlock, 0, maxRollback),
563 }
564 }
565 566 // NewSatoshiPerByte creates a SatoshiPerByte from an Amount and a size in bytes.
567 func NewSatoshiPerByte(fee amt.Amount, size uint32) SatoshiPerByte {
568 return SatoshiPerByte(float64(fee) / float64(size))
569 }
570 571 // RestoreFeeEstimator takes a FeeEstimatorState that was previously returned by Save and restores it to a FeeEstimator
572 func RestoreFeeEstimator(data FeeEstimatorState) (*FeeEstimator, error) {
573 r := bytes.NewReader(data)
574 // Chk version
575 var version uint32
576 e := binary.Read(r, binary.BigEndian, &version)
577 if e != nil {
578 return nil, e
579 }
580 if version != estimateFeeSaveVersion {
581 return nil, fmt.Errorf(
582 "incorrect version: expected %d found %d",
583 estimateFeeSaveVersion, version,
584 )
585 }
586 ef := &FeeEstimator{
587 observed: make(map[chainhash.Hash]*observedTransaction),
588 }
589 // Read basic parameters.
590 e = binary.Read(r, binary.BigEndian, &ef.maxRollback)
591 if e != nil {
592 F.Ln("failed to read", e)
593 }
594 e = binary.Read(r, binary.BigEndian, &ef.binSize)
595 if e != nil {
596 F.Ln("failed to read", e)
597 }
598 e = binary.Read(r, binary.BigEndian, &ef.maxReplacements)
599 if e != nil {
600 F.Ln("failed to read", e)
601 }
602 e = binary.Read(r, binary.BigEndian, &ef.minRegisteredBlocks)
603 if e != nil {
604 F.Ln("failed to read", e)
605 }
606 e = binary.Read(r, binary.BigEndian, &ef.lastKnownHeight)
607 if e != nil {
608 F.Ln("failed to read", e)
609 }
610 e = binary.Read(r, binary.BigEndian, &ef.numBlocksRegistered)
611 if e != nil {
612 F.Ln("failed to read", e)
613 }
614 // Read transactions.
615 var numObserved uint32
616 observed := make(map[uint32]*observedTransaction)
617 e = binary.Read(r, binary.BigEndian, &numObserved)
618 if e != nil {
619 F.Ln("failed to read", e)
620 }
621 for i := uint32(0); i < numObserved; i++ {
622 var ot *observedTransaction
623 ot, e = deserializeObservedTransaction(r)
624 if e != nil {
625 return nil, e
626 }
627 observed[i] = ot
628 ef.observed[ot.hash] = ot
629 }
630 // Read bins.
631 for i := 0; i < estimateFeeDepth; i++ {
632 var numTransactions uint32
633 e = binary.Read(r, binary.BigEndian, &numTransactions)
634 if e != nil {
635 F.Ln("failed to read", e)
636 }
637 bin := make([]*observedTransaction, numTransactions)
638 for j := uint32(0); j < numTransactions; j++ {
639 var index uint32
640 e = binary.Read(r, binary.BigEndian, &index)
641 if e != nil {
642 F.Ln("failed to read", e)
643 }
644 var exists bool
645 bin[j], exists = observed[index]
646 if !exists {
647 return nil, fmt.Errorf(
648 "invalid transaction reference %d",
649 index,
650 )
651 }
652 }
653 ef.bin[i] = bin
654 }
655 // Read dropped transactions.
656 var numDropped uint32
657 e = binary.Read(r, binary.BigEndian, &numDropped)
658 if e != nil {
659 F.Ln("failed to read", e)
660 }
661 ef.dropped = make([]*registeredBlock, numDropped)
662 for i := uint32(0); i < numDropped; i++ {
663 var e error
664 ef.dropped[int(i)], e = deserializeRegisteredBlock(r, observed)
665 if e != nil {
666 return nil, e
667 }
668 }
669 return ef, nil
670 }
671 func deserializeObservedTransaction(r io.Reader) (*observedTransaction, error) {
672 ot := observedTransaction{}
673 // The first 32 bytes should be a hash.
674 e := binary.Read(r, binary.BigEndian, &ot.hash)
675 if e != nil {
676 F.Ln("failed to read", e)
677 }
678 // The next 8 are SatoshiPerByte
679 e = binary.Read(r, binary.BigEndian, &ot.feeRate)
680 if e != nil {
681 F.Ln("failed to read", e)
682 }
683 // And next there are two uint32's.
684 e = binary.Read(r, binary.BigEndian, &ot.observed)
685 if e != nil {
686 F.Ln("failed to read", e)
687 }
688 e = binary.Read(r, binary.BigEndian, &ot.mined)
689 if e != nil {
690 F.Ln("failed to read", e)
691 }
692 return &ot, nil
693 }
694 func deserializeRegisteredBlock(
695 r io.Reader,
696 txs map[uint32]*observedTransaction,
697 ) (*registeredBlock, error) {
698 var lenTransactions uint32
699 rb := ®isteredBlock{}
700 e := binary.Read(r, binary.BigEndian, &rb.hash)
701 if e != nil {
702 F.Ln("failed to read", e)
703 }
704 e = binary.Read(r, binary.BigEndian, &lenTransactions)
705 if e != nil {
706 F.Ln("failed to read", e)
707 }
708 rb.transactions = make([]*observedTransaction, lenTransactions)
709 for i := uint32(0); i < lenTransactions; i++ {
710 var index uint32
711 e = binary.Read(r, binary.BigEndian, &index)
712 if e != nil {
713 F.Ln("failed to read", e)
714 }
715 rb.transactions[i] = txs[index]
716 }
717 return rb, nil
718 }
719