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 }