github.com/energicryptocurrency/go-energi@v1.1.7/core/energi_checkpoints.go (about) 1 // Copyright 2019 The Energi Core Authors 2 // This file is part of the Energi Core library. 3 // 4 // The Energi Core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The Energi Core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the Energi Core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package core 18 19 import ( 20 "errors" 21 "fmt" 22 "math" 23 "math/big" 24 "sort" 25 "sync" 26 "sync/atomic" 27 28 "github.com/energicryptocurrency/go-energi/common" 29 "github.com/energicryptocurrency/go-energi/core/types" 30 "github.com/energicryptocurrency/go-energi/crypto" 31 energi_params "github.com/energicryptocurrency/go-energi/energi/params" 32 "github.com/energicryptocurrency/go-energi/event" 33 "github.com/energicryptocurrency/go-energi/log" 34 "github.com/energicryptocurrency/go-energi/params" 35 ) 36 37 // max number of checkpoints stored in validated checkpoint map 38 const MaxCachedCheckpoints int = 10 39 40 type CheckpointValidateChain interface { 41 GetHeaderByNumber(number uint64) *types.Header 42 CurrentHeader() *types.Header 43 } 44 45 type CheckpointChain interface { 46 CheckpointValidateChain 47 48 EnforceCheckpoint(cp Checkpoint) error 49 Config() *params.ChainConfig 50 } 51 52 type Checkpoint struct { 53 Since uint64 54 Number uint64 55 Hash common.Hash 56 } 57 58 // Format implements fmt.Formatter, forcing the Checkpoint to be formatted as is, 59 // without going through the stringer interface used for logging. 60 func (cp Checkpoint) Format(s fmt.State, c rune) { 61 cpStr := struct { 62 Number uint64 63 Hash string 64 }{ 65 cp.Number, 66 cp.Hash.String(), 67 } 68 fmt.Fprintf(s, "%+"+string(c), cpStr) 69 } 70 71 type CheckpointSignature []byte 72 73 type CheckpointInfo struct { 74 Checkpoint 75 CppSignature CheckpointSignature 76 SigCount uint64 77 } 78 79 type NewCheckpointEvent struct { 80 CheckpointInfo 81 } 82 83 type validCheckpoint struct { 84 Checkpoint 85 signatures []CheckpointSignature 86 } 87 88 type futureCheckpoint struct { 89 Checkpoint 90 } 91 92 type checkpointManager struct { 93 validated map[uint64]validCheckpoint 94 latest uint64 95 future map[uint64]futureCheckpoint 96 mtx sync.RWMutex 97 newCpFeed event.Feed 98 } 99 100 func newCheckpointManager() *checkpointManager { 101 return &checkpointManager{ 102 validated: make(map[uint64]validCheckpoint), 103 future: make(map[uint64]futureCheckpoint), 104 } 105 } 106 107 func (cm *checkpointManager) setup(chain CheckpointChain) { 108 genesis_hash := chain.GetHeaderByNumber(0).Hash() 109 if checkpoints, ok := energi_params.EnergiCheckpoints[genesis_hash]; ok { 110 for k, v := range checkpoints { 111 cm.addCheckpoint( 112 chain, 113 Checkpoint{ 114 Number: k, 115 Hash: v, 116 }, 117 []CheckpointSignature{}, 118 true, 119 ) 120 } 121 } 122 } 123 124 func (cm *checkpointManager) validate(chain CheckpointValidateChain, num uint64, hash common.Hash) error { 125 cm.mtx.Lock() 126 defer cm.mtx.Unlock() 127 128 // Check against validated checkpoints & mismatch 129 if cp, ok := cm.validated[num]; ok { 130 if cp.Hash != hash { 131 return ErrCheckpointMismatch 132 } 133 134 cm.updateLatest(chain, &cp.Checkpoint) 135 136 return nil 137 } 138 139 // Check if before the latest checkpoint & mismatch 140 if num < cm.latest { 141 header := chain.GetHeaderByNumber(num) 142 143 if header != nil && header.Hash() != hash { 144 return ErrCheckpointMismatch 145 } 146 147 return nil 148 } 149 150 // TODO: proper future checkpoint processing 151 if cp, ok := cm.future[num]; ok { 152 if cp.Hash != hash { 153 return ErrCheckpointMismatch 154 } 155 156 return nil 157 } 158 159 return nil 160 } 161 162 // returns the smallest key (blockHeight) 163 func oldestCheckpoint(validated map[uint64]validCheckpoint) uint64 { 164 minHeight := uint64(math.MaxUint64) 165 for k := range validated { 166 if k < minHeight { 167 minHeight = k 168 } 169 } 170 return minHeight 171 } 172 173 func (bc *BlockChain) AddCheckpoint( 174 cp Checkpoint, 175 sigs []CheckpointSignature, 176 local bool, 177 ) error { 178 return bc.checkpoints.addCheckpoint(bc, cp, sigs, local) 179 } 180 181 func (cm *checkpointManager) addCheckpoint( 182 chain CheckpointChain, 183 cp Checkpoint, 184 sigs []CheckpointSignature, 185 local bool, 186 ) (err error) { 187 cm.mtx.Lock() 188 defer cm.mtx.Unlock() 189 190 if curr, ok := cm.validated[cp.Number]; ok { 191 if curr.Checkpoint == cp { 192 return nil 193 } 194 195 if curr.Since > cp.Since { 196 return nil 197 } 198 } 199 200 if !local { 201 // ignore checkpoints which occur before the latest local checkpoint 202 var maxHardcodedCheckpoint uint64 203 genesis_hash := chain.GetHeaderByNumber(0).Hash() 204 for maxHardcodedCheckpoint = range energi_params.EnergiCheckpoints[genesis_hash] { 205 break 206 } 207 for n := range energi_params.EnergiCheckpoints[genesis_hash] { 208 if n > maxHardcodedCheckpoint { 209 maxHardcodedCheckpoint = n 210 } 211 } 212 213 if cp.Number <= maxHardcodedCheckpoint { 214 //log.Info("Ignoring checkpoint which occurs before latest checkpoint at", "block", maxHardcodedCheckpoint) 215 return nil 216 } 217 218 // TODO: proper validation and use of future checkpoints 219 if len(sigs) == 0 { 220 log.Warn("Checkpoint: missing signatures", 221 "num", cp.Number, "hash", cp.Hash) 222 return errors.New("missing checkpoint signatures") 223 } 224 225 // The first one must always be CPP_signer 226 pubkey, err := crypto.Ecrecover(cm.hashToSign(&cp), sigs[0][:]) 227 if err != nil { 228 log.Warn("Checkpoint: failed to extract signature", 229 "num", cp.Number, "hash", cp.Hash, "err", err) 230 return err 231 } 232 233 // Check the primary signature 234 var signer common.Address 235 copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) 236 if nrgconf := chain.Config().Energi; nrgconf == nil || signer != nrgconf.CPPSigner { 237 log.Warn("Checkpoint: invalid CPP signature", "num", cp.Number, "hash", cp.Hash) 238 return errors.New("invalid CPP signature") 239 } 240 241 } 242 243 //only received(non-hardcoded) checkpoints will be stored in validated map 244 if len(cm.validated) == MaxCachedCheckpoints { 245 oldestCheckpointHeight := oldestCheckpoint(cm.validated) 246 if cp.Number > oldestCheckpointHeight { 247 delete(cm.validated, oldestCheckpointHeight) 248 cm.validated[cp.Number] = validCheckpoint{ 249 Checkpoint: cp, 250 signatures: append([]CheckpointSignature{}, sigs...), 251 } 252 } 253 } else { 254 cm.validated[cp.Number] = validCheckpoint{ 255 Checkpoint: cp, 256 signatures: append([]CheckpointSignature{}, sigs...), 257 } 258 } 259 260 log.Info("Added new checkpoint", "checkpoint", cp, "local", local) 261 262 err = chain.EnforceCheckpoint(cp) 263 264 cm.updateLatest(chain, &cp) 265 266 if !local { 267 // Send regardless of enforcement success 268 cm.newCpFeed.Send(NewCheckpointEvent{CheckpointInfo{cp, sigs[0], uint64(len(sigs))}}) 269 } 270 271 return err 272 } 273 274 func (cm *checkpointManager) hashToSign(cp *Checkpoint) []byte { 275 data := []byte("||Energi Blockchain Checkpoint||") 276 data = append(data, common.BigToHash(new(big.Int).SetUint64(cp.Number)).Bytes()...) 277 data = append(data, cp.Hash.Bytes()...) 278 return crypto.Keccak256(data) 279 } 280 281 func (cm *checkpointManager) updateLatest(chain CheckpointValidateChain, cp *Checkpoint) { 282 if cp.Number > cm.latest && cp.Number <= chain.CurrentHeader().Number.Uint64() { 283 cm.latest = cp.Number 284 log.Info("Latest checkpoint", "height", cp.Number, "hash", cp.Hash.Hex()) 285 } 286 } 287 288 func (bc *BlockChain) EnforceCheckpoint(cp Checkpoint) error { 289 header := bc.GetHeaderByNumber(cp.Number) 290 291 if header != nil && header.Hash() != cp.Hash { 292 log.Error("Side chain is detected as canonical", "number", cp.Number, "hash", cp.Hash, "old", header.Hash()) 293 294 if cp_block := bc.GetBlock(cp.Hash, cp.Number); cp_block != nil { 295 // Known block 296 bc.mu.Lock() 297 defer bc.mu.Unlock() 298 299 if err := bc.reorg(bc.GetBlock(header.Hash(), cp.Number), cp_block); err != nil { 300 log.Crit("Failed to reorg", "err", err) 301 // should terminate 302 return err 303 } 304 305 log.Warn("Chain reorg was successful, resuming normal operation") 306 } else { 307 // Unknown block 308 if err := bc.SetHead(cp.Number - 1); err != nil { 309 log.Crit("Failed to rewind before fork point", "err", err) 310 // should terminate 311 return err 312 } 313 log.Warn("Chain rewind was successful, resuming normal operation") 314 } 315 } 316 317 return nil 318 } 319 320 func (bc *BlockChain) ListCheckpoints() []CheckpointInfo { 321 cm := bc.checkpoints 322 323 cm.mtx.Lock() 324 defer cm.mtx.Unlock() 325 326 res := make([]CheckpointInfo, 0, len(cm.validated)) 327 328 for _, v := range cm.validated { 329 if len(v.signatures) > 0 { 330 res = append(res, CheckpointInfo{v.Checkpoint, v.signatures[0], uint64(len(v.signatures))}) 331 } 332 } 333 334 sort.Slice(res, func(i, j int) bool { 335 return res[i].Since > res[j].Since 336 }) 337 338 return res 339 } 340 341 func (bc *BlockChain) CheckpointSignatures(cp Checkpoint) []CheckpointSignature { 342 cm := bc.checkpoints 343 344 cm.mtx.Lock() 345 defer cm.mtx.Unlock() 346 347 if vcp, ok := cm.validated[cp.Number]; ok && vcp.Hash == cp.Hash { 348 return append([]CheckpointSignature{}, vcp.signatures...) 349 } 350 351 return nil 352 } 353 354 func (bc *BlockChain) SubscribeNewCheckpointEvent(ch chan<- NewCheckpointEvent) event.Subscription { 355 return bc.scope.Track(bc.checkpoints.newCpFeed.Subscribe(ch)) 356 } 357 358 func (bc *BlockChain) IsRunning() bool { 359 return atomic.LoadInt32(&bc.running) == 0 360 }