github.com/codingfuture/orig-energi3@v0.8.4/energi/consensus/pos.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 consensus 18 19 import ( 20 "encoding/binary" 21 "errors" 22 "math/big" 23 "sort" 24 "time" 25 26 "github.com/ethereum/go-ethereum/common" 27 eth_consensus "github.com/ethereum/go-ethereum/consensus" 28 "github.com/ethereum/go-ethereum/core/types" 29 "github.com/ethereum/go-ethereum/crypto" 30 "github.com/ethereum/go-ethereum/log" 31 32 energi_params "energi.world/core/gen3/energi/params" 33 ) 34 35 const ( 36 MaturityPeriod uint64 = energi_params.MaturityPeriod 37 AverageTimeBlocks uint64 = energi_params.AverageTimeBlocks 38 TargetBlockGap uint64 = energi_params.TargetBlockGap 39 MinBlockGap uint64 = energi_params.MinBlockGap 40 MaxFutureGap uint64 = energi_params.MaxFutureGap 41 TargetPeriodGap uint64 = energi_params.TargetPeriodGap 42 ) 43 44 var ( 45 minStake = big.NewInt(1e18) 46 47 diff1Target = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) 48 49 errBlockMinTime = errors.New("Minimal time gap is not obeyed") 50 51 errInvalidPoSHash = errors.New("Invalid PoS hash") 52 errInvalidPoSNonce = errors.New("Invalid Stake weight") 53 ) 54 55 type timeTarget struct { 56 min_time uint64 57 max_time uint64 58 block_target uint64 59 period_target uint64 60 } 61 62 /** 63 * Implements block time consensus 64 * 65 * POS-11: Block time restrictions 66 * POS-12: Block interval enforcement 67 */ 68 func (e *Energi) calcTimeTarget( 69 chain ChainReader, 70 parent *types.Header, 71 ) *timeTarget { 72 ret := &timeTarget{} 73 now := e.now() 74 parent_number := parent.Number.Uint64() 75 block_number := parent_number + 1 76 77 // POS-11: Block time restrictions 78 ret.max_time = now + MaxFutureGap 79 80 // POS-11: Block time restrictions 81 ret.min_time = parent.Time + MinBlockGap 82 ret.block_target = parent.Time + TargetBlockGap 83 ret.period_target = ret.block_target 84 85 // POS-12: Block interval enforcement 86 //--- 87 if block_number >= AverageTimeBlocks { 88 // TODO: LRU cache here for extra DoS mitigation 89 past := parent 90 91 // NOTE: we have to do this way as parent may be not part of canonical 92 // chain. As no mutex is held, we cannot do checks for canonical. 93 for i := AverageTimeBlocks - 1; i > 0; i-- { 94 past = chain.GetHeader(past.ParentHash, past.Number.Uint64()-1) 95 96 if past == nil { 97 log.Trace("Inconsistent tree, shutdown?") 98 return ret 99 } 100 } 101 102 ret.period_target = past.Time + TargetPeriodGap 103 period_min_time := ret.period_target - MinBlockGap 104 105 if period_min_time > ret.min_time { 106 ret.min_time = period_min_time 107 } 108 } 109 110 log.Trace("PoS time", "block", block_number, 111 "min", ret.min_time, "max", ret.max_time, 112 "block_target", ret.block_target, 113 "period_target", ret.period_target, 114 ) 115 return ret 116 } 117 118 func (e *Energi) enforceTime( 119 header *types.Header, 120 time_target *timeTarget, 121 ) error { 122 // NOTE: allow Miner to hint already tried period by 123 if header.Time < time_target.min_time { 124 header.Time = time_target.min_time 125 } 126 127 return nil 128 } 129 130 func (e *Energi) checkTime( 131 header *types.Header, 132 time_target *timeTarget, 133 ) error { 134 if header.Time < time_target.min_time { 135 return errBlockMinTime 136 } 137 138 // Check if allowed to mine 139 if header.Time > time_target.max_time { 140 return eth_consensus.ErrFutureBlock 141 } 142 143 return nil 144 } 145 146 /** 147 * Implements check modifier consensus 148 * 149 * POS-14: Stake modifier 150 */ 151 func (e *Energi) calcPoSModifier( 152 chain ChainReader, 153 time uint64, 154 parent *types.Header, 155 ) (ret common.Hash) { 156 // TODO: LRU cache here for extra DoS mitigation 157 158 // Find maturity period border 159 maturity_border := time 160 161 if maturity_border < MaturityPeriod { 162 // This should happen only in testing 163 maturity_border = 0 164 } else { 165 maturity_border -= MaturityPeriod 166 } 167 168 // Find the oldest inside maturity period 169 // NOTE: we have to do this walk as parent may not be part of the canonical chain 170 parent_height := parent.Number.Uint64() 171 oldest := parent 172 173 for header, num := oldest, oldest.Number.Uint64(); (header.Time > maturity_border) && (num > 0); { 174 175 oldest = header 176 num-- 177 header = chain.GetHeader(header.ParentHash, num) 178 } 179 180 // Create Stake Modifier 181 ret = crypto.Keccak256Hash( 182 parent.Coinbase.Bytes(), 183 oldest.Root.Bytes(), 184 ) 185 186 // 187 log.Trace("PoS modifier", "block", parent_height+1, 188 "modifier", ret, "oldest", oldest.Number.Uint64()) 189 return ret 190 } 191 192 /** 193 * Implements difficulty consensus 194 */ 195 func (e *Energi) calcPoSDifficulty( 196 chain ChainReader, 197 time uint64, 198 parent *types.Header, 199 tt *timeTarget, 200 ) (ret *big.Int) { 201 ret = e.diffFn(chain, time, parent, tt) 202 log.Trace("PoS difficulty", "block", parent.Number.Uint64()+1, "time", time, "diff", ret) 203 return ret 204 } 205 206 /** 207 * POS-13: Difficulty algorithm (Proposal v1) 208 */ 209 const ( 210 diffV1_BMax uint64 = 30 211 diffV1_AMax uint64 = 120 212 diffV1_DivPlain uint64 = 100 213 214 // Roughly get 2x difficulty decrease 215 diffV1_MigrationStakerDelay uint64 = 15 216 diffV1_MigrationStakerTarget uint64 = 0xFFFF 217 ) 218 219 var ( 220 diffV1_BTable []*big.Int 221 diffV1_ATable []*big.Int 222 diffV1_Div = new(big.Int).SetUint64(diffV1_DivPlain) 223 ) 224 225 func initDiffTable(l uint64, c float64) []*big.Int { 226 t := make([]*big.Int, l+1) 227 t[0] = common.Big1 228 var acc float64 = 1 229 for i := 1; i < len(t); i++ { 230 acc *= c 231 t[i] = big.NewInt(int64(acc * float64(diffV1_DivPlain))) 232 } 233 return t 234 } 235 func init() { 236 diffV1_BTable = initDiffTable(diffV1_BMax, 1.1) 237 diffV1_ATable = initDiffTable(diffV1_AMax, 1.05) 238 } 239 func calcPoSDifficultyV1( 240 chain ChainReader, 241 time uint64, 242 parent *types.Header, 243 tt *timeTarget, 244 ) (D *big.Int) { 245 // Find out our target anchor 246 target := (tt.block_target + tt.period_target) / 2 247 if target < tt.min_time { 248 target = tt.min_time 249 } 250 251 if time < target { 252 S := target - time 253 if S > diffV1_BMax { 254 S = diffV1_BMax 255 } 256 B := diffV1_BTable[S] 257 D = new(big.Int).Div( 258 new(big.Int).Mul(parent.Difficulty, B), 259 diffV1_Div, 260 ) 261 log.Trace("Diff multiplier", "before", S, "mult", B) 262 } else if time > target { 263 S := time - target 264 if S > diffV1_AMax { 265 S = diffV1_AMax 266 } 267 A := diffV1_ATable[S] 268 D = new(big.Int).Div( 269 new(big.Int).Mul(parent.Difficulty, diffV1_Div), 270 A, 271 ) 272 log.Trace("Diff multiplier", "after", S, "div", A) 273 } else { 274 log.Trace("No difficulty change", "parent", parent.Difficulty) 275 return parent.Difficulty 276 } 277 278 if D.Cmp(common.Big1) < 0 { 279 D = common.Big1 280 } 281 282 log.Trace("Difficulty change", 283 "parent", parent.Difficulty, "new", D, 284 "time", time, "target", target) 285 return D 286 } 287 288 /** 289 * Implements hash consensus 290 * 291 * POS-18: PoS hash generation 292 * POS-22: Partial stake amount 293 */ 294 func (e *Energi) calcPoSHash( 295 header *types.Header, 296 target *big.Int, 297 weight uint64, 298 ) (poshash *big.Int, used_weight uint64) { 299 betime64 := [8]byte{} 300 binary.BigEndian.PutUint64(betime64[:], header.Time) 301 302 poshash = new(big.Int).SetBytes(crypto.Keccak256( 303 betime64[:], 304 header.MixDigest.Bytes(), 305 header.Coinbase.Bytes(), 306 )) 307 308 if poshash.Cmp(target) > 0 { 309 mod := new(big.Int) 310 count, mod := new(big.Int).DivMod(poshash, target, mod) 311 used_weight = count.Uint64() 312 313 if mod.Cmp(common.Big0) > 0 { 314 used_weight += 1 315 } 316 } else { 317 used_weight = 1 318 } 319 320 if weight < used_weight { 321 return nil, 0 322 } 323 324 log.Trace("PoS hash", 325 "target", target, 326 "poshash", poshash, 327 "used_weight", used_weight, 328 "weight", weight) 329 return poshash, used_weight 330 } 331 332 func (e *Energi) verifyPoSHash( 333 chain ChainReader, 334 header *types.Header, 335 ) error { 336 parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) 337 weight, err := e.lookupStakeWeight(chain, header.Time, parent, header.Coinbase) 338 if err != nil { 339 return err 340 } 341 342 target := new(big.Int).Div(diff1Target, header.Difficulty) 343 344 poshash, used_weight := e.calcPoSHash(header, target, weight) 345 346 if poshash == nil { 347 return errInvalidPoSHash 348 } 349 350 if used_weight != header.Nonce.Uint64() { 351 return errInvalidPoSNonce 352 } 353 354 return nil 355 } 356 357 /** 358 * Implements stake amount calculation. 359 * 360 * POS-3: Stake maturity period 361 * POS-4: Stake amount 362 * POS-22: Partial stake amount 363 * 364 * This is a basic helper for stake amount calculation. 365 * There are ways to optimize it for high load, but we need something 366 * to start with. 367 */ 368 func (e *Energi) lookupStakeWeight( 369 chain ChainReader, 370 now uint64, 371 till *types.Header, 372 addr common.Address, 373 ) (weight uint64, err error) { 374 var since uint64 375 376 if now > MaturityPeriod { 377 since = now - MaturityPeriod 378 } else { 379 since = 0 380 } 381 382 // NOTE: Do not set to high initial value due to defensive coding approach! 383 weight = 0 384 total_staked := uint64(0) 385 first_run := true 386 blockst := chain.CalculateBlockState(till.Hash(), till.Number.Uint64()) 387 388 // NOTE: we need to ensure at least one iteration with the balance condition 389 for (till.Time > since) || first_run { 390 if blockst == nil { 391 log.Warn("PoS state root failure", "header", till.Hash()) 392 return 0, eth_consensus.ErrMissingState 393 } 394 395 weight_at_block := new(big.Int).Div( 396 blockst.GetBalance(addr), 397 minStake, 398 ).Uint64() 399 400 if first_run { 401 weight = weight_at_block 402 first_run = false 403 } 404 405 // Find the minimum balance 406 if weight > weight_at_block { 407 weight = weight_at_block 408 } 409 410 // No need to lookup further 411 if weight < 1 { 412 break 413 } 414 415 // POS-22: partial stake amount 416 if till.Coinbase == addr { 417 total_staked += till.Nonce.Uint64() 418 } 419 420 curr := till 421 parent_number := curr.Number.Uint64() - 1 422 till = chain.GetHeader(curr.ParentHash, parent_number) 423 424 if till == nil { 425 if curr.Number.Cmp(common.Big0) == 0 { 426 break 427 } 428 429 log.Error("PoS state missing parent", "parent", curr.ParentHash) 430 return 0, eth_consensus.ErrUnknownAncestor 431 } 432 433 blockst = chain.CalculateBlockState(curr.ParentHash, parent_number) 434 } 435 436 if weight < total_staked { 437 log.Debug("Nothing to stake", 438 "addr", addr, "since", since, "weight", weight, "total_staked", total_staked) 439 weight = 0 440 } else { 441 weight -= total_staked 442 } 443 444 //log.Trace("PoS stake weight", "addr", addr, "weight", weight) 445 return weight, nil 446 } 447 448 /** 449 * POS-19: PoS miner implementation 450 */ 451 func (e *Energi) mine( 452 chain ChainReader, 453 header *types.Header, 454 stop <-chan struct{}, 455 ) (success bool, err error) { 456 type Candidates struct { 457 addr common.Address 458 weight uint64 459 } 460 461 accounts := e.accountsFn() 462 if len(accounts) == 0 { 463 select { 464 case <-stop: 465 return false, nil 466 } 467 } 468 469 candidates := make([]Candidates, 0, len(accounts)) 470 migration_dpos := false 471 for _, a := range accounts { 472 candidates = append(candidates, Candidates{ 473 addr: a, 474 weight: 0, 475 }) 476 //log.Trace("PoS miner candidate found", "address", a) 477 478 if a == energi_params.Energi_MigrationContract { 479 migration_dpos = true 480 } 481 } 482 483 //--- 484 parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) 485 486 if parent == nil { 487 return false, eth_consensus.ErrUnknownAncestor 488 } 489 490 time_target := e.calcTimeTarget(chain, parent) 491 492 blockTime := time_target.min_time 493 494 // Special case due to expected very large gap between Genesis and Migration 495 if header.IsGen2Migration() && !e.testing { 496 blockTime = e.now() 497 } 498 499 // A special workaround to obey target time when migration contract is used 500 // for mining to prevent any difficult bombs. 501 if migration_dpos && !e.testing { 502 // Obey block target 503 if blockTime < time_target.block_target { 504 blockTime = time_target.block_target 505 } 506 507 // Also, obey period target 508 if blockTime < time_target.period_target { 509 blockTime = time_target.period_target 510 } 511 512 // Decrease difficulty, if it got bumped 513 if header.Difficulty.Uint64() > diffV1_MigrationStakerTarget { 514 blockTime += diffV1_MigrationStakerDelay 515 } 516 } 517 518 //--- 519 for ; ; blockTime++ { 520 if max_time := e.now() + MaxFutureGap; blockTime > max_time { 521 log.Trace("PoS miner is sleeping") 522 select { 523 case <-stop: 524 // NOTE: it's very important to ignore stop until all variants are tried 525 // to prevent rogue stakers taking the initiative. 526 return false, nil 527 case <-time.After(time.Duration(blockTime-max_time) * time.Second): 528 } 529 } 530 531 if e.peerCountFn() == 0 { 532 log.Trace("Skipping PoS miner due to missing peers") 533 continue 534 } 535 536 header.Time = blockTime 537 time_target, err = e.posPrepare(chain, header, parent) 538 if err != nil { 539 return false, err 540 } 541 542 target := new(big.Int).Div(diff1Target, header.Difficulty) 543 log.Trace("PoS miner time", "time", blockTime) 544 545 // It could be done once, but then there is a chance to miss blocks. 546 // Some significant algo optimizations are possible, but we start with simplicity. 547 for i := range candidates { 548 v := &candidates[i] 549 v.weight, err = e.lookupStakeWeight( 550 chain, blockTime, parent, v.addr) 551 if err != nil { 552 return false, err 553 } 554 } 555 // Try smaller amounts first 556 sort.Slice(candidates, func(i, j int) bool { 557 return candidates[i].weight < candidates[j].weight 558 }) 559 // Try to match target 560 for i := range candidates { 561 v := &candidates[i] 562 if v.weight < 1 { 563 continue 564 } 565 566 //log.Trace("PoS stake candidate", "addr", v.addr, "weight", v.weight) 567 header.Coinbase = v.addr 568 poshash, used_weight := e.calcPoSHash(header, target, v.weight) 569 570 nonceCap := e.GetMinerNonceCap() 571 if nonceCap != 0 && nonceCap < used_weight { 572 continue 573 } else if poshash != nil { 574 log.Trace("PoS stake", "addr", v.addr, "weight", v.weight, "used_weight", used_weight) 575 header.Nonce = types.EncodeNonce(used_weight) 576 return true, nil 577 } 578 } 579 } 580 581 return false, nil 582 }