2022-11-03 11:32:07 +00:00
package sidechain
import (
"bytes"
2023-03-07 17:57:06 +00:00
"encoding/binary"
2022-11-03 11:32:07 +00:00
"errors"
"fmt"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
2022-11-07 22:59:52 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
2022-11-03 11:32:07 +00:00
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
2023-03-05 14:06:49 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
2023-03-07 17:57:06 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
2023-03-05 14:06:49 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
2022-11-03 11:32:07 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
"golang.org/x/exp/slices"
2022-11-06 10:58:19 +00:00
"log"
2023-03-07 17:57:06 +00:00
"lukechampine.com/uint128"
2022-11-03 11:32:07 +00:00
"math"
"sync"
"sync/atomic"
2022-12-09 12:13:00 +00:00
"time"
2022-11-03 11:32:07 +00:00
)
2022-11-06 20:11:03 +00:00
type Cache interface {
GetBlob ( key [ ] byte ) ( blob [ ] byte , err error )
SetBlob ( key , blob [ ] byte ) ( err error )
RemoveBlob ( key [ ] byte ) ( err error )
}
type P2PoolInterface interface {
2022-11-03 11:32:07 +00:00
ConsensusProvider
2022-11-06 20:11:03 +00:00
Cache
2022-11-03 11:32:07 +00:00
Broadcast ( block * PoolBlock )
2023-03-05 14:06:49 +00:00
ClientRPC ( ) * client . Client
GetChainMainByHeight ( height uint64 ) * ChainMain
GetChainMainByHash ( hash types . Hash ) * ChainMain
GetMinimalBlockHeaderByHeight ( height uint64 ) * mainblock . Header
GetMinimalBlockHeaderByHash ( hash types . Hash ) * mainblock . Header
GetDifficultyByHeight ( height uint64 ) types . Difficulty
UpdateBlockFound ( data * ChainMain , block * PoolBlock )
SubmitBlock ( block * mainblock . Block )
GetChainMainTip ( ) * ChainMain
}
type ChainMain struct {
Difficulty types . Difficulty
Height uint64
Timestamp uint64
Reward uint64
Id types . Hash
2022-11-03 11:32:07 +00:00
}
type SideChain struct {
2022-12-09 12:13:00 +00:00
derivationCache * DerivationCache
server P2PoolInterface
2022-11-03 11:32:07 +00:00
2022-11-06 12:17:26 +00:00
sidechainLock sync . RWMutex
2023-03-05 14:06:49 +00:00
watchBlock * ChainMain
watchBlockSidechainId types . Hash
2022-11-06 12:17:26 +00:00
sharesCache Shares
2022-11-03 11:32:07 +00:00
blocksByTemplateId map [ types . Hash ] * PoolBlock
blocksByHeight map [ uint64 ] [ ] * PoolBlock
chainTip atomic . Pointer [ PoolBlock ]
currentDifficulty atomic . Pointer [ types . Difficulty ]
2022-12-09 12:13:00 +00:00
precalcFinished atomic . Bool
2022-11-03 11:32:07 +00:00
}
2022-11-06 20:11:03 +00:00
func NewSideChain ( server P2PoolInterface ) * SideChain {
2022-11-03 11:32:07 +00:00
return & SideChain {
2022-12-09 12:13:00 +00:00
derivationCache : NewDerivationCache ( ) ,
2022-11-03 11:32:07 +00:00
server : server ,
blocksByTemplateId : make ( map [ types . Hash ] * PoolBlock ) ,
blocksByHeight : make ( map [ uint64 ] [ ] * PoolBlock ) ,
2022-11-06 12:17:26 +00:00
sharesCache : make ( Shares , 0 , server . Consensus ( ) . ChainWindowSize * 2 ) ,
2022-11-03 11:32:07 +00:00
}
}
func ( c * SideChain ) Consensus ( ) * Consensus {
return c . server . Consensus ( )
}
2022-12-04 14:15:33 +00:00
func ( c * SideChain ) PreCalcFinished ( ) bool {
2022-12-09 12:13:00 +00:00
return c . precalcFinished . Load ( )
2022-12-04 14:15:33 +00:00
}
2022-12-04 01:20:36 +00:00
func ( c * SideChain ) PreprocessBlock ( block * PoolBlock ) ( missingBlocks [ ] types . Hash , err error ) {
2022-11-06 12:17:26 +00:00
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
2022-11-03 11:32:07 +00:00
if len ( block . Main . Coinbase . Outputs ) == 0 {
if outputs := c . getOutputs ( block ) ; outputs == nil {
2022-12-04 01:20:36 +00:00
return nil , errors . New ( "nil transaction outputs" )
2022-11-03 11:32:07 +00:00
} else {
block . Main . Coinbase . Outputs = outputs
}
if outputBlob , err := block . Main . Coinbase . OutputsBlob ( ) ; err != nil {
2022-12-04 01:20:36 +00:00
return nil , err
2022-11-03 11:32:07 +00:00
} else if uint64 ( len ( outputBlob ) ) != block . Main . Coinbase . OutputsBlobSize {
2022-12-04 01:20:36 +00:00
return nil , fmt . Errorf ( "invalid output blob size, got %d, expected %d" , block . Main . Coinbase . OutputsBlobSize , len ( outputBlob ) )
2022-11-03 11:32:07 +00:00
}
}
2022-12-03 22:43:18 +00:00
if len ( block . Main . TransactionParentIndices ) > 0 && len ( block . Main . TransactionParentIndices ) == len ( block . Main . Transactions ) {
2022-12-04 14:00:05 +00:00
if slices . Index ( block . Main . Transactions , types . ZeroHash ) != - 1 { //only do this when zero hashes exist
parent := c . getParent ( block )
if parent == nil {
missingBlocks = append ( missingBlocks , block . Side . Parent )
return missingBlocks , errors . New ( "parent does not exist in compact block" )
}
for i , parentIndex := range block . Main . TransactionParentIndices {
if parentIndex != 0 {
// p2pool stores coinbase transaction hash as well, decrease
actualIndex := parentIndex - 1
if actualIndex > uint64 ( len ( parent . Main . Transactions ) ) {
return nil , errors . New ( "index of parent transaction out of bounds" )
}
block . Main . Transactions [ i ] = parent . Main . Transactions [ actualIndex ]
2022-12-03 22:43:18 +00:00
}
}
}
2022-12-04 00:46:22 +00:00
} else {
// fill if not received from network
2022-12-12 13:20:08 +00:00
c . fillPoolBlockTransactionParentIndices ( block )
}
return nil , nil
}
func ( c * SideChain ) fillPoolBlockTransactionParentIndices ( block * PoolBlock ) {
if len ( block . Main . Transactions ) != len ( block . Main . TransactionParentIndices ) {
2022-12-04 00:46:22 +00:00
parent := c . getParent ( block )
if parent != nil {
2022-12-12 13:20:08 +00:00
block . Main . TransactionParentIndices = make ( [ ] uint64 , len ( block . Main . Transactions ) )
2022-12-04 00:46:22 +00:00
//do not fail if not found
for i , txHash := range block . Main . Transactions {
if parentIndex := slices . Index ( parent . Main . Transactions , txHash ) ; parentIndex != - 1 {
//increase as p2pool stores tx hash as well
block . Main . TransactionParentIndices [ i ] = uint64 ( parentIndex + 1 )
}
}
}
2022-12-03 22:43:18 +00:00
}
2022-11-03 11:32:07 +00:00
}
2022-11-06 20:11:03 +00:00
func ( c * SideChain ) isPoolBlockTransactionKeyIsDeterministic ( block * PoolBlock ) bool {
2023-03-07 17:57:06 +00:00
kP := c . derivationCache . GetDeterministicTransactionKey ( block . GetPrivateKeySeed ( ) , block . Main . PreviousId )
if block . ShareVersion ( ) > ShareVersion_V1 {
block . Side . CoinbasePrivateKey = kP . PrivateKey . AsBytes ( )
}
return bytes . Compare ( block . CoinbaseExtra ( SideCoinbasePublicKey ) , kP . PublicKey . AsSlice ( ) ) == 0 && bytes . Compare ( kP . PrivateKey . AsSlice ( ) , block . Side . CoinbasePrivateKey [ : ] ) == 0
2022-11-06 20:11:03 +00:00
}
2023-03-05 14:06:49 +00:00
func ( c * SideChain ) getSeedByHeightFunc ( ) mainblock . GetSeedByHeightFunc {
return func ( height uint64 ) ( hash types . Hash ) {
seedHeight := randomx . SeedHeight ( height )
if h := c . server . GetMinimalBlockHeaderByHeight ( seedHeight ) ; h != nil {
return h . Id
} else {
return types . ZeroHash
}
}
}
2022-11-06 10:58:19 +00:00
func ( c * SideChain ) AddPoolBlockExternal ( block * PoolBlock ) ( missingBlocks [ ] types . Hash , err error ) {
2022-11-03 11:32:07 +00:00
// Technically some p2pool node could keep stuffing block with transactions until reward is less than 0.6 XMR
// But default transaction picking algorithm never does that. It's better to just ban such nodes
2022-11-06 20:11:03 +00:00
if block . Main . Coinbase . TotalReward < monero . TailEmissionReward {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "block reward too low" )
2022-11-03 11:32:07 +00:00
}
// Enforce deterministic tx keys starting from v15
if block . Main . MajorVersion >= monero . HardForkViewTagsVersion {
2022-11-06 20:11:03 +00:00
if ! c . isPoolBlockTransactionKeyIsDeterministic ( block ) {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "invalid deterministic transaction keys" )
2022-11-03 11:32:07 +00:00
}
}
// Both tx types are allowed by Monero consensus during v15 because it needs to process pre-fork mempool transactions,
// but P2Pool can switch to using only TXOUT_TO_TAGGED_KEY for miner payouts starting from v15
expectedTxType := c . GetTransactionOutputType ( block . Main . MajorVersion )
2022-12-04 01:20:36 +00:00
if missingBlocks , err = c . PreprocessBlock ( block ) ; err != nil {
return missingBlocks , err
2022-11-03 11:32:07 +00:00
}
for _ , o := range block . Main . Coinbase . Outputs {
if o . Type != expectedTxType {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "unexpected transaction type" )
2022-11-03 11:32:07 +00:00
}
}
2023-03-07 17:57:06 +00:00
templateId := c . Consensus ( ) . CalculateSideTemplateId ( block )
2022-11-03 11:32:07 +00:00
if templateId != block . SideTemplateId ( c . Consensus ( ) ) {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "invalid template id %s, expected %s" , templateId . String ( ) , block . SideTemplateId ( c . Consensus ( ) ) . String ( ) )
2022-11-03 11:32:07 +00:00
}
if block . Side . Difficulty . Cmp64 ( c . Consensus ( ) . MinimumDifficulty ) < 0 {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "block mined by %s has invalid difficulty %s, expected >= %d" , block . GetAddress ( ) . ToBase58 ( ) , block . Side . Difficulty . StringNumeric ( ) , c . Consensus ( ) . MinimumDifficulty )
2022-11-03 11:32:07 +00:00
}
//TODO: cache?
2022-11-06 10:58:19 +00:00
//expectedDifficulty := c.GetDifficulty(block)
//tooLowDiff := block.Side.Difficulty.Cmp(expectedDifficulty) < 0
tooLowDiff := false
var expectedDifficulty types . Difficulty
2022-11-03 11:32:07 +00:00
if otherBlock := c . GetPoolBlockByTemplateId ( templateId ) ; otherBlock != nil {
//already added
//TODO: specifically check Main id for nonce changes! p2pool does not do this
2022-11-06 10:58:19 +00:00
return nil , nil
2022-11-03 11:32:07 +00:00
}
// This is mainly an anti-spam measure, not an actual verification step
if tooLowDiff {
// Reduce required diff by 50% (by doubling this block's diff) to account for alternative chains
diff2 := block . Side . Difficulty . Mul64 ( 2 )
tip := c . GetChainTip ( )
for tmp := tip ; tmp != nil && ( tmp . Side . Height + c . Consensus ( ) . ChainWindowSize > tip . Side . Height ) ; tmp = c . GetParent ( tmp ) {
if diff2 . Cmp ( tmp . Side . Difficulty ) >= 0 {
tooLowDiff = false
break
}
}
}
//TODO log
if tooLowDiff {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "block mined by %s has too low difficulty %s, expected >= %s" , block . GetAddress ( ) . ToBase58 ( ) , block . Side . Difficulty . StringNumeric ( ) , expectedDifficulty . StringNumeric ( ) )
2022-11-03 11:32:07 +00:00
}
// This check is not always possible to perform because of mainchain reorgs
//TODO: cache current miner data?
2023-03-05 14:06:49 +00:00
if data := c . server . GetChainMainByHash ( block . Main . PreviousId ) ; data != nil {
2022-11-03 11:32:07 +00:00
if data . Height + 1 != block . Main . Coinbase . GenHeight {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "wrong mainchain height %d, expected %d" , block . Main . Coinbase . GenHeight , data . Height + 1 )
2022-11-03 11:32:07 +00:00
}
} else {
//TODO warn unknown block, reorg
}
2023-03-05 14:06:49 +00:00
if _ , err := block . PowHashWithError ( c . getSeedByHeightFunc ( ) ) ; err != nil {
2022-11-06 10:58:19 +00:00
return nil , err
2022-11-03 11:32:07 +00:00
} else {
2023-03-05 14:06:49 +00:00
if isHigherMainChain , err := block . IsProofHigherThanMainDifficultyWithError ( c . server . GetDifficultyByHeight , c . getSeedByHeightFunc ( ) ) ; err != nil {
log . Printf ( "[SideChain] add_external_block: couldn't get mainchain difficulty for height = %d: %s" , block . Main . Coinbase . GenHeight , err )
} else if isHigherMainChain {
log . Printf ( "[SideChain]: add_external_block: block %s has enough PoW for Monero height %d, submitting it" , templateId . String ( ) , block . Main . Coinbase . GenHeight )
c . server . SubmitBlock ( & block . Main )
}
if isHigher , err := block . IsProofHigherThanDifficultyWithError ( c . getSeedByHeightFunc ( ) ) ; err != nil {
2022-11-06 10:58:19 +00:00
return nil , err
2022-11-03 11:32:07 +00:00
} else if ! isHigher {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "not enough PoW for height = %d, mainchain height %d" , block . Side . Height , block . Main . Coinbase . GenHeight )
2022-11-03 11:32:07 +00:00
}
}
//TODO: block found section
2022-11-06 10:58:19 +00:00
return func ( ) [ ] types . Hash {
2022-11-06 12:17:26 +00:00
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
2022-11-06 10:58:19 +00:00
missing := make ( [ ] types . Hash , 0 , 4 )
if block . Side . Parent != types . ZeroHash && c . getPoolBlockByTemplateId ( block . Side . Parent ) == nil {
missing = append ( missing , block . Side . Parent )
}
for _ , uncleId := range block . Side . Uncles {
if uncleId != types . ZeroHash && c . getPoolBlockByTemplateId ( uncleId ) == nil {
missing = append ( missing , uncleId )
}
}
return missing
} ( ) , c . AddPoolBlock ( block )
2022-11-03 11:32:07 +00:00
}
func ( c * SideChain ) AddPoolBlock ( block * PoolBlock ) ( err error ) {
2022-11-07 14:58:02 +00:00
2022-11-06 12:17:26 +00:00
c . sidechainLock . Lock ( )
defer c . sidechainLock . Unlock ( )
2022-11-03 11:32:07 +00:00
if _ , ok := c . blocksByTemplateId [ block . SideTemplateId ( c . Consensus ( ) ) ] ; ok {
//already inserted
//TODO WARN
return nil
}
c . blocksByTemplateId [ block . SideTemplateId ( c . Consensus ( ) ) ] = block
2022-11-07 18:38:29 +00:00
log . Printf ( "[SideChain] add_block: height = %d, id = %s, mainchain height = %d, verified = %t, total = %d" , block . Side . Height , block . SideTemplateId ( c . Consensus ( ) ) , block . Main . Coinbase . GenHeight , block . Verified . Load ( ) , len ( c . blocksByTemplateId ) )
2022-11-07 14:58:02 +00:00
2023-03-05 14:06:49 +00:00
if block . SideTemplateId ( c . Consensus ( ) ) == c . watchBlockSidechainId {
c . server . UpdateBlockFound ( c . watchBlock , block )
c . watchBlockSidechainId = types . ZeroHash
}
2022-11-03 11:32:07 +00:00
if l , ok := c . blocksByHeight [ block . Side . Height ] ; ok {
c . blocksByHeight [ block . Side . Height ] = append ( l , block )
} else {
c . blocksByHeight [ block . Side . Height ] = [ ] * PoolBlock { block }
}
c . updateDepths ( block )
if block . Verified . Load ( ) {
if ! block . Invalid . Load ( ) {
c . updateChainTip ( block )
}
2022-11-06 10:58:19 +00:00
return nil
2022-11-03 11:32:07 +00:00
} else {
2022-11-06 10:58:19 +00:00
return c . verifyLoop ( block )
2022-11-03 11:32:07 +00:00
}
}
2022-11-06 10:58:19 +00:00
func ( c * SideChain ) verifyLoop ( blockToVerify * PoolBlock ) ( err error ) {
2022-11-03 11:32:07 +00:00
// PoW is already checked at this point
blocksToVerify := make ( [ ] * PoolBlock , 1 , 8 )
2022-11-06 10:58:19 +00:00
blocksToVerify [ 0 ] = blockToVerify
2022-11-03 11:32:07 +00:00
var highestBlock * PoolBlock
for len ( blocksToVerify ) != 0 {
2022-11-06 10:58:19 +00:00
block := blocksToVerify [ len ( blocksToVerify ) - 1 ]
2022-11-03 11:32:07 +00:00
blocksToVerify = blocksToVerify [ : len ( blocksToVerify ) - 1 ]
if block . Verified . Load ( ) {
continue
}
2022-11-06 10:58:19 +00:00
if verification , invalid := c . verifyBlock ( block ) ; invalid != nil {
log . Printf ( "[SideChain] block at height = %d, id = %s, mainchain height = %d, mined by %s is invalid: %s" , block . Side . Height , block . SideTemplateId ( c . Consensus ( ) ) , block . Main . Coinbase . GenHeight , block . GetAddress ( ) . ToBase58 ( ) , invalid . Error ( ) )
block . Invalid . Store ( true )
block . Verified . Store ( verification == nil )
if block == blockToVerify {
//Save error for return
err = invalid
}
} else if verification != nil {
2022-11-07 14:58:02 +00:00
//log.Printf("[SideChain] can't verify block at height = %d, id = %s, mainchain height = %d, mined by %s: %s", block.Side.Height, block.SideTemplateId(c.Consensus()), block.Main.Coinbase.GenHeight, block.GetAddress().ToBase58(), verification.Error())
2022-11-06 10:58:19 +00:00
block . Verified . Store ( false )
block . Invalid . Store ( false )
2022-11-03 11:32:07 +00:00
} else {
2022-11-06 10:58:19 +00:00
block . Verified . Store ( true )
block . Invalid . Store ( false )
log . Printf ( "[SideChain] verified block at height = %d, depth = %d, id = %s, mainchain height = %d, mined by %s" , block . Side . Height , block . Depth . Load ( ) , block . SideTemplateId ( c . Consensus ( ) ) , block . Main . Coinbase . GenHeight , block . GetAddress ( ) . ToBase58 ( ) )
2022-11-03 11:32:07 +00:00
// This block is now verified
2022-11-06 12:17:26 +00:00
if isLongerChain , _ := c . isLongerChain ( highestBlock , block ) ; isLongerChain {
2022-11-03 11:32:07 +00:00
highestBlock = block
} else if highestBlock != nil && highestBlock . Side . Height > block . Side . Height {
2022-11-06 10:58:19 +00:00
log . Printf ( "[SideChain] block at height = %d, id = %s, is not a longer chain than height = %d, id = %s" , block . Side . Height , block . SideTemplateId ( c . Consensus ( ) ) , highestBlock . Side . Height , highestBlock . SideTemplateId ( c . Consensus ( ) ) )
2022-11-03 11:32:07 +00:00
}
2022-12-12 13:20:08 +00:00
c . fillPoolBlockTransactionParentIndices ( block )
2022-11-03 11:32:07 +00:00
if block . WantBroadcast . Load ( ) && ! block . Broadcasted . Swap ( true ) {
if block . Depth . Load ( ) < UncleBlockDepth {
c . server . Broadcast ( block )
}
}
2022-11-06 20:11:03 +00:00
//store for faster startup
2022-12-11 13:00:00 +00:00
c . saveBlock ( block )
2022-11-03 11:32:07 +00:00
// Try to verify blocks on top of this one
for i := uint64 ( 1 ) ; i <= UncleBlockDepth ; i ++ {
blocksToVerify = append ( blocksToVerify , c . blocksByHeight [ block . Side . Height + i ] ... )
}
}
}
if highestBlock != nil {
c . updateChainTip ( highestBlock )
}
2022-11-06 10:58:19 +00:00
return
2022-11-03 11:32:07 +00:00
}
2022-11-06 10:58:19 +00:00
func ( c * SideChain ) verifyBlock ( block * PoolBlock ) ( verification error , invalid error ) {
2022-11-03 11:32:07 +00:00
// Genesis
if block . Side . Height == 0 {
if block . Side . Parent != types . ZeroHash ||
len ( block . Side . Uncles ) != 0 ||
block . Side . Difficulty . Cmp64 ( c . Consensus ( ) . MinimumDifficulty ) != 0 ||
2023-03-07 17:57:06 +00:00
block . Side . CumulativeDifficulty . Cmp64 ( c . Consensus ( ) . MinimumDifficulty ) != 0 ||
( block . ShareVersion ( ) > ShareVersion_V1 && block . Side . CoinbasePrivateKeySeed == types . ZeroHash ) {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "genesis block has invalid parameters" )
2022-11-03 11:32:07 +00:00
}
//this does not verify coinbase outputs, but that's fine
2022-11-06 10:58:19 +00:00
return nil , nil
2022-11-03 11:32:07 +00:00
}
// Deep block
//
// Blocks in PPLNS window (m_chainWindowSize) require up to m_chainWindowSize earlier blocks to verify
// If a block is deeper than m_chainWindowSize * 2 - 1 it can't influence blocks in PPLNS window
// Also, having so many blocks on top of this one means it was verified by the network at some point
// We skip checks in this case to make pruning possible
if block . Depth . Load ( ) >= c . Consensus ( ) . ChainWindowSize * 2 {
2022-11-06 10:58:19 +00:00
log . Printf ( "[SideChain] block at height = %d, id = %s skipped verification" , block . Side . Height , block . SideTemplateId ( c . Consensus ( ) ) )
return nil , nil
2022-11-03 11:32:07 +00:00
}
//Regular block
//Must have parent
if block . Side . Parent == types . ZeroHash {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "block must have a parent" )
2022-11-03 11:32:07 +00:00
}
if parent := c . getParent ( block ) ; parent != nil {
// If it's invalid then this block is also invalid
2022-11-06 10:58:19 +00:00
if ! parent . Verified . Load ( ) {
return errors . New ( "parent is not verified" ) , nil
}
2022-11-03 11:32:07 +00:00
if parent . Invalid . Load ( ) {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "parent is invalid" )
2022-11-03 11:32:07 +00:00
}
2023-03-07 17:57:06 +00:00
if block . ShareVersion ( ) > ShareVersion_V1 {
expectedSeed := parent . Side . CoinbasePrivateKeySeed
if parent . Main . PreviousId != block . Main . PreviousId {
expectedSeed = parent . CalculateTransactionPrivateKeySeed ( )
}
if block . Side . CoinbasePrivateKeySeed != expectedSeed {
return nil , fmt . Errorf ( "invalid tx key seed: expected %s, got %s" , expectedSeed . String ( ) , block . Side . CoinbasePrivateKeySeed . String ( ) )
}
}
2022-11-03 11:32:07 +00:00
expectedHeight := parent . Side . Height + 1
if expectedHeight != block . Side . Height {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "wrong height, expected %d" , expectedHeight )
2022-11-03 11:32:07 +00:00
}
// Uncle hashes must be sorted in the ascending order to prevent cheating when the same hash is repeated multiple times
for i , uncleId := range block . Side . Uncles {
2022-11-05 09:10:04 +00:00
if i == 0 {
2022-11-03 11:32:07 +00:00
continue
}
2022-11-07 07:37:39 +00:00
if block . Side . Uncles [ i - 1 ] . Compare ( uncleId ) != - 1 {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "invalid uncle order" )
2022-11-03 11:32:07 +00:00
}
}
2022-11-05 09:10:04 +00:00
expectedCumulativeDifficulty := parent . Side . CumulativeDifficulty . Add ( block . Side . Difficulty )
2022-11-03 11:32:07 +00:00
//check uncles
minedBlocks := make ( [ ] types . Hash , 0 , len ( block . Side . Uncles ) * UncleBlockDepth * 2 + 1 )
{
tmp := parent
n := utils . Min ( UncleBlockDepth , block . Side . Height + 1 )
for i := uint64 ( 0 ) ; tmp != nil && i < n ; i ++ {
minedBlocks = append ( minedBlocks , tmp . SideTemplateId ( c . Consensus ( ) ) )
for _ , uncleId := range tmp . Side . Uncles {
minedBlocks = append ( minedBlocks , uncleId )
}
tmp = c . getParent ( tmp )
}
}
for _ , uncleId := range block . Side . Uncles {
// Empty hash is only used in the genesis block and only for its parent
// Uncles can't be empty
if uncleId == types . ZeroHash {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "empty uncle hash" )
2022-11-03 11:32:07 +00:00
}
// Can't mine the same uncle block twice
if slices . Index ( minedBlocks , uncleId ) != - 1 {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "uncle %s has already been mined" , uncleId . String ( ) )
2022-11-03 11:32:07 +00:00
}
if uncle := c . getPoolBlockByTemplateId ( uncleId ) ; uncle == nil {
2022-11-06 10:58:19 +00:00
return errors . New ( "uncle does not exist" ) , nil
} else if ! uncle . Verified . Load ( ) {
// If it's invalid then this block is also invalid
return errors . New ( "uncle is not verified" ) , nil
2022-11-03 11:32:07 +00:00
} else if uncle . Invalid . Load ( ) {
// If it's invalid then this block is also invalid
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "uncle is invalid" )
2022-11-03 11:32:07 +00:00
} else if uncle . Side . Height >= block . Side . Height || ( uncle . Side . Height + UncleBlockDepth < block . Side . Height ) {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "uncle at the wrong height (%d)" , uncle . Side . Height )
2022-11-03 11:32:07 +00:00
} else {
// Check that uncle and parent have the same ancestor (they must be on the same chain)
tmp := parent
for tmp . Side . Height > uncle . Side . Height {
tmp = c . getParent ( tmp )
if tmp == nil {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "uncle from different chain (check 1)" )
2022-11-03 11:32:07 +00:00
}
}
if tmp . Side . Height < uncle . Side . Height {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "uncle from different chain (check 2)" )
2022-11-03 11:32:07 +00:00
}
if sameChain := func ( ) bool {
tmp2 := uncle
for j := uint64 ( 0 ) ; j < UncleBlockDepth && tmp != nil && tmp2 != nil && ( tmp . Side . Height + UncleBlockDepth >= block . Side . Height ) ; j ++ {
if tmp . Side . Parent == tmp2 . Side . Parent {
return true
}
tmp = c . getParent ( tmp )
tmp2 = c . getParent ( tmp2 )
}
return false
} ( ) ; ! sameChain {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "uncle from different chain (check 3)" )
2022-11-03 11:32:07 +00:00
}
2022-11-05 09:10:04 +00:00
expectedCumulativeDifficulty = expectedCumulativeDifficulty . Add ( uncle . Side . Difficulty )
2022-11-03 11:32:07 +00:00
}
}
// We can verify this block now (all previous blocks in the window are verified and valid)
// It can still turn out to be invalid
2022-11-05 09:10:04 +00:00
if ! block . Side . CumulativeDifficulty . Equals ( expectedCumulativeDifficulty ) {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "wrong cumulative difficulty, got %s, expected %s" , block . Side . CumulativeDifficulty . StringNumeric ( ) , expectedCumulativeDifficulty . StringNumeric ( ) )
2022-11-03 11:32:07 +00:00
}
// Verify difficulty and miner rewards only for blocks in PPLNS window
if block . Depth . Load ( ) >= c . Consensus ( ) . ChainWindowSize {
2022-11-06 10:58:19 +00:00
log . Printf ( "[SideChain] block at height = %d, id = %s skipped diff/reward verification" , block . Side . Height , block . SideTemplateId ( c . Consensus ( ) ) )
2022-11-03 11:32:07 +00:00
return
}
if diff := c . getDifficulty ( parent ) ; diff == types . ZeroDifficulty {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "could not get difficulty" )
2022-11-03 11:32:07 +00:00
} else if diff != block . Side . Difficulty {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "wrong difficulty, got %s, expected %s" , block . Side . Difficulty . StringNumeric ( ) , diff . StringNumeric ( ) )
2022-11-03 11:32:07 +00:00
}
2023-03-07 17:57:06 +00:00
if c . sharesCache , _ = c . getShares ( block , c . sharesCache ) ; len ( c . sharesCache ) == 0 {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "could not get outputs" )
2022-11-06 12:17:26 +00:00
} else if len ( c . sharesCache ) != len ( block . Main . Coinbase . Outputs ) {
return nil , fmt . Errorf ( "invalid number of outputs, got %d, expected %d" , len ( block . Main . Coinbase . Outputs ) , len ( c . sharesCache ) )
2022-11-03 11:32:07 +00:00
} else if totalReward := func ( ) ( result uint64 ) {
for _ , o := range block . Main . Coinbase . Outputs {
result += o . Reward
}
return
} ( ) ; totalReward != block . Main . Coinbase . TotalReward {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "invalid total reward, got %d, expected %d" , block . Main . Coinbase . TotalReward , totalReward )
2022-11-06 12:17:26 +00:00
} else if rewards := c . SplitReward ( totalReward , c . sharesCache ) ; len ( rewards ) != len ( block . Main . Coinbase . Outputs ) {
2022-11-06 10:58:19 +00:00
return nil , fmt . Errorf ( "invalid number of outputs, got %d, expected %d" , len ( block . Main . Coinbase . Outputs ) , len ( rewards ) )
2022-11-03 11:32:07 +00:00
} else {
2022-11-07 22:59:52 +00:00
//prevent multiple allocations
txPrivateKeySlice := block . Side . CoinbasePrivateKey . AsSlice ( )
txPrivateKeyScalar := block . Side . CoinbasePrivateKey . AsScalar ( )
2022-11-06 20:11:03 +00:00
results := utils . SplitWork ( - 2 , uint64 ( len ( rewards ) ) , func ( workIndex uint64 , workerIndex int ) error {
out := block . Main . Coinbase . Outputs [ workIndex ]
if rewards [ workIndex ] != out . Reward {
return fmt . Errorf ( "has invalid reward at index %d, got %d, expected %d" , workIndex , out . Reward , rewards [ workIndex ] )
}
2022-11-06 12:17:26 +00:00
2022-12-09 12:13:00 +00:00
if ephPublicKey , viewTag := c . derivationCache . GetEphemeralPublicKey ( & c . sharesCache [ workIndex ] . Address , txPrivateKeySlice , txPrivateKeyScalar , workIndex ) ; ephPublicKey != out . EphemeralPublicKey {
2022-11-06 20:11:03 +00:00
return fmt . Errorf ( "has incorrect eph_public_key at index %d, got %s, expected %s" , workIndex , out . EphemeralPublicKey . String ( ) , ephPublicKey . String ( ) )
} else if out . Type == transaction . TxOutToTaggedKey && viewTag != out . ViewTag {
return fmt . Errorf ( "has incorrect view tag at index %d, got %d, expected %d" , workIndex , out . ViewTag , viewTag )
}
return nil
} )
2022-11-06 12:17:26 +00:00
for i := range results {
if results [ i ] != nil {
return nil , results [ i ]
2022-11-03 11:32:07 +00:00
}
}
}
// All checks passed
2022-11-06 10:58:19 +00:00
return nil , nil
2022-11-03 11:32:07 +00:00
} else {
2022-11-06 10:58:19 +00:00
return errors . New ( "parent does not exist" ) , nil
2022-11-03 11:32:07 +00:00
}
}
func ( c * SideChain ) updateDepths ( block * PoolBlock ) {
for i := uint64 ( 1 ) ; i <= UncleBlockDepth ; i ++ {
for _ , child := range c . blocksByHeight [ block . Side . Height + i ] {
if child . Side . Parent . Equals ( block . SideTemplateId ( c . Consensus ( ) ) ) {
if i != 1 {
//TODO: error
} else {
block . Depth . Store ( utils . Max ( block . Depth . Load ( ) , child . Depth . Load ( ) + 1 ) )
}
}
if ix := slices . Index ( child . Side . Uncles , block . SideTemplateId ( c . Consensus ( ) ) ) ; ix != 1 {
block . Depth . Store ( utils . Max ( block . Depth . Load ( ) , child . Depth . Load ( ) + 1 ) )
}
}
}
blocksToUpdate := make ( [ ] * PoolBlock , 1 , 8 )
blocksToUpdate [ 0 ] = block
for len ( blocksToUpdate ) != 0 {
block = blocksToUpdate [ len ( blocksToUpdate ) - 1 ]
blocksToUpdate = blocksToUpdate [ : len ( blocksToUpdate ) - 1 ]
blockDepth := block . Depth . Load ( )
// Verify this block and possibly other blocks on top of it when we're sure it will get verified
if ! block . Verified . Load ( ) && ( blockDepth >= c . Consensus ( ) . ChainWindowSize * 2 || block . Side . Height == 0 ) {
c . verifyLoop ( block )
}
if parent := c . getParent ( block ) ; parent != nil {
if parent . Side . Height + 1 != block . Side . Height {
//TODO error
}
if parent . Depth . Load ( ) < blockDepth + 1 {
parent . Depth . Store ( blockDepth + 1 )
blocksToUpdate = append ( blocksToUpdate , parent )
}
}
for _ , uncleId := range block . Side . Uncles {
if uncle := c . getPoolBlockByTemplateId ( uncleId ) ; uncle != nil {
if uncle . Side . Height >= block . Side . Height || ( uncle . Side . Height + UncleBlockDepth < block . Side . Height ) {
//TODO: error
}
d := block . Side . Height - uncle . Side . Height
if uncle . Depth . Load ( ) < blockDepth + d {
uncle . Depth . Store ( blockDepth + d )
blocksToUpdate = append ( blocksToUpdate , uncle )
}
}
}
}
}
func ( c * SideChain ) updateChainTip ( block * PoolBlock ) {
if ! block . Verified . Load ( ) || block . Invalid . Load ( ) {
//todo err
return
}
if block . Depth . Load ( ) >= c . Consensus ( ) . ChainWindowSize {
//TODO err
return
}
tip := c . GetChainTip ( )
2023-03-07 17:57:06 +00:00
if block == tip {
log . Printf ( "[SideChain] Trying to update chain tip to the same block again. Ignoring it." )
return
}
2022-11-06 12:17:26 +00:00
if isLongerChain , isAlternative := c . isLongerChain ( tip , block ) ; isLongerChain {
2022-11-03 11:32:07 +00:00
if diff := c . getDifficulty ( block ) ; diff != types . ZeroDifficulty {
c . chainTip . Store ( block )
c . currentDifficulty . Store ( & diff )
//TODO log
block . WantBroadcast . Store ( true )
if isAlternative {
2022-12-09 12:13:00 +00:00
c . derivationCache . Clear ( )
log . Printf ( "[SideChain] SYNCHRONIZED to tip %s" , block . SideTemplateId ( c . Consensus ( ) ) )
2022-11-03 11:32:07 +00:00
}
c . pruneOldBlocks ( )
}
} else if block . Side . Height > tip . Side . Height {
2022-12-09 12:13:00 +00:00
log . Printf ( "[SideChain] block %s, height = %d, is not a longer chain than %s, height = %d" , block . SideTemplateId ( c . Consensus ( ) ) , block . Side . Height , tip . SideTemplateId ( c . Consensus ( ) ) , tip . Side . Height )
2022-11-03 11:32:07 +00:00
} else if block . Side . Height + UncleBlockDepth > tip . Side . Height {
2022-12-09 12:13:00 +00:00
log . Printf ( "[SideChain] possible uncle block: id = %s, height = %d" , block . SideTemplateId ( c . Consensus ( ) ) , block . Side . Height )
2022-11-03 11:32:07 +00:00
}
if block . WantBroadcast . Load ( ) && ! block . Broadcasted . Swap ( true ) {
c . server . Broadcast ( block )
}
}
func ( c * SideChain ) pruneOldBlocks ( ) {
2022-12-09 12:13:00 +00:00
// Leave 2 minutes worth of spare blocks in addition to 2xPPLNS window for lagging nodes which need to sync
pruneDistance := c . Consensus ( ) . ChainWindowSize * 2 + monero . BlockTime / c . Consensus ( ) . TargetBlockTime
curTime := uint64 ( time . Now ( ) . Unix ( ) )
// Remove old blocks from alternative unconnected chains after long enough time
pruneDelay := c . Consensus ( ) . ChainWindowSize * 4 * c . Consensus ( ) . TargetBlockTime
tip := c . GetChainTip ( )
if tip == nil || tip . Side . Height < pruneDistance {
return
}
h := tip . Side . Height - pruneDistance
numBlocksPruned := 0
for height , v := range c . blocksByHeight {
if height > h {
continue
}
// loop backwards for proper deletions
for i := len ( v ) - 1 ; i >= 0 ; i -- {
block := v [ i ]
if block . Depth . Load ( ) >= pruneDistance || ( curTime >= ( block . LocalTimestamp + pruneDelay ) ) {
if _ , ok := c . blocksByTemplateId [ block . SideTemplateId ( c . Consensus ( ) ) ] ; ok {
delete ( c . blocksByTemplateId , block . SideTemplateId ( c . Consensus ( ) ) )
numBlocksPruned ++
} else {
log . Printf ( "[SideChain] blocksByHeight and blocksByTemplateId are inconsistent at height = %d, id = %s" , height , block . SideTemplateId ( c . Consensus ( ) ) )
}
v = slices . Delete ( v , i , i + 1 )
}
}
if len ( v ) == 0 {
delete ( c . blocksByHeight , height )
} else {
c . blocksByHeight [ height ] = v
}
}
if numBlocksPruned > 0 {
log . Printf ( "[SideChain] pruned %d old blocks at heights <= %d" , numBlocksPruned , h )
if ! c . precalcFinished . Swap ( true ) {
c . derivationCache . Clear ( )
}
}
2022-11-03 11:32:07 +00:00
}
2022-12-12 15:58:45 +00:00
func ( c * SideChain ) GetMissingBlocks ( ) [ ] types . Hash {
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
missingBlocks := make ( [ ] types . Hash , 0 )
for _ , b := range c . blocksByTemplateId {
if b . Verified . Load ( ) {
continue
}
if ! b . Side . Parent . Equals ( types . ZeroHash ) && c . getPoolBlockByTemplateId ( b . Side . Parent ) == nil {
missingBlocks = append ( missingBlocks , b . Side . Parent )
}
missingUncles := 0
for _ , uncleId := range b . Side . Uncles {
if ! uncleId . Equals ( types . ZeroHash ) && c . getPoolBlockByTemplateId ( uncleId ) == nil {
missingBlocks = append ( missingBlocks , uncleId )
missingUncles ++
// Get no more than 2 first missing uncles at a time from each block
// Blocks with more than 2 uncles are very rare and they will be processed in several steps
if missingUncles >= 2 {
break
}
}
}
}
return missingBlocks
}
2022-11-03 11:32:07 +00:00
func ( c * SideChain ) GetTransactionOutputType ( majorVersion uint8 ) uint8 {
// Both tx types are allowed by Monero consensus during v15 because it needs to process pre-fork mempool transactions,
// but P2Pool can switch to using only TXOUT_TO_TAGGED_KEY for miner payouts starting from v15
expectedTxType := uint8 ( transaction . TxOutToKey )
if majorVersion >= monero . HardForkViewTagsVersion {
expectedTxType = transaction . TxOutToTaggedKey
}
return expectedTxType
}
2022-11-05 10:28:10 +00:00
func ( c * SideChain ) getOutputs ( block * PoolBlock ) transaction . Outputs {
2022-11-06 16:01:36 +00:00
//cannot use SideTemplateId() as it might not be proper to calculate yet. fetch from coinbase only here
2022-11-06 20:11:03 +00:00
if b := c . getPoolBlockByTemplateId ( types . HashFromBytes ( block . CoinbaseExtra ( SideTemplateId ) ) ) ; b != nil {
2022-11-03 11:32:07 +00:00
return b . Main . Coinbase . Outputs
}
2022-11-06 20:11:03 +00:00
return c . calculateOutputs ( block )
}
func ( c * SideChain ) calculateOutputs ( block * PoolBlock ) transaction . Outputs {
2022-11-06 16:01:36 +00:00
//TODO: buffer
2023-03-07 17:57:06 +00:00
tmpShares , _ := c . getShares ( block , make ( Shares , 0 , c . Consensus ( ) . ChainWindowSize * 2 ) )
2022-11-03 11:32:07 +00:00
tmpRewards := c . SplitReward ( block . Main . Coinbase . TotalReward , tmpShares )
if tmpShares == nil || tmpRewards == nil || len ( tmpRewards ) != len ( tmpShares ) {
return nil
}
2022-11-06 20:11:03 +00:00
n := uint64 ( len ( tmpShares ) )
2022-11-03 11:32:07 +00:00
2022-11-05 10:28:10 +00:00
outputs := make ( transaction . Outputs , n )
2022-11-03 11:32:07 +00:00
txType := c . GetTransactionOutputType ( block . Main . MajorVersion )
2022-11-07 22:59:52 +00:00
txPrivateKeySlice := block . Side . CoinbasePrivateKey . AsSlice ( )
txPrivateKeyScalar := block . Side . CoinbasePrivateKey . AsScalar ( )
2022-11-06 20:11:03 +00:00
_ = utils . SplitWork ( - 2 , n , func ( workIndex uint64 , workerIndex int ) error {
2022-11-07 22:59:52 +00:00
output := transaction . Output {
2022-11-06 20:11:03 +00:00
Index : workIndex ,
Type : txType ,
}
output . Reward = tmpRewards [ output . Index ]
2022-12-09 12:13:00 +00:00
output . EphemeralPublicKey , output . ViewTag = c . derivationCache . GetEphemeralPublicKey ( & tmpShares [ output . Index ] . Address , txPrivateKeySlice , txPrivateKeyScalar , output . Index )
2022-11-03 11:32:07 +00:00
2022-11-06 20:11:03 +00:00
outputs [ output . Index ] = output
2022-11-03 11:32:07 +00:00
2022-11-06 20:11:03 +00:00
return nil
} )
2022-11-03 11:32:07 +00:00
return outputs
}
func ( c * SideChain ) SplitReward ( reward uint64 , shares Shares ) ( rewards [ ] uint64 ) {
2022-12-11 13:00:00 +00:00
var totalWeight types . Difficulty
2022-11-03 11:32:07 +00:00
for i := range shares {
2022-12-11 13:00:00 +00:00
totalWeight = totalWeight . Add ( shares [ i ] . Weight )
2022-11-03 11:32:07 +00:00
}
2022-12-11 13:00:00 +00:00
if totalWeight . Equals64 ( 0 ) {
2022-11-03 11:32:07 +00:00
//TODO: err
return nil
}
rewards = make ( [ ] uint64 , len ( shares ) )
2022-12-11 13:00:00 +00:00
var w types . Difficulty
2022-11-03 11:32:07 +00:00
var rewardGiven uint64
for i := range shares {
2022-12-11 13:00:00 +00:00
w = w . Add ( shares [ i ] . Weight )
nextValue := w . Mul64 ( reward ) . Div ( totalWeight )
rewards [ i ] = nextValue . Lo - rewardGiven
rewardGiven = nextValue . Lo
2022-11-03 11:32:07 +00:00
}
// 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-07 17:57:06 +00:00
func ( c * SideChain ) getShares ( tip * PoolBlock , preAllocatedShares Shares ) ( shares Shares , bottomHeight uint64 ) {
2022-11-03 11:32:07 +00:00
var blockDepth uint64
cur := tip
2022-11-06 12:17:26 +00:00
2023-03-07 17:57:06 +00:00
var mainchainDiff types . Difficulty
if tip . Side . Parent != types . ZeroHash {
mainchainDiff = c . server . GetDifficultyByHeight ( tip . Main . Coinbase . GenHeight )
if mainchainDiff == types . ZeroDifficulty {
log . Printf ( "[SideChain] get_shares: couldn't get mainchain difficulty for height = %d" , tip . Main . Coinbase . GenHeight )
return nil , 0
}
}
// 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
sharesSet := make ( map [ address . PackedAddress ] * Share , c . Consensus ( ) . ChainWindowSize * 2 )
insertSet := func ( weight types . Difficulty , a * address . PackedAddress ) {
if _ , ok := sharesSet [ * a ] ; ok {
sharesSet [ * a ] . Weight = sharesSet [ * a ] . Weight . Add ( weight )
} else {
sharesSet [ * a ] = & Share {
Weight : weight ,
Address : * a ,
}
}
}
2022-11-07 22:59:52 +00:00
index := 0
l := len ( preAllocatedShares )
2023-03-07 17:57:06 +00:00
insertPreAllocated := func ( share * Share ) {
2022-11-07 22:59:52 +00:00
if index < l {
2023-03-07 17:57:06 +00:00
preAllocatedShares [ index ] . Weight , preAllocatedShares [ index ] . Address = share . Weight , share . Address
2022-11-07 22:59:52 +00:00
} else {
2023-03-07 17:57:06 +00:00
preAllocatedShares = append ( preAllocatedShares , share )
2022-11-07 22:59:52 +00:00
}
index ++
}
2022-11-03 11:32:07 +00:00
for {
2022-12-11 13:00:00 +00:00
curWeight := cur . Side . Difficulty
2022-11-03 11:32:07 +00:00
for _ , uncleId := range cur . Side . Uncles {
2022-11-06 10:58:19 +00:00
if uncle := c . getPoolBlockByTemplateId ( uncleId ) ; uncle == nil {
2022-11-03 11:32:07 +00:00
//cannot find uncles
2023-03-07 17:57:06 +00:00
return nil , 0
2022-11-03 11:32:07 +00:00
} else {
// Skip uncles which are already out of PPLNS window
if ( tip . Side . Height - uncle . Side . Height ) >= c . Consensus ( ) . ChainWindowSize {
continue
}
// Take some % of uncle's weight into this share
2023-03-07 17:57:06 +00:00
unclePenalty := uncle . Side . Difficulty . Mul64 ( c . Consensus ( ) . UnclePenalty ) . Div64 ( 100 )
uncleWeight := uncle . Side . Difficulty . Sub ( unclePenalty )
newPplnsWeight := pplnsWeight . Add ( uncleWeight )
// Skip uncles that push PPLNS weight above the limit
if newPplnsWeight . Cmp ( maxPplnsWeight ) > 0 {
continue
}
2022-12-11 13:00:00 +00:00
curWeight = curWeight . Add ( unclePenalty )
2022-11-03 11:32:07 +00:00
2023-03-07 17:57:06 +00:00
insertSet ( uncleWeight , uncle . GetAddress ( ) )
pplnsWeight = newPplnsWeight
2022-11-03 11:32:07 +00:00
}
}
2023-03-07 17:57:06 +00:00
// Always add non-uncle shares even if PPLNS weight goes above the limit
insertSet ( curWeight , cur . GetAddress ( ) )
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
}
2022-11-03 11:32:07 +00:00
blockDepth ++
if blockDepth >= c . Consensus ( ) . ChainWindowSize {
break
}
// Reached the genesis block so we're done
if cur . Side . Height == 0 {
break
}
2022-11-06 10:58:19 +00:00
cur = c . getParent ( cur )
2022-11-03 11:32:07 +00:00
if cur == nil {
2023-03-07 17:57:06 +00:00
return nil , 0
2022-11-03 11:32:07 +00:00
}
}
2023-03-07 17:57:06 +00:00
bottomHeight = cur . Side . Height
for _ , share := range sharesSet {
insertPreAllocated ( share )
}
shares = preAllocatedShares [ : index ]
2022-11-07 22:59:52 +00:00
2022-11-03 11:32:07 +00:00
// Combine shares with the same wallet addresses
2022-11-06 12:17:26 +00:00
slices . SortFunc ( shares , func ( a * Share , b * Share ) bool {
2022-11-05 05:29:12 +00:00
return a . Address . Compare ( & b . Address ) < 0
2022-11-03 11:32:07 +00:00
} )
2023-03-07 17:57:06 +00:00
n := len ( shares )
//Shuffle shares
if sidechainVersion > ShareVersion_V1 && n > 1 {
h := crypto . PooledKeccak256 ( tip . Side . CoinbasePrivateKeySeed [ : ] )
seed := binary . LittleEndian . Uint64 ( h [ : ] )
for i := 0 ; i < ( n - 1 ) ; i ++ {
seed = utils . XorShift64Star ( seed )
k := int ( uint128 . From64 ( seed ) . Mul64 ( uint64 ( n - 1 ) ) . Hi )
//swap
shares [ i ] , shares [ i + k ] = shares [ i + k ] , shares [ i ]
2022-11-03 11:32:07 +00:00
}
}
2023-03-07 17:57:06 +00:00
return shares , bottomHeight
2022-11-03 11:32:07 +00:00
}
type DifficultyData struct {
CumulativeDifficulty types . Difficulty
Timestamp uint64
}
func ( c * SideChain ) GetDifficulty ( tip * PoolBlock ) types . Difficulty {
2022-11-06 12:17:26 +00:00
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
2022-11-03 11:32:07 +00:00
return c . getDifficulty ( tip )
}
func ( c * SideChain ) getDifficulty ( tip * PoolBlock ) types . Difficulty {
difficultyData := make ( [ ] DifficultyData , 0 , c . Consensus ( ) . ChainWindowSize * 2 )
cur := tip
var blockDepth uint64
var oldestTimestamp uint64 = math . MaxUint64
for {
oldestTimestamp = utils . Min ( oldestTimestamp , cur . Main . Timestamp )
difficultyData = append ( difficultyData , DifficultyData { CumulativeDifficulty : cur . Side . CumulativeDifficulty , Timestamp : cur . Main . Timestamp } )
for _ , uncleId := range cur . Side . Uncles {
if uncle := c . getPoolBlockByTemplateId ( uncleId ) ; uncle == nil {
//cannot find uncles
return types . ZeroDifficulty
} else {
// Skip uncles which are already out of PPLNS window
if ( tip . Side . Height - uncle . Side . Height ) >= c . Consensus ( ) . ChainWindowSize {
continue
}
oldestTimestamp = utils . Min ( oldestTimestamp , uncle . Main . Timestamp )
difficultyData = append ( difficultyData , DifficultyData { CumulativeDifficulty : uncle . Side . CumulativeDifficulty , Timestamp : uncle . Main . Timestamp } )
}
}
blockDepth ++
if blockDepth >= c . Consensus ( ) . ChainWindowSize {
break
}
// Reached the genesis block so we're done
if cur . Side . Height == 0 {
break
}
2022-11-06 10:58:19 +00:00
cur = c . getParent ( cur )
2022-11-03 11:32:07 +00:00
if cur == nil {
return types . ZeroDifficulty
}
}
// Discard 10% oldest and 10% newest (by timestamp) blocks
tmpTimestamps := make ( [ ] uint32 , 0 , len ( difficultyData ) )
for i := range difficultyData {
tmpTimestamps = append ( tmpTimestamps , uint32 ( difficultyData [ i ] . Timestamp - oldestTimestamp ) )
}
cutSize := ( len ( difficultyData ) + 9 ) / 10
index1 := cutSize - 1
index2 := len ( difficultyData ) - cutSize
//TODO: replace this with introspective selection, use order for now
slices . Sort ( tmpTimestamps )
timestamp1 := oldestTimestamp + uint64 ( tmpTimestamps [ index1 ] )
timestamp2 := oldestTimestamp + uint64 ( tmpTimestamps [ index2 ] )
deltaT := uint64 ( 1 )
if timestamp2 > timestamp1 {
deltaT = timestamp2 - timestamp1
}
var diff1 = types . Difficulty { Hi : math . MaxUint64 , Lo : math . MaxUint64 }
var diff2 types . Difficulty
for i := range difficultyData {
d := & difficultyData [ i ]
if timestamp1 <= d . Timestamp && d . Timestamp <= timestamp2 {
if d . CumulativeDifficulty . Cmp ( diff1 ) < 0 {
diff1 = d . CumulativeDifficulty
}
if diff2 . Cmp ( d . CumulativeDifficulty ) < 0 {
diff2 = d . CumulativeDifficulty
}
}
}
deltaDiff := diff2 . Sub ( diff1 )
2022-12-11 13:00:00 +00:00
curDifficulty := deltaDiff . Mul64 ( c . Consensus ( ) . TargetBlockTime ) . Div64 ( deltaT )
2022-11-03 11:32:07 +00:00
if curDifficulty . Cmp64 ( c . Consensus ( ) . MinimumDifficulty ) < 0 {
curDifficulty = types . DifficultyFrom64 ( c . Consensus ( ) . MinimumDifficulty )
}
return curDifficulty
}
func ( c * SideChain ) GetParent ( block * PoolBlock ) * PoolBlock {
2022-11-06 12:17:26 +00:00
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
2022-11-03 11:32:07 +00:00
return c . getParent ( block )
}
func ( c * SideChain ) getParent ( block * PoolBlock ) * PoolBlock {
return c . getPoolBlockByTemplateId ( block . Side . Parent )
}
func ( c * SideChain ) GetPoolBlockByTemplateId ( id types . Hash ) * PoolBlock {
2022-11-06 12:17:26 +00:00
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
2022-11-03 11:32:07 +00:00
return c . getPoolBlockByTemplateId ( id )
}
2022-12-11 12:31:43 +00:00
func ( c * SideChain ) getPoolBlockByTemplateId ( id types . Hash ) * PoolBlock {
return c . blocksByTemplateId [ id ]
}
func ( c * SideChain ) GetPoolBlocksByHeight ( height uint64 ) [ ] * PoolBlock {
2022-11-06 12:17:26 +00:00
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
2022-12-11 12:31:43 +00:00
return slices . Clone ( c . getPoolBlocksByHeight ( height ) )
2022-11-06 10:58:19 +00:00
}
2022-12-11 12:31:43 +00:00
func ( c * SideChain ) getPoolBlocksByHeight ( height uint64 ) [ ] * PoolBlock {
return c . blocksByHeight [ height ]
}
func ( c * SideChain ) GetPoolBlockCount ( ) int {
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
return len ( c . blocksByTemplateId )
2022-11-03 11:32:07 +00:00
}
2023-03-05 14:06:49 +00:00
func ( c * SideChain ) WatchMainChainBlock ( mainData * ChainMain , possibleId types . Hash ) {
c . sidechainLock . Lock ( )
defer c . sidechainLock . Unlock ( )
c . watchBlock = mainData
c . watchBlockSidechainId = possibleId
}
2022-11-03 11:32:07 +00:00
func ( c * SideChain ) GetChainTip ( ) * PoolBlock {
return c . chainTip . Load ( )
}
func ( c * SideChain ) IsLongerChain ( block , candidate * PoolBlock ) ( isLonger , isAlternative bool ) {
2022-11-06 12:17:26 +00:00
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
return c . isLongerChain ( block , candidate )
}
func ( c * SideChain ) isLongerChain ( block , candidate * PoolBlock ) ( isLonger , isAlternative bool ) {
if candidate == nil || ! candidate . Verified . Load ( ) || candidate . Invalid . Load ( ) {
2022-11-03 11:32:07 +00:00
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 {
2022-11-06 12:17:26 +00:00
blockAncestor = c . getParent ( blockAncestor )
2022-11-03 11:32:07 +00:00
//TODO: err on blockAncestor nil
}
if blockAncestor != nil {
candidateAncestor := candidate
for candidateAncestor != nil && candidateAncestor . Side . Height > blockAncestor . Side . Height {
2022-11-06 12:17:26 +00:00
candidateAncestor = c . getParent ( candidateAncestor )
2022-11-03 11:32:07 +00:00
//TODO: err on candidateAncestor nil
}
for blockAncestor != nil && candidateAncestor != nil {
if blockAncestor . Side . Parent . Equals ( candidateAncestor . Side . Parent ) {
return block . Side . CumulativeDifficulty . Cmp ( candidate . Side . CumulativeDifficulty ) < 0 , false
}
2022-11-06 12:17:26 +00:00
blockAncestor = c . getParent ( blockAncestor )
candidateAncestor = c . getParent ( candidateAncestor )
2022-11-03 11:32:07 +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
var mainchainPrevId types . Hash
for i := uint64 ( 0 ) ; i < c . Consensus ( ) . ChainWindowSize && ( oldChain != nil || newChain != nil ) ; i ++ {
if oldChain != nil {
blockTotalDiff = blockTotalDiff . Add ( oldChain . Side . Difficulty )
2022-11-06 12:17:26 +00:00
oldChain = c . getParent ( oldChain )
2022-11-03 11:32:07 +00:00
}
if newChain != nil {
if candidateMainchainMinHeight != 0 {
candidateMainchainMinHeight = utils . Min ( candidateMainchainMinHeight , newChain . Main . Coinbase . GenHeight )
} else {
candidateMainchainMinHeight = newChain . Main . Coinbase . GenHeight
}
candidateTotalDiff = candidateTotalDiff . Add ( newChain . Side . Difficulty )
if ! newChain . Main . PreviousId . Equals ( mainchainPrevId ) {
2023-03-05 14:06:49 +00:00
if data := c . server . GetMinimalBlockHeaderByHash ( newChain . Main . PreviousId ) ; data != nil {
2022-11-03 11:32:07 +00:00
mainchainPrevId = data . Id
candidateMainchainHeight = utils . Max ( candidateMainchainHeight , data . Height )
}
}
2022-11-06 12:17:26 +00:00
newChain = c . getParent ( newChain )
2022-11-03 11:32:07 +00:00
}
}
if blockTotalDiff . Cmp ( candidateTotalDiff ) >= 0 {
return false , true
}
// Final check: candidate chain must be built on top of recent mainchain blocks
2023-03-05 14:06:49 +00:00
if headerTip := c . server . GetChainMainTip ( ) ; headerTip != nil {
if candidateMainchainHeight + 10 < headerTip . Height {
2022-11-03 11:32:07 +00:00
//TODO: warn received a longer alternative chain but it's stale: height
return false , true
}
limit := c . Consensus ( ) . ChainWindowSize * 4 * c . Consensus ( ) . TargetBlockTime / monero . BlockTime
2023-03-05 14:06:49 +00:00
if candidateMainchainMinHeight + limit < headerTip . Height {
2022-11-03 11:32:07 +00:00
//TODO: warn received a longer alternative chain but it's stale: min height
return false , true
}
return true , true
} else {
return false , true
}
}