decred.org/dcrdex@v1.0.5/server/asset/dcr/cache.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package dcr
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  
    10  	"decred.org/dcrdex/dex"
    11  	"github.com/decred/dcrd/chaincfg/chainhash"
    12  	chainjson "github.com/decred/dcrd/rpc/jsonrpc/types/v4"
    13  )
    14  
    15  // The dcrBlock structure should hold a minimal amount of information about a
    16  // block needed to verify UTXO validity.
    17  type dcrBlock struct {
    18  	hash     chainhash.Hash
    19  	height   uint32
    20  	orphaned bool
    21  	vote     bool // stakeholder vote result for the previous block
    22  }
    23  
    24  // The blockCache caches block information to prevent repeated calls to
    25  // rpcclient.GetblockVerbose.
    26  type blockCache struct {
    27  	mtx       sync.RWMutex
    28  	blocks    map[chainhash.Hash]*dcrBlock
    29  	mainchain map[uint32]*dcrBlock
    30  	best      dcrBlock
    31  	log       dex.Logger
    32  }
    33  
    34  // Constructor for a blockCache.
    35  func newBlockCache(logger dex.Logger) *blockCache {
    36  	return &blockCache{
    37  		blocks:    make(map[chainhash.Hash]*dcrBlock),
    38  		mainchain: make(map[uint32]*dcrBlock),
    39  		log:       logger,
    40  	}
    41  }
    42  
    43  // Getter for a block by it's hash.
    44  func (cache *blockCache) block(h *chainhash.Hash) (*dcrBlock, bool) {
    45  	cache.mtx.RLock()
    46  	defer cache.mtx.RUnlock()
    47  	blk, found := cache.blocks[*h]
    48  	return blk, found
    49  }
    50  
    51  // Getter for a mainchain block by its height. This method does not attempt
    52  // to load the block from the blockchain if it is not found. If that is required
    53  // use (*Backend).getMainchainDcrBlock.
    54  func (cache *blockCache) atHeight(height uint32) (*dcrBlock, bool) {
    55  	cache.mtx.RLock()
    56  	defer cache.mtx.RUnlock()
    57  	blk, found := cache.mainchain[height]
    58  	return blk, found
    59  }
    60  
    61  // Add a block to the blockCache. This method will translate the RPC result
    62  // to a dcrBlock, returning the dcrBlock. If the block is not orphaned, it will
    63  // be added to the mainchain.
    64  func (cache *blockCache) add(block *chainjson.GetBlockVerboseResult) (*dcrBlock, error) {
    65  	cache.mtx.Lock()
    66  	defer cache.mtx.Unlock()
    67  	hash, err := chainhash.NewHashFromStr(block.Hash)
    68  	if err != nil {
    69  		return nil, fmt.Errorf("error decoding block hash %s: %w", block.Hash, err)
    70  	}
    71  	blk := &dcrBlock{
    72  		hash:     *hash,
    73  		height:   uint32(block.Height),
    74  		orphaned: block.Confirmations == -1,
    75  		vote:     block.VoteBits&1 != 0,
    76  	}
    77  	cache.blocks[*hash] = blk
    78  
    79  	// Orphaned blocks will have -1 confirmations. Don't add them to mainchain.
    80  	if block.Confirmations > -1 {
    81  		cache.mainchain[uint32(block.Height)] = blk
    82  		if block.Height > int64(cache.best.height) {
    83  			cache.best.height = uint32(block.Height)
    84  			cache.best.hash = *hash
    85  		}
    86  	}
    87  	return blk, nil
    88  }
    89  
    90  // Get the best known block height for the blockCache.
    91  func (cache *blockCache) tipHeight() uint32 {
    92  	cache.mtx.Lock()
    93  	defer cache.mtx.Unlock()
    94  	return cache.best.height
    95  }
    96  
    97  // Get the best known block hash in the blockCache.
    98  func (cache *blockCache) tipHash() chainhash.Hash {
    99  	cache.mtx.RLock()
   100  	defer cache.mtx.RUnlock()
   101  	return cache.best.hash
   102  }
   103  
   104  // Get the best known block height in the blockCache.
   105  func (cache *blockCache) tip() dcrBlock {
   106  	cache.mtx.RLock()
   107  	defer cache.mtx.RUnlock()
   108  	return cache.best
   109  }
   110  
   111  // Trigger a reorg, setting any blocks at or above the provided height as
   112  // orphaned and removing them from mainchain, but not the blocks map. reorg
   113  // clears the best block, so should always be followed with the addition of a
   114  // new mainchain block.
   115  func (cache *blockCache) reorg(from int64) {
   116  	cache.mtx.Lock()
   117  	defer cache.mtx.Unlock()
   118  	if from < 0 {
   119  		return
   120  	}
   121  	for height := uint32(from); height <= cache.best.height; height++ {
   122  		block, found := cache.mainchain[height]
   123  		if !found {
   124  			cache.log.Errorf("reorg block not found on mainchain at height %d for a reorg from %d to %d", height, from, cache.best.height)
   125  			continue
   126  		}
   127  		// Delete the block from mainchain.
   128  		delete(cache.mainchain, block.height)
   129  		// Store an orphaned block in the blocks cache.
   130  		cache.blocks[block.hash] = &dcrBlock{
   131  			hash:     block.hash,
   132  			height:   block.height,
   133  			orphaned: true,
   134  			vote:     block.vote,
   135  		}
   136  	}
   137  	// Set this to a zero block so that the new block will replace it even if
   138  	// it is of the same height as the previous best block.
   139  	cache.best = dcrBlock{}
   140  }