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