2023-03-17 09:01:52 +00:00
package sidechain
import (
2023-03-22 10:05:04 +00:00
"fmt"
2023-03-27 11:56:27 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
2023-03-17 09:01:52 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
2023-05-12 06:55:01 +00:00
"git.gammaspectra.live/P2Pool/sha3"
2023-07-20 05:37:10 +00:00
"lukechampine.com/uint128"
2023-03-17 09:01:52 +00:00
"math"
2023-05-23 07:31:20 +00:00
"math/bits"
2023-06-27 09:36:00 +00:00
"slices"
2023-03-17 09:01:52 +00:00
)
2023-03-26 03:44:30 +00:00
type GetByMainIdFunc func ( h types . Hash ) * PoolBlock
2023-03-27 18:22:35 +00:00
type GetByMainHeightFunc func ( height uint64 ) UniquePoolBlockSlice
2023-03-20 07:36:39 +00:00
type GetByTemplateIdFunc func ( h types . Hash ) * PoolBlock
2023-03-27 18:22:35 +00:00
type GetBySideHeightFunc func ( height uint64 ) UniquePoolBlockSlice
2023-03-20 07:36:39 +00:00
2023-03-27 11:56:27 +00:00
// GetChainMainByHashFunc if h = types.ZeroHash, return tip
type GetChainMainByHashFunc func ( h types . Hash ) * ChainMain
2023-07-12 10:32:02 +00:00
func CalculateOutputs ( block * PoolBlock , consensus * Consensus , difficultyByHeight block . GetDifficultyByHeightFunc , getByTemplateId GetByTemplateIdFunc , derivationCache DerivationCacheInterface , preAllocatedShares Shares , preAllocatedRewards [ ] uint64 ) ( outputs transaction . Outputs , bottomHeight uint64 ) {
2023-03-21 10:01:51 +00:00
tmpShares , bottomHeight := GetShares ( block , consensus , difficultyByHeight , getByTemplateId , preAllocatedShares )
2023-07-12 10:32:02 +00:00
if preAllocatedRewards == nil {
preAllocatedRewards = make ( [ ] uint64 , 0 , len ( tmpShares ) )
}
tmpRewards := SplitReward ( preAllocatedRewards , block . Main . Coinbase . TotalReward , tmpShares )
2023-03-17 09:01:52 +00:00
if tmpShares == nil || tmpRewards == nil || len ( tmpRewards ) != len ( tmpShares ) {
2023-03-21 10:01:51 +00:00
return nil , 0
2023-03-17 09:01:52 +00:00
}
n := uint64 ( len ( tmpShares ) )
2023-03-21 10:01:51 +00:00
outputs = make ( transaction . Outputs , n )
2023-03-17 09:01:52 +00:00
txType := block . GetTransactionOutputType ( )
txPrivateKeySlice := block . Side . CoinbasePrivateKey . AsSlice ( )
txPrivateKeyScalar := block . Side . CoinbasePrivateKey . AsScalar ( )
2023-05-12 06:55:01 +00:00
var hashers [ ] * sha3 . HasherState
2023-05-11 07:51:00 +00:00
2023-05-17 15:29:01 +00:00
defer func ( ) {
for _ , h := range hashers {
crypto . PutKeccak256Hasher ( h )
}
} ( )
utils . SplitWork ( - 2 , n , func ( workIndex uint64 , workerIndex int ) error {
2023-03-17 09:01:52 +00:00
output := transaction . Output {
Index : workIndex ,
Type : txType ,
}
output . Reward = tmpRewards [ output . Index ]
2023-05-11 07:51:00 +00:00
output . EphemeralPublicKey , output . ViewTag = derivationCache . GetEphemeralPublicKey ( & tmpShares [ output . Index ] . Address , txPrivateKeySlice , txPrivateKeyScalar , output . Index , hashers [ workerIndex ] )
2023-03-17 09:01:52 +00:00
outputs [ output . Index ] = output
return nil
2023-05-11 07:51:00 +00:00
} , func ( routines , routineIndex int ) error {
hashers = append ( hashers , crypto . GetKeccak256Hasher ( ) )
return nil
2023-05-17 15:29:01 +00:00
} , nil )
2023-03-17 09:01:52 +00:00
2023-03-21 10:01:51 +00:00
return outputs , bottomHeight
2023-03-17 09:01:52 +00:00
}
2023-03-22 10:05:04 +00:00
type PoolBlockWindowSlot struct {
Block * PoolBlock
// Uncles that count for the window weight
Uncles UniquePoolBlockSlice
}
2023-03-17 09:01:52 +00:00
2023-03-22 10:05:04 +00:00
type PoolBlockWindowAddWeightFunc func ( b * PoolBlock , weight types . Difficulty )
2023-03-17 09:01:52 +00:00
2023-06-05 21:42:53 +00:00
func IterateBlocksInPPLNSWindow ( tip * PoolBlock , consensus * Consensus , difficultyByHeight block . GetDifficultyByHeightFunc , getByTemplateId GetByTemplateIdFunc , addWeightFunc PoolBlockWindowAddWeightFunc , slotFunc func ( slot PoolBlockWindowSlot ) ) error {
2023-03-17 09:01:52 +00:00
2023-06-05 21:42:53 +00:00
cur := tip
var blockDepth uint64
var mainchainDiff types . Difficulty
if tip . Side . Parent != types . ZeroHash {
seedHeight := randomx . SeedHeight ( tip . Main . Coinbase . GenHeight )
mainchainDiff = difficultyByHeight ( seedHeight )
if mainchainDiff == types . ZeroDifficulty {
return fmt . Errorf ( "couldn't get mainchain difficulty for height = %d" , seedHeight )
}
}
// Dynamic PPLNS window starting from v2
// Limit PPLNS weight to 2x of the Monero difficulty (max 2 blocks per PPLNS window on average)
sidechainVersion := tip . ShareVersion ( )
maxPplnsWeight := types . MaxDifficulty
if sidechainVersion > ShareVersion_V1 {
maxPplnsWeight = mainchainDiff . Mul64 ( 2 )
}
var pplnsWeight types . Difficulty
for {
curEntry := PoolBlockWindowSlot {
Block : cur ,
}
curWeight := cur . Side . Difficulty
2023-03-17 09:01:52 +00:00
2023-07-07 11:17:11 +00:00
if err := cur . iteratorUncles ( getByTemplateId , func ( uncle * PoolBlock ) {
//Needs to be added regardless - for other consumers
curEntry . Uncles = append ( curEntry . Uncles , uncle )
// Skip uncles which are already out of PPLNS window
if ( tip . Side . Height - uncle . Side . Height ) >= consensus . ChainWindowSize {
return
}
// Take some % of uncle's weight into this share
2023-07-10 14:52:19 +00:00
uncleWeight , unclePenalty := consensus . ApplyUnclePenalty ( uncle . Side . Difficulty )
2023-07-07 11:17:11 +00:00
newPplnsWeight := pplnsWeight . Add ( uncleWeight )
// Skip uncles that push PPLNS weight above the limit
if newPplnsWeight . Cmp ( maxPplnsWeight ) > 0 {
return
2023-03-22 10:05:04 +00:00
}
2023-07-07 11:17:11 +00:00
curWeight = curWeight . Add ( unclePenalty )
if addWeightFunc != nil {
addWeightFunc ( uncle , uncleWeight )
}
pplnsWeight = newPplnsWeight
} ) ; err != nil {
return err
2023-03-17 09:01:52 +00:00
}
2023-06-05 21:42:53 +00:00
// Always add non-uncle shares even if PPLNS weight goes above the limit
slotFunc ( curEntry )
2023-03-17 09:01:52 +00:00
2023-06-05 21:42:53 +00:00
if addWeightFunc != nil {
addWeightFunc ( cur , curWeight )
}
pplnsWeight = pplnsWeight . Add ( curWeight )
2023-03-17 09:01:52 +00:00
2023-06-05 21:42:53 +00:00
// One non-uncle share can go above the limit, but it will also guarantee that "shares" is never empty
if pplnsWeight . Cmp ( maxPplnsWeight ) > 0 {
break
2023-03-22 10:05:04 +00:00
}
2023-03-17 09:01:52 +00:00
2023-06-05 21:42:53 +00:00
blockDepth ++
2023-03-22 10:05:04 +00:00
2023-06-05 21:42:53 +00:00
if blockDepth >= consensus . ChainWindowSize {
break
}
2023-03-22 10:05:04 +00:00
2023-06-05 21:42:53 +00:00
// Reached the genesis block so we're done
if cur . Side . Height == 0 {
break
}
2023-03-22 10:05:04 +00:00
2023-06-05 21:42:53 +00:00
parentId := cur . Side . Parent
2023-07-07 11:17:11 +00:00
cur = cur . iteratorGetParent ( getByTemplateId )
2023-03-22 10:05:04 +00:00
2023-06-05 21:42:53 +00:00
if cur == nil {
return fmt . Errorf ( "could not find parent %s" , parentId . String ( ) )
}
}
return nil
}
2023-03-22 10:05:04 +00:00
2023-06-05 21:42:53 +00:00
func BlocksInPPLNSWindow ( tip * PoolBlock , consensus * Consensus , difficultyByHeight block . GetDifficultyByHeightFunc , getByTemplateId GetByTemplateIdFunc , addWeightFunc PoolBlockWindowAddWeightFunc ) ( bottomHeight uint64 , err error ) {
2023-03-22 10:05:04 +00:00
2023-06-05 21:42:53 +00:00
cur := tip
2023-03-22 10:05:04 +00:00
2023-06-05 21:42:53 +00:00
var blockDepth uint64
2023-03-22 10:05:04 +00:00
2023-06-05 21:42:53 +00:00
var mainchainDiff types . Difficulty
if tip . Side . Parent != types . ZeroHash {
seedHeight := randomx . SeedHeight ( tip . Main . Coinbase . GenHeight )
mainchainDiff = difficultyByHeight ( seedHeight )
if mainchainDiff == types . ZeroDifficulty {
return 0 , fmt . Errorf ( "couldn't get mainchain difficulty for height = %d" , seedHeight )
}
}
2023-03-22 10:05:04 +00:00
2023-06-05 21:42:53 +00:00
// Dynamic PPLNS window starting from v2
// Limit PPLNS weight to 2x of the Monero difficulty (max 2 blocks per PPLNS window on average)
sidechainVersion := tip . ShareVersion ( )
maxPplnsWeight := types . MaxDifficulty
if sidechainVersion > ShareVersion_V1 {
maxPplnsWeight = mainchainDiff . Mul64 ( 2 )
}
2023-03-22 10:05:04 +00:00
2023-06-05 21:42:53 +00:00
var pplnsWeight types . Difficulty
for {
curWeight := cur . Side . Difficulty
2023-07-01 12:17:24 +00:00
if err := cur . iteratorUncles ( getByTemplateId , func ( uncle * PoolBlock ) {
// Skip uncles which are already out of PPLNS window
if ( tip . Side . Height - uncle . Side . Height ) >= consensus . ChainWindowSize {
return
}
2023-06-05 21:42:53 +00:00
2023-07-01 12:17:24 +00:00
// Take some % of uncle's weight into this share
2023-07-10 14:52:19 +00:00
uncleWeight , unclePenalty := consensus . ApplyUnclePenalty ( uncle . Side . Difficulty )
2023-07-01 12:17:24 +00:00
newPplnsWeight := pplnsWeight . Add ( uncleWeight )
2023-06-05 21:42:53 +00:00
2023-07-01 12:17:24 +00:00
// Skip uncles that push PPLNS weight above the limit
if newPplnsWeight . Cmp ( maxPplnsWeight ) > 0 {
return
}
curWeight = curWeight . Add ( unclePenalty )
2023-06-05 21:42:53 +00:00
2023-07-10 14:52:19 +00:00
addWeightFunc ( uncle , uncleWeight )
2023-07-01 12:17:24 +00:00
pplnsWeight = newPplnsWeight
} ) ; err != nil {
return 0 , err
2023-03-22 10:05:04 +00:00
}
2023-06-05 21:42:53 +00:00
// Always add non-uncle shares even if PPLNS weight goes above the limit
bottomHeight = cur . Side . Height
2023-07-10 14:52:19 +00:00
addWeightFunc ( cur , curWeight )
2023-06-05 21:42:53 +00:00
pplnsWeight = pplnsWeight . Add ( curWeight )
// One non-uncle share can go above the limit, but it will also guarantee that "shares" is never empty
if pplnsWeight . Cmp ( maxPplnsWeight ) > 0 {
break
}
blockDepth ++
if blockDepth >= consensus . ChainWindowSize {
break
}
// Reached the genesis block so we're done
if cur . Side . Height == 0 {
break
}
parentId := cur . Side . Parent
2023-07-01 12:17:24 +00:00
cur = cur . iteratorGetParent ( getByTemplateId )
2023-06-05 21:42:53 +00:00
if cur == nil {
return 0 , fmt . Errorf ( "could not find parent %s" , parentId . String ( ) )
}
}
return bottomHeight , nil
2023-03-22 10:05:04 +00:00
}
2023-06-05 21:42:53 +00:00
func GetSharesOrdered ( tip * PoolBlock , consensus * Consensus , difficultyByHeight block . GetDifficultyByHeightFunc , getByTemplateId GetByTemplateIdFunc , preAllocatedShares Shares ) ( shares Shares , bottomHeight uint64 ) {
2023-03-20 07:36:39 +00:00
index := 0
l := len ( preAllocatedShares )
2023-03-17 09:01:52 +00:00
2023-07-01 12:17:24 +00:00
if bottomHeight , err := BlocksInPPLNSWindow ( tip , consensus , difficultyByHeight , getByTemplateId , func ( b * PoolBlock , weight types . Difficulty ) {
2023-03-17 09:01:52 +00:00
if index < l {
2023-07-22 22:18:04 +00:00
preAllocatedShares [ index ] . Address = b . Side . PublicKey
2023-07-05 09:16:36 +00:00
2023-05-14 07:22:01 +00:00
preAllocatedShares [ index ] . Weight = weight
2023-03-17 09:01:52 +00:00
} else {
2023-05-14 07:22:01 +00:00
preAllocatedShares = append ( preAllocatedShares , & Share {
2023-07-22 22:18:04 +00:00
Address : b . Side . PublicKey ,
2023-05-14 07:22:01 +00:00
Weight : weight ,
} )
2023-03-17 09:01:52 +00:00
}
index ++
2023-06-05 21:42:53 +00:00
} ) ; err != nil {
2023-03-22 10:05:04 +00:00
return nil , 0
2023-06-05 21:42:53 +00:00
} else {
shares = preAllocatedShares [ : index ]
2023-03-17 09:01:52 +00:00
2023-06-05 21:42:53 +00:00
//remove dupes
shares = shares . Compact ( )
2023-03-17 09:01:52 +00:00
2023-06-05 21:42:53 +00:00
return shares , bottomHeight
2023-05-14 07:22:01 +00:00
}
2023-06-05 21:42:53 +00:00
}
2023-05-14 07:22:01 +00:00
2023-06-05 21:42:53 +00:00
func GetShares ( tip * PoolBlock , consensus * Consensus , difficultyByHeight block . GetDifficultyByHeightFunc , getByTemplateId GetByTemplateIdFunc , preAllocatedShares Shares ) ( shares Shares , bottomHeight uint64 ) {
shares , bottomHeight = GetSharesOrdered ( tip , consensus , difficultyByHeight , getByTemplateId , preAllocatedShares )
if shares == nil {
return
}
2023-05-14 07:22:01 +00:00
2023-03-17 09:01:52 +00:00
//Shuffle shares
2023-04-22 02:15:27 +00:00
ShuffleShares ( shares , tip . ShareVersion ( ) , tip . Side . CoinbasePrivateKeySeed )
return shares , bottomHeight
}
2023-05-30 23:53:58 +00:00
// ShuffleShares Shuffles shares according to consensus parameters via ShuffleSequence. Requires pre-sorted shares based on address
2023-04-22 02:15:27 +00:00
func ShuffleShares [ T any ] ( shares [ ] T , shareVersion ShareVersion , privateKeySeed types . Hash ) {
2023-05-30 23:53:58 +00:00
ShuffleSequence ( shareVersion , privateKeySeed , len ( shares ) , func ( i , j int ) {
shares [ i ] , shares [ j ] = shares [ j ] , shares [ i ]
} )
}
// ShuffleSequence Iterates through a swap sequence according to consensus parameters.
func ShuffleSequence ( shareVersion ShareVersion , privateKeySeed types . Hash , items int , swap func ( i , j int ) ) {
n := uint64 ( items )
2023-04-22 02:15:27 +00:00
if shareVersion > ShareVersion_V1 && n > 1 {
2023-07-21 14:20:41 +00:00
seed := crypto . PooledKeccak256 ( privateKeySeed [ : ] ) . Uint64 ( )
2023-03-17 09:01:52 +00:00
2023-03-18 21:11:20 +00:00
if seed == 0 {
seed = 1
}
2023-05-23 07:31:20 +00:00
for i := uint64 ( 0 ) ; i < ( n - 1 ) ; i ++ {
2023-03-17 09:01:52 +00:00
seed = utils . XorShift64Star ( seed )
2023-05-23 07:31:20 +00:00
k , _ := bits . Mul64 ( seed , n - i )
2023-03-17 09:01:52 +00:00
//swap
2023-05-30 23:53:58 +00:00
swap ( int ( i ) , int ( i + k ) )
2023-03-17 09:01:52 +00:00
}
}
}
2023-07-20 05:37:10 +00:00
type DifficultyData struct {
cumulativeDifficulty types . Difficulty
timestamp uint64
}
// GetDifficultyForNextBlock Gets the difficulty at tip (the next block will require this difficulty)
2023-07-01 12:17:24 +00:00
// preAllocatedDifficultyData should contain enough capacity to fit all entries to iterate through.
// preAllocatedTimestampDifferences should contain enough capacity to fit all differences.
2023-07-20 05:37:10 +00:00
//
// Ported from SideChain::get_difficulty() from C p2pool,
// somewhat based on Blockchain::get_difficulty_for_next_block() from Monero with the addition of uncles
func GetDifficultyForNextBlock ( tip * PoolBlock , consensus * Consensus , getByTemplateId GetByTemplateIdFunc , preAllocatedDifficultyData [ ] DifficultyData , preAllocatedTimestampData [ ] uint64 ) ( difficulty types . Difficulty , verifyError , invalidError error ) {
2023-05-14 07:22:01 +00:00
2023-07-01 12:17:24 +00:00
difficultyData := preAllocatedDifficultyData [ : 0 ]
2023-05-14 07:22:01 +00:00
2023-07-20 05:37:10 +00:00
timestampData := preAllocatedTimestampData [ : 0 ]
2023-03-17 09:01:52 +00:00
cur := tip
var blockDepth uint64
2023-07-01 12:17:24 +00:00
for {
difficultyData = append ( difficultyData , DifficultyData {
2023-07-20 05:37:10 +00:00
cumulativeDifficulty : cur . Side . CumulativeDifficulty ,
timestamp : cur . Main . Timestamp ,
2023-07-01 12:17:24 +00:00
} )
2023-07-20 05:37:10 +00:00
timestampData = append ( timestampData , cur . Main . Timestamp )
2023-07-04 18:28:54 +00:00
2023-07-01 12:17:24 +00:00
if err := cur . iteratorUncles ( getByTemplateId , func ( uncle * PoolBlock ) {
// Skip uncles which are already out of PPLNS window
if ( tip . Side . Height - uncle . Side . Height ) >= consensus . ChainWindowSize {
return
2023-03-17 09:01:52 +00:00
}
2023-07-01 12:17:24 +00:00
difficultyData = append ( difficultyData , DifficultyData {
2023-07-20 05:37:10 +00:00
cumulativeDifficulty : uncle . Side . CumulativeDifficulty ,
timestamp : uncle . Main . Timestamp ,
2023-07-01 12:17:24 +00:00
} )
2023-07-04 18:28:54 +00:00
2023-07-20 05:37:10 +00:00
timestampData = append ( timestampData , uncle . Main . Timestamp )
2023-07-01 12:17:24 +00:00
} ) ; err != nil {
return types . ZeroDifficulty , err , nil
2023-03-17 09:01:52 +00:00
}
blockDepth ++
if blockDepth >= consensus . ChainWindowSize {
break
}
// Reached the genesis block so we're done
if cur . Side . Height == 0 {
break
}
2023-04-26 07:32:52 +00:00
parentId := cur . Side . Parent
2023-07-01 12:17:24 +00:00
cur = cur . iteratorGetParent ( getByTemplateId )
2023-03-17 09:01:52 +00:00
if cur == nil {
2023-07-01 12:17:24 +00:00
return types . ZeroDifficulty , fmt . Errorf ( "could not find parent %s" , parentId . String ( ) ) , nil
2023-03-17 09:01:52 +00:00
}
}
2023-07-20 05:37:10 +00:00
difficulty , invalidError = NextDifficulty ( consensus , timestampData , difficultyData )
return
}
2023-07-01 12:17:24 +00:00
2023-07-20 05:37:10 +00:00
// NextDifficulty returns the next block difficulty based on gathered timestamp/difficulty data
// Returns error on wrap/overflow/underflow on uint128 operations
func NextDifficulty ( consensus * Consensus , timestamps [ ] uint64 , difficultyData [ ] DifficultyData ) ( nextDifficulty types . Difficulty , err error ) {
2023-03-17 09:01:52 +00:00
// Discard 10% oldest and 10% newest (by timestamp) blocks
2023-07-20 05:37:10 +00:00
cutSize := ( len ( timestamps ) + 9 ) / 10
2023-07-04 09:43:25 +00:00
lowIndex := cutSize - 1
2023-07-20 05:37:10 +00:00
upperIndex := len ( timestamps ) - cutSize
2023-03-17 09:01:52 +00:00
2023-07-20 05:37:10 +00:00
utils . NthElementSlice ( timestamps , lowIndex )
timestampLowerBound := timestamps [ lowIndex ]
2023-07-04 18:28:54 +00:00
2023-07-20 05:37:10 +00:00
utils . NthElementSlice ( timestamps , upperIndex )
timestampUpperBound := timestamps [ upperIndex ]
2023-03-17 09:01:52 +00:00
2023-07-04 09:43:25 +00:00
// Make a reasonable assumption that each block has higher timestamp, so deltaTimestamp can't be less than deltaIndex
2023-03-27 12:37:50 +00:00
// Because if it is, someone is trying to mess with timestamps
2023-07-04 09:43:25 +00:00
// In reality, deltaTimestamp ~ deltaIndex*10 (sidechain block time)
2023-03-27 12:37:50 +00:00
deltaIndex := uint64 ( 1 )
2023-07-20 05:37:10 +00:00
if upperIndex > lowIndex {
2023-07-04 09:43:25 +00:00
deltaIndex = uint64 ( upperIndex - lowIndex )
2023-03-27 12:37:50 +00:00
}
2023-07-04 09:43:25 +00:00
deltaTimestamp := deltaIndex
if timestampUpperBound > ( timestampLowerBound + deltaIndex ) {
deltaTimestamp = timestampUpperBound - timestampLowerBound
2023-03-17 09:01:52 +00:00
}
2023-07-04 09:43:25 +00:00
var minDifficulty = types . Difficulty { Hi : math . MaxUint64 , Lo : math . MaxUint64 }
var maxDifficulty types . Difficulty
2023-03-17 09:01:52 +00:00
for i := range difficultyData {
2023-07-20 05:37:10 +00:00
// Pick only the cumulative difficulty from specifically the entries that are within the timestamp upper and low bounds
if timestampLowerBound <= difficultyData [ i ] . timestamp && difficultyData [ i ] . timestamp <= timestampUpperBound {
2023-07-24 17:48:44 +00:00
if minDifficulty . Cmp ( difficultyData [ i ] . cumulativeDifficulty ) > 0 {
2023-07-20 05:37:10 +00:00
minDifficulty = difficultyData [ i ] . cumulativeDifficulty
2023-03-17 09:01:52 +00:00
}
2023-07-20 05:37:10 +00:00
if maxDifficulty . Cmp ( difficultyData [ i ] . cumulativeDifficulty ) < 0 {
maxDifficulty = difficultyData [ i ] . cumulativeDifficulty
2023-03-17 09:01:52 +00:00
}
}
}
2023-07-20 05:37:10 +00:00
// Specific section that could wrap and needs to be detected
// Use calls that panic on wrap/overflow/underflow
{
defer func ( ) {
if e := recover ( ) ; e != nil {
if panicError , ok := e . ( error ) ; ok {
err = fmt . Errorf ( "panic in NextDifficulty, wrap occured?: %w" , panicError )
} else {
err = fmt . Errorf ( "panic in NextDifficulty, wrap occured?: %v" , e )
}
}
} ( )
2023-03-17 09:01:52 +00:00
2023-07-20 05:37:10 +00:00
deltaDifficulty := uint128 . Uint128 ( maxDifficulty ) . Sub ( uint128 . Uint128 ( minDifficulty ) )
curDifficulty := deltaDifficulty . Mul64 ( consensus . TargetBlockTime ) . Div64 ( deltaTimestamp )
2023-03-17 09:01:52 +00:00
2023-07-20 05:37:10 +00:00
if curDifficulty . Cmp64 ( consensus . MinimumDifficulty ) < 0 {
return types . DifficultyFrom64 ( consensus . MinimumDifficulty ) , nil
}
return types . Difficulty ( curDifficulty ) , nil
2023-03-17 09:01:52 +00:00
}
}
2023-07-12 10:32:02 +00:00
func SplitRewardAllocate ( reward uint64 , shares Shares ) ( rewards [ ] uint64 ) {
return SplitReward ( make ( [ ] uint64 , 0 , len ( shares ) ) , reward , shares )
}
func SplitReward ( preAllocatedRewards [ ] uint64 , reward uint64 , shares Shares ) ( rewards [ ] uint64 ) {
2023-03-17 09:01:52 +00:00
var totalWeight types . Difficulty
for i := range shares {
totalWeight = totalWeight . Add ( shares [ i ] . Weight )
}
if totalWeight . Equals64 ( 0 ) {
//TODO: err
return nil
}
2023-07-24 09:48:01 +00:00
rewards = preAllocatedRewards [ : 0 ]
2023-03-17 09:01:52 +00:00
var w types . Difficulty
var rewardGiven uint64
2023-07-12 10:32:02 +00:00
for _ , share := range shares {
w = w . Add ( share . Weight )
2023-03-17 09:01:52 +00:00
nextValue := w . Mul64 ( reward ) . Div ( totalWeight )
2023-06-05 21:42:53 +00:00
rewards = append ( rewards , nextValue . Lo - rewardGiven )
2023-03-17 09:01:52 +00:00
rewardGiven = nextValue . Lo
}
// Double check that we gave out the exact amount
rewardGiven = 0
for _ , r := range rewards {
rewardGiven += r
}
if rewardGiven != reward {
return nil
}
return rewards
}
2023-03-27 11:56:27 +00:00
func IsLongerChain ( block , candidate * PoolBlock , consensus * Consensus , getByTemplateId GetByTemplateIdFunc , getChainMainByHash GetChainMainByHashFunc ) ( isLonger , isAlternative bool ) {
if candidate == nil || ! candidate . Verified . Load ( ) || candidate . Invalid . Load ( ) {
return false , false
}
// Switching from an empty to a non-empty chain
if block == nil {
return true , true
}
// If these two blocks are on the same chain, they must have a common ancestor
blockAncestor := block
for blockAncestor != nil && blockAncestor . Side . Height > candidate . Side . Height {
2023-07-05 09:37:27 +00:00
blockAncestor = blockAncestor . iteratorGetParent ( getByTemplateId )
2023-03-27 11:56:27 +00:00
//TODO: err on blockAncestor nil
}
if blockAncestor != nil {
candidateAncestor := candidate
for candidateAncestor != nil && candidateAncestor . Side . Height > blockAncestor . Side . Height {
2023-07-05 09:37:27 +00:00
candidateAncestor = candidateAncestor . iteratorGetParent ( getByTemplateId )
2023-03-27 11:56:27 +00:00
//TODO: err on candidateAncestor nil
}
for blockAncestor != nil && candidateAncestor != nil {
2023-07-05 09:37:27 +00:00
if blockAncestor . Side . Parent == candidateAncestor . Side . Parent {
2023-03-27 11:56:27 +00:00
return block . Side . CumulativeDifficulty . Cmp ( candidate . Side . CumulativeDifficulty ) < 0 , false
}
2023-07-05 09:37:27 +00:00
blockAncestor = blockAncestor . iteratorGetParent ( getByTemplateId )
candidateAncestor = candidateAncestor . iteratorGetParent ( getByTemplateId )
2023-03-27 11:56:27 +00:00
}
}
// They're on totally different chains. Compare total difficulties over the last m_chainWindowSize blocks
var blockTotalDiff , candidateTotalDiff types . Difficulty
oldChain := block
newChain := candidate
var candidateMainchainHeight , candidateMainchainMinHeight uint64
2023-03-27 12:34:57 +00:00
var moneroBlocksReserve = consensus . ChainWindowSize * consensus . TargetBlockTime * 2 / monero . BlockTime
currentChainMoneroBlocks , candidateChainMoneroBlocks := make ( [ ] types . Hash , 0 , moneroBlocksReserve ) , make ( [ ] types . Hash , 0 , moneroBlocksReserve )
2023-03-27 11:56:27 +00:00
for i := uint64 ( 0 ) ; i < consensus . ChainWindowSize && ( oldChain != nil || newChain != nil ) ; i ++ {
if oldChain != nil {
blockTotalDiff = blockTotalDiff . Add ( oldChain . Side . Difficulty )
2023-07-05 09:37:27 +00:00
_ = oldChain . iteratorUncles ( getByTemplateId , func ( uncle * PoolBlock ) {
blockTotalDiff = blockTotalDiff . Add ( uncle . Side . Difficulty )
} )
2023-03-27 12:34:57 +00:00
if ! slices . Contains ( currentChainMoneroBlocks , oldChain . Main . PreviousId ) && getChainMainByHash ( oldChain . Main . PreviousId ) != nil {
currentChainMoneroBlocks = append ( currentChainMoneroBlocks , oldChain . Main . PreviousId )
}
2023-07-05 09:37:27 +00:00
oldChain = oldChain . iteratorGetParent ( getByTemplateId )
2023-03-27 11:56:27 +00:00
}
if newChain != nil {
if candidateMainchainMinHeight != 0 {
2023-06-27 09:36:00 +00:00
candidateMainchainMinHeight = min ( candidateMainchainMinHeight , newChain . Main . Coinbase . GenHeight )
2023-03-27 11:56:27 +00:00
} else {
candidateMainchainMinHeight = newChain . Main . Coinbase . GenHeight
}
candidateTotalDiff = candidateTotalDiff . Add ( newChain . Side . Difficulty )
2023-07-05 09:37:27 +00:00
_ = newChain . iteratorUncles ( getByTemplateId , func ( uncle * PoolBlock ) {
candidateTotalDiff = candidateTotalDiff . Add ( uncle . Side . Difficulty )
} )
2023-03-27 12:34:57 +00:00
if ! slices . Contains ( candidateChainMoneroBlocks , newChain . Main . PreviousId ) {
2023-03-27 11:56:27 +00:00
if data := getChainMainByHash ( newChain . Main . PreviousId ) ; data != nil {
2023-03-27 12:34:57 +00:00
candidateChainMoneroBlocks = append ( candidateChainMoneroBlocks , newChain . Main . PreviousId )
2023-06-27 09:36:00 +00:00
candidateMainchainHeight = max ( candidateMainchainHeight , data . Height )
2023-03-27 11:56:27 +00:00
}
}
2023-07-05 09:37:27 +00:00
newChain = newChain . iteratorGetParent ( getByTemplateId )
2023-03-27 11:56:27 +00:00
}
}
if blockTotalDiff . Cmp ( candidateTotalDiff ) >= 0 {
return false , true
}
2023-03-27 12:34:57 +00:00
// Candidate chain must be built on top of recent mainchain blocks
2023-03-27 11:56:27 +00:00
if headerTip := getChainMainByHash ( types . ZeroHash ) ; headerTip != nil {
if candidateMainchainHeight + 10 < headerTip . Height {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "Received a longer alternative chain but it's stale: height %d, current height %d" , candidateMainchainHeight , headerTip . Height )
2023-03-27 11:56:27 +00:00
return false , true
}
limit := consensus . ChainWindowSize * 4 * consensus . TargetBlockTime / monero . BlockTime
if candidateMainchainMinHeight + limit < headerTip . Height {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "Received a longer alternative chain but it's stale: min height %d, must be >= %d" , candidateMainchainMinHeight , headerTip . Height - limit )
2023-03-27 12:34:57 +00:00
return false , true
}
// Candidate chain must have been mined on top of at least half as many known Monero blocks, compared to the current chain
if len ( candidateChainMoneroBlocks ) * 2 < len ( currentChainMoneroBlocks ) {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "Received a longer alternative chain but it wasn't mined on current Monero blockchain: only %d / %d blocks found" , len ( candidateChainMoneroBlocks ) , len ( currentChainMoneroBlocks ) )
2023-03-27 11:56:27 +00:00
return false , true
}
return true , true
} else {
return false , true
}
}