2022-11-03 11:32:07 +00:00
package sidechain
import (
"bytes"
"errors"
"fmt"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
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"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
2022-11-03 11:32:07 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
2023-03-09 15:18:42 +00:00
p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types"
2022-11-03 11:32:07 +00:00
"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"
2022-11-03 11:32:07 +00:00
"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
2023-03-09 15:18:42 +00:00
GetMinerDataTip ( ) * p2pooltypes . MinerData
2023-03-10 15:45:22 +00:00
Store ( block * PoolBlock )
ClearCachedBlocks ( )
2023-03-05 14:06:49 +00:00
}
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 {
2023-03-27 13:27:29 +00:00
s := & 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
}
2023-03-27 13:27:29 +00:00
minDiff := types . DifficultyFrom64 ( server . Consensus ( ) . MinimumDifficulty )
s . currentDifficulty . Store ( & minDiff )
return s
2022-11-03 11:32:07 +00:00
}
func ( c * SideChain ) Consensus ( ) * Consensus {
return c . server . Consensus ( )
}
2023-03-20 07:36:39 +00:00
func ( c * SideChain ) DerivationCache ( ) * DerivationCache {
return c . derivationCache
}
2023-03-27 13:27:29 +00:00
func ( c * SideChain ) Difficulty ( ) types . Difficulty {
return * c . currentDifficulty . Load ( )
}
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-03 11:32:07 +00:00
if len ( block . Main . Coinbase . Outputs ) == 0 {
2023-03-21 10:01:51 +00:00
//cannot use SideTemplateId() as it might not be proper to calculate yet. fetch from coinbase only here
if b := c . GetPoolBlockByTemplateId ( types . HashFromBytes ( block . CoinbaseExtra ( SideTemplateId ) ) ) ; b != nil {
block . Main . Coinbase . Outputs = b . Main . Coinbase . Outputs
2022-12-03 22:43:18 +00:00
}
2022-12-12 13:20:08 +00:00
}
2023-03-21 10:01:51 +00:00
preAllocatedShares := make ( Shares , 0 , c . Consensus ( ) . ChainWindowSize * 2 )
return block . PreProcessBlock ( c . Consensus ( ) , c . derivationCache , preAllocatedShares , c . server . GetDifficultyByHeight , c . GetPoolBlockByTemplateId )
2022-12-12 13:20:08 +00:00
}
func ( c * SideChain ) fillPoolBlockTransactionParentIndices ( block * PoolBlock ) {
2023-03-12 14:00:38 +00:00
block . FillTransactionParentIndices ( c . getParent ( block ) )
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 )
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
}
}
2023-03-17 09:01:52 +00:00
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
2023-03-17 09:01:52 +00:00
expectedTxType := block . GetTransactionOutputType ( )
2022-11-03 11:32:07 +00:00
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?
2023-03-27 13:27:29 +00:00
expectedDifficulty := c . Difficulty ( )
tooLowDiff := block . Side . Difficulty . Cmp ( expectedDifficulty ) < 0
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
}
}
}
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
2023-03-05 14:06:49 +00:00
if data := c . server . GetChainMainByHash ( block . Main . PreviousId ) ; data != nil {
2023-03-10 23:07:25 +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 {
2023-03-18 23:25:52 +00:00
return nil , fmt . Errorf ( "not enough PoW for id %s, height = %d, mainchain height %d" , templateId . String ( ) , 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 )
2023-03-11 18:08:04 +00:00
if block . ShareVersion ( ) > ShareVersion_V1 {
log . Printf ( "[SideChain] verified block at height = %d, depth = %d, id = %s, mainchain height = %d, mined by %s via %s %s" , block . Side . Height , block . Depth . Load ( ) , block . SideTemplateId ( c . Consensus ( ) ) , block . Main . Coinbase . GenHeight , block . GetAddress ( ) . ToBase58 ( ) , block . Side . ExtraBuffer . SoftwareId , block . Side . ExtraBuffer . SoftwareVersion )
} else {
if signalingVersion := block . ShareVersionSignaling ( ) ; signalingVersion > ShareVersion_None {
log . Printf ( "[SideChain] verified block at height = %d, depth = %d, id = %s, mainchain height = %d, mined by %s, signaling v%d" , block . Side . Height , block . Depth . Load ( ) , block . SideTemplateId ( c . Consensus ( ) ) , block . Main . Coinbase . GenHeight , block . GetAddress ( ) . ToBase58 ( ) , signalingVersion )
} else {
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
}
2023-03-27 12:09:17 +00:00
var diff types . Difficulty
if parent == c . GetChainTip ( ) {
// built on top of the current chain tip, using current difficulty for verification
2023-03-27 13:27:29 +00:00
diff = c . Difficulty ( )
2023-03-27 12:09:17 +00:00
} else if diff = c . getDifficulty ( parent ) ; diff == types . ZeroDifficulty {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "could not get difficulty" )
2023-03-27 12:09:17 +00:00
}
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 )
2023-03-17 09:01:52 +00:00
} else if rewards := 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
}
2023-03-21 10:01:51 +00:00
func ( c * SideChain ) calculateOutputs ( block * PoolBlock ) ( outputs transaction . Outputs , bottomHeight uint64 ) {
2022-11-06 16:01:36 +00:00
//TODO: buffer
2023-03-17 09:01:52 +00:00
preAllocatedShares := make ( Shares , 0 , c . Consensus ( ) . ChainWindowSize * 2 )
return CalculateOutputs ( block , c . Consensus ( ) , c . server . GetDifficultyByHeight , c . getPoolBlockByTemplateId , c . derivationCache , preAllocatedShares )
2022-11-03 11:32:07 +00:00
}
2023-03-07 17:57:06 +00:00
func ( c * SideChain ) getShares ( tip * PoolBlock , preAllocatedShares Shares ) ( shares Shares , bottomHeight uint64 ) {
2023-03-17 09:01:52 +00:00
return GetShares ( tip , c . Consensus ( ) , c . server . GetDifficultyByHeight , c . getPoolBlockByTemplateId , preAllocatedShares )
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 {
2023-03-17 09:01:52 +00:00
return GetDifficulty ( tip , c . Consensus ( ) , c . getPoolBlockByTemplateId )
2022-11-03 11:32:07 +00:00
}
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 ]
}
2023-03-18 20:19:54 +00:00
func ( c * SideChain ) GetPoolBlocksFromTip ( id types . Hash ) ( chain , uncles UniquePoolBlockSlice ) {
2023-03-18 10:28:54 +00:00
chain = make ( [ ] * PoolBlock , 0 , c . Consensus ( ) . ChainWindowSize * 2 + monero . BlockTime / c . Consensus ( ) . TargetBlockTime )
uncles = make ( [ ] * PoolBlock , 0 , len ( chain ) / 20 )
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
for cur := c . getPoolBlockByTemplateId ( id ) ; cur != nil ; cur = c . getPoolBlockByTemplateId ( cur . Side . Parent ) {
for i , uncleId := range cur . Side . Uncles {
if u := c . getPoolBlockByTemplateId ( uncleId ) ; u == nil {
//return few uncles than necessary
return chain , uncles [ : len ( uncles ) - i ]
} else {
uncles = append ( uncles , u )
}
}
chain = append ( chain , cur )
}
return chain , uncles
}
2023-03-18 20:19:54 +00:00
func ( c * SideChain ) GetPoolBlocksFromTipWithDepth ( id types . Hash , depth uint64 ) ( chain , uncles UniquePoolBlockSlice ) {
chain = make ( [ ] * PoolBlock , 0 , utils . Min ( depth , c . Consensus ( ) . ChainWindowSize * 2 + monero . BlockTime / c . Consensus ( ) . TargetBlockTime ) )
uncles = make ( [ ] * PoolBlock , 0 , len ( chain ) / 20 )
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
for cur := c . getPoolBlockByTemplateId ( id ) ; cur != nil && len ( chain ) < int ( depth ) ; cur = c . getPoolBlockByTemplateId ( cur . Side . Parent ) {
for i , uncleId := range cur . Side . Uncles {
if u := c . getPoolBlockByTemplateId ( uncleId ) ; u == nil {
//return few uncles than necessary
return chain , uncles [ : len ( uncles ) - i ]
} else {
uncles = append ( uncles , u )
}
}
chain = append ( chain , cur )
}
return chain , uncles
}
2022-12-11 12:31:43 +00:00
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 ) {
2023-03-27 11:56:27 +00:00
return IsLongerChain ( block , candidate , c . Consensus ( ) , c . getPoolBlockByTemplateId , func ( h types . Hash ) * ChainMain {
if h == types . ZeroHash {
return c . server . GetChainMainTip ( )
2022-11-03 11:32:07 +00:00
}
2023-03-27 11:56:27 +00:00
return c . server . GetChainMainByHash ( h )
} )
2022-11-03 11:32:07 +00:00
}