github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/eth/downloader/whitelist/service.go (about) 1 package whitelist 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 8 "github.com/ethereum/go-ethereum/common" 9 "github.com/ethereum/go-ethereum/core/types" 10 "github.com/ethereum/go-ethereum/log" 11 ) 12 13 // Checkpoint whitelist 14 type Service struct { 15 m sync.Mutex 16 checkpointWhitelist map[uint64]common.Hash // Checkpoint whitelist, populated by reaching out to heimdall 17 checkpointOrder []uint64 // Checkpoint order, populated by reaching out to heimdall 18 maxCapacity uint // Max capacity of the whitelist 19 checkpointInterval uint64 // Checkpoint interval, until which we can allow importing 20 } 21 22 func NewService(maxCapacity uint) *Service { 23 return &Service{ 24 checkpointWhitelist: make(map[uint64]common.Hash), 25 checkpointOrder: []uint64{}, 26 maxCapacity: maxCapacity, 27 checkpointInterval: 256, // TODO: make it configurable through params? 28 } 29 } 30 31 var ( 32 ErrCheckpointMismatch = errors.New("checkpoint mismatch") 33 ErrLongFutureChain = errors.New("received future chain of unacceptable length") 34 ErrNoRemoteCheckpoint = errors.New("remote peer doesn't have a checkpoint") 35 ) 36 37 // IsValidPeer checks if the chain we're about to receive from a peer is valid or not 38 // in terms of reorgs. We won't reorg beyond the last bor checkpoint submitted to mainchain. 39 func (w *Service) IsValidPeer(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) { 40 // We want to validate the chain by comparing the last checkpointed block 41 // we're storing in `checkpointWhitelist` with the peer's block. 42 // 43 // Check for availaibility of the last checkpointed block. 44 // This can be also be empty if our heimdall is not responding 45 // or we're running without it. 46 if len(w.checkpointWhitelist) == 0 { 47 // worst case, we don't have the checkpoints in memory 48 return true, nil 49 } 50 51 // Fetch the last checkpoint entry 52 lastCheckpointBlockNum := w.checkpointOrder[len(w.checkpointOrder)-1] 53 lastCheckpointBlockHash := w.checkpointWhitelist[lastCheckpointBlockNum] 54 55 // todo: we can extract this as an interface and mock as well or just test IsValidChain in isolation from downloader passing fake fetchHeadersByNumber functions 56 headers, hashes, err := fetchHeadersByNumber(lastCheckpointBlockNum, 1, 0, false) 57 if err != nil { 58 return false, fmt.Errorf("%w: last checkpoint %d, err %v", ErrNoRemoteCheckpoint, lastCheckpointBlockNum, err) 59 } 60 61 if len(headers) == 0 { 62 return false, fmt.Errorf("%w: last checkpoint %d", ErrNoRemoteCheckpoint, lastCheckpointBlockNum) 63 } 64 65 reqBlockNum := headers[0].Number.Uint64() 66 reqBlockHash := hashes[0] 67 68 // Check against the checkpointed blocks 69 if reqBlockNum == lastCheckpointBlockNum && reqBlockHash == lastCheckpointBlockHash { 70 return true, nil 71 } 72 73 return false, ErrCheckpointMismatch 74 } 75 76 // IsValidChain checks the validity of chain by comparing it 77 // against the local checkpoint entries 78 func (w *Service) IsValidChain(currentHeader *types.Header, chain []*types.Header) (bool, error) { 79 // Check if we have checkpoints to validate incoming chain in memory 80 if len(w.checkpointWhitelist) == 0 { 81 // We don't have any entries, no additional validation will be possible 82 return true, nil 83 } 84 85 // Return if we've received empty chain 86 if len(chain) == 0 { 87 return false, nil 88 } 89 90 var ( 91 oldestCheckpointNumber uint64 = w.checkpointOrder[0] 92 current uint64 = currentHeader.Number.Uint64() 93 ) 94 95 // Check if we have whitelist entries in required range 96 if chain[len(chain)-1].Number.Uint64() < oldestCheckpointNumber { 97 // We have future whitelisted entries, so no additional validation will be possible 98 // This case will occur when bor is in middle of sync, but heimdall is ahead/fully synced. 99 return true, nil 100 } 101 102 // Split the chain into past and future chain 103 pastChain, _ := splitChain(current, chain) 104 105 // Note: Do not act on future chain and allow importing all kinds of future chains. 106 107 // Add an offset to future chain if it's not in continuity 108 // offset := 0 109 // if len(futureChain) != 0 { 110 // offset += int(futureChain[0].Number.Uint64()-currentHeader.Number.Uint64()) - 1 111 // } 112 113 // Don't accept future chain of unacceptable length (from current block) 114 // if len(futureChain)+offset > int(w.checkpointInterval) { 115 // return false, ErrLongFutureChain 116 // } 117 118 // Iterate over the chain and validate against the last checkpoint 119 // It will handle all cases where the incoming chain has atleast one checkpoint 120 for i := len(pastChain) - 1; i >= 0; i-- { 121 if _, ok := w.checkpointWhitelist[pastChain[i].Number.Uint64()]; ok { 122 return pastChain[i].Hash() == w.checkpointWhitelist[pastChain[i].Number.Uint64()], nil 123 } 124 } 125 126 return true, nil 127 } 128 129 func splitChain(current uint64, chain []*types.Header) ([]*types.Header, []*types.Header) { 130 var ( 131 pastChain []*types.Header 132 futureChain []*types.Header 133 first uint64 = chain[0].Number.Uint64() 134 last uint64 = chain[len(chain)-1].Number.Uint64() 135 ) 136 137 if current >= first { 138 if len(chain) == 1 || current >= last { 139 pastChain = chain 140 } else { 141 pastChain = chain[:current-first+1] 142 } 143 } 144 145 if current < last { 146 if len(chain) == 1 || current < first { 147 futureChain = chain 148 } else { 149 futureChain = chain[current-first+1:] 150 } 151 } 152 153 return pastChain, futureChain 154 } 155 156 func (w *Service) ProcessCheckpoint(endBlockNum uint64, endBlockHash common.Hash) { 157 w.m.Lock() 158 defer w.m.Unlock() 159 160 w.enqueueCheckpointWhitelist(endBlockNum, endBlockHash) 161 // If size of checkpoint whitelist map is greater than 10, remove the oldest entry. 162 163 if w.length() > int(w.maxCapacity) { 164 w.dequeueCheckpointWhitelist() 165 } 166 } 167 168 // GetCheckpointWhitelist returns the existing whitelisted 169 // entries of checkpoint of the form block number -> block hash. 170 func (w *Service) GetCheckpointWhitelist() map[uint64]common.Hash { 171 w.m.Lock() 172 defer w.m.Unlock() 173 174 return w.checkpointWhitelist 175 } 176 177 // PurgeCheckpointWhitelist purges data from checkpoint whitelist map 178 func (w *Service) PurgeCheckpointWhitelist() { 179 w.m.Lock() 180 defer w.m.Unlock() 181 182 w.checkpointWhitelist = make(map[uint64]common.Hash) 183 w.checkpointOrder = make([]uint64, 0) 184 } 185 186 // EnqueueWhitelistBlock enqueues blockNumber, blockHash to the checkpoint whitelist map 187 func (w *Service) enqueueCheckpointWhitelist(key uint64, val common.Hash) { 188 if _, ok := w.checkpointWhitelist[key]; !ok { 189 log.Debug("Enqueing new checkpoint whitelist", "block number", key, "block hash", val) 190 191 w.checkpointWhitelist[key] = val 192 w.checkpointOrder = append(w.checkpointOrder, key) 193 } 194 } 195 196 // DequeueWhitelistBlock dequeues block, blockhash from the checkpoint whitelist map 197 func (w *Service) dequeueCheckpointWhitelist() { 198 if len(w.checkpointOrder) > 0 { 199 log.Debug("Dequeing checkpoint whitelist", "block number", w.checkpointOrder[0], "block hash", w.checkpointWhitelist[w.checkpointOrder[0]]) 200 201 delete(w.checkpointWhitelist, w.checkpointOrder[0]) 202 w.checkpointOrder = w.checkpointOrder[1:] // fixme: this slice is growing infinitely and never will be released. also a panic is possible if the last element is going to be removed 203 } 204 } 205 206 // length returns the len of the whitelist. 207 func (w *Service) length() int { 208 return len(w.checkpointWhitelist) 209 }