2022-11-03 11:32:07 +00:00
package sidechain
import (
"bytes"
2023-05-30 06:49:52 +00:00
"context"
2022-11-03 11:32:07 +00:00
"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"
2023-05-11 07:51:00 +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"
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"
2023-05-12 06:55:01 +00:00
"git.gammaspectra.live/P2Pool/sha3"
2023-06-09 23:31:42 +00:00
"github.com/dolthub/swiss"
2023-06-27 09:36:00 +00:00
"slices"
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
2023-05-30 06:49:52 +00:00
Context ( ) context . Context
2023-04-22 23:38:33 +00:00
UpdateTip ( tip * PoolBlock )
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
2023-05-22 09:11:55 +00:00
seenBlocksLock sync . Mutex
2023-06-09 23:31:42 +00:00
seenBlocks * swiss . Map [ FullId , struct { } ]
2023-05-22 09:11:55 +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
2023-07-04 09:09:10 +00:00
blocksByTemplateId * swiss . Map [ types . Hash , * PoolBlock ]
blocksByHeight * swiss . Map [ uint64 , [ ] * PoolBlock ]
blocksByHeightKeysSorted bool
blocksByHeightKeys [ ] uint64
2022-11-03 11:32:07 +00:00
2023-05-17 15:29:01 +00:00
preAllocatedBuffer [ ] byte
2023-05-10 00:39:00 +00:00
syncTip atomic . Pointer [ PoolBlock ]
2022-11-03 11:32:07 +00:00
chainTip atomic . Pointer [ PoolBlock ]
currentDifficulty atomic . Pointer [ types . Difficulty ]
2022-12-09 12:13:00 +00:00
precalcFinished atomic . Bool
2023-05-14 07:22:01 +00:00
2023-07-20 05:37:10 +00:00
preAllocatedShares Shares
preAllocatedRewards [ ] uint64
preAllocatedSharesPool * PreAllocatedSharesPool
preAllocatedDifficultyData [ ] DifficultyData
preAllocatedTimestampData [ ] uint64
preAllocatedMinedBlocks [ ] types . Hash
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 {
2023-07-20 05:37:10 +00:00
derivationCache : NewDerivationMapCache ( ) ,
server : server ,
blocksByTemplateId : swiss . NewMap [ types . Hash , * PoolBlock ] ( uint32 ( server . Consensus ( ) . ChainWindowSize * 2 + 300 ) ) ,
blocksByHeight : swiss . NewMap [ uint64 , [ ] * PoolBlock ] ( uint32 ( server . Consensus ( ) . ChainWindowSize * 2 + 300 ) ) ,
preAllocatedShares : PreAllocateShares ( server . Consensus ( ) . ChainWindowSize * 2 ) ,
preAllocatedRewards : make ( [ ] uint64 , 0 , server . Consensus ( ) . ChainWindowSize * 2 ) ,
preAllocatedDifficultyData : make ( [ ] DifficultyData , 0 , server . Consensus ( ) . ChainWindowSize * 2 ) ,
preAllocatedTimestampData : make ( [ ] uint64 , 0 , server . Consensus ( ) . ChainWindowSize * 2 ) ,
preAllocatedSharesPool : NewPreAllocatedSharesPool ( server . Consensus ( ) . ChainWindowSize * 2 ) ,
preAllocatedBuffer : make ( [ ] byte , 0 , PoolBlockMaxTemplateSize ) ,
preAllocatedMinedBlocks : make ( [ ] types . Hash , 0 , 6 * UncleBlockDepth * 2 + 1 ) ,
seenBlocks : swiss . NewMap [ FullId , struct { } ] ( uint32 ( server . Consensus ( ) . ChainWindowSize * 2 + 300 ) ) ,
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 ) {
2023-03-27 18:22:35 +00:00
var preAllocatedShares Shares
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
2023-03-27 18:22:35 +00:00
} else {
2023-05-14 07:46:28 +00:00
preAllocatedShares = c . preAllocatedSharesPool . Get ( )
2023-05-14 07:22:01 +00:00
defer c . preAllocatedSharesPool . Put ( preAllocatedShares )
2022-12-03 22:43:18 +00:00
}
2022-12-12 13:20:08 +00:00
}
2023-03-21 10:01:51 +00:00
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 )
2023-07-12 10:42:47 +00:00
return bytes . Compare ( block . CoinbaseExtra ( SideCoinbasePublicKey ) , kP . PublicKey . AsSlice ( ) ) == 0 && block . Side . CoinbasePrivateKey == kP . PrivateKey . AsBytes ( )
2022-11-06 20:11:03 +00:00
}
2023-03-05 14:06:49 +00:00
func ( c * SideChain ) getSeedByHeightFunc ( ) mainblock . GetSeedByHeightFunc {
2023-03-27 18:22:35 +00:00
//TODO: do not make this return a function
2023-03-05 14:06:49 +00:00
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
}
}
}
2023-06-05 21:46:34 +00:00
func ( c * SideChain ) GetPossibleUncles ( tip * PoolBlock , forHeight uint64 ) ( uncles [ ] types . Hash ) {
minedBlocks := make ( [ ] types . Hash , 0 , UncleBlockDepth * 2 + 1 )
tmp := tip
c . sidechainLock . RLock ( )
defer c . sidechainLock . RUnlock ( )
2023-06-27 09:36:00 +00:00
for i , n := uint64 ( 0 ) , min ( UncleBlockDepth , tip . Side . Height + 1 ) ; tmp != nil && ( i < n ) ; i ++ {
2023-06-05 21:46:34 +00:00
minedBlocks = append ( minedBlocks , tmp . SideTemplateId ( c . Consensus ( ) ) )
for _ , uncleId := range tmp . Side . Uncles {
minedBlocks = append ( minedBlocks , uncleId )
}
tmp = c . getParent ( tmp )
}
2023-06-27 09:36:00 +00:00
for i , n := uint64 ( 0 ) , min ( UncleBlockDepth , tip . Side . Height + 1 ) ; i < n ; i ++ {
2023-06-05 21:46:34 +00:00
for _ , uncle := range c . getPoolBlocksByHeight ( tip . Side . Height - i ) {
// Only add verified and valid blocks
if ! uncle . Verified . Load ( ) || uncle . Invalid . Load ( ) {
continue
}
// Only add it if it hasn't been mined already
if slices . Contains ( minedBlocks , uncle . SideTemplateId ( c . Consensus ( ) ) ) {
continue
}
if sameChain := func ( ) bool {
tmp = tip
for tmp != nil && tmp . Side . Height > uncle . Side . Height {
tmp = c . getParent ( tmp )
}
if tmp == nil || tmp . Side . Height < uncle . Side . Height {
return false
}
tmp2 := uncle
for j := 0 ; j < UncleBlockDepth && tmp != nil && tmp2 != nil && ( tmp . Side . Height + UncleBlockDepth >= forHeight ) ; j ++ {
if tmp . Side . Parent == tmp2 . Side . Parent {
return true
}
tmp = c . getParent ( tmp )
tmp2 = c . getParent ( tmp2 )
}
return false
} ( ) ; sameChain {
uncles = append ( uncles , uncle . SideTemplateId ( c . Consensus ( ) ) )
}
}
}
if len ( uncles ) > 0 {
// Sort hashes, consensus
2023-06-27 09:36:00 +00:00
slices . SortFunc ( uncles , func ( a , b types . Hash ) int {
return a . Compare ( b )
2023-06-05 21:46:34 +00:00
} )
}
return uncles
}
2023-05-22 09:11:55 +00:00
func ( c * SideChain ) BlockSeen ( block * PoolBlock ) bool {
tip := c . GetChainTip ( )
//early exit for
if tip != nil && tip . Side . Height > ( block . Side . Height + c . Consensus ( ) . ChainWindowSize * 2 ) && block . Side . CumulativeDifficulty . Cmp ( tip . Side . CumulativeDifficulty ) < 0 {
return true
}
fullId := block . FullId ( )
c . seenBlocksLock . Lock ( )
defer c . seenBlocksLock . Unlock ( )
2023-06-09 23:31:42 +00:00
if c . seenBlocks . Has ( fullId ) {
2023-05-22 09:11:55 +00:00
return true
} else {
2023-06-09 23:31:42 +00:00
c . seenBlocks . Put ( fullId , struct { } { } )
2023-05-22 09:11:55 +00:00
return false
}
}
func ( c * SideChain ) BlockUnsee ( block * PoolBlock ) {
fullId := block . FullId ( )
c . seenBlocksLock . Lock ( )
defer c . seenBlocksLock . Unlock ( )
2023-06-09 23:31:42 +00:00
c . seenBlocks . Delete ( fullId )
2023-05-22 09:11:55 +00:00
}
2023-04-19 18:24:55 +00:00
func ( c * SideChain ) AddPoolBlockExternal ( block * PoolBlock ) ( missingBlocks [ ] types . Hash , err error , ban bool ) {
2023-07-07 11:17:11 +00:00
defer func ( ) {
if e := recover ( ) ; e != nil {
//recover from panics
missingBlocks = nil
if panicError , ok := e . ( error ) ; ok {
err = fmt . Errorf ( "panic: %w" , panicError )
} else {
err = fmt . Errorf ( "panic: %v" , e )
}
ban = true
2024-02-26 18:45:13 +00:00
utils . Errorf ( "SideChain" , "add_external_block: panic %v, block %+v" , e , block )
2023-07-07 11:17:11 +00:00
}
} ( )
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 {
2023-04-19 18:24:55 +00:00
return nil , errors . New ( "block reward too low" ) , true
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 ) {
2023-04-19 18:24:55 +00:00
return nil , errors . New ( "invalid deterministic transaction keys" ) , true
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 {
2023-04-19 18:24:55 +00:00
return missingBlocks , err , true
2022-11-03 11:32:07 +00:00
}
for _ , o := range block . Main . Coinbase . Outputs {
if o . Type != expectedTxType {
2023-04-19 18:24:55 +00:00
return nil , errors . New ( "unexpected transaction type" ) , true
2022-11-03 11:32:07 +00:00
}
}
2023-07-07 11:17:11 +00:00
templateId := types . HashFromBytes ( block . CoinbaseExtra ( SideTemplateId ) )
2022-11-03 11:32:07 +00:00
if templateId != block . SideTemplateId ( c . Consensus ( ) ) {
2023-04-19 18:24:55 +00:00
return nil , fmt . Errorf ( "invalid template id %s, expected %s" , templateId . String ( ) , block . SideTemplateId ( c . Consensus ( ) ) . String ( ) ) , true
2022-11-03 11:32:07 +00:00
}
if block . Side . Difficulty . Cmp64 ( c . Consensus ( ) . MinimumDifficulty ) < 0 {
2023-07-22 22:18:04 +00:00
return nil , fmt . Errorf ( "block mined by %s has invalid difficulty %s, expected >= %d" , block . GetAddress ( ) . ToBase58 ( c . Consensus ( ) . NetworkType . AddressNetwork ( ) ) , block . Side . Difficulty . StringNumeric ( ) , c . Consensus ( ) . MinimumDifficulty ) , true
2022-11-03 11:32:07 +00:00
}
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
2023-05-22 09:11:55 +00:00
newMainId := block . MainId ( )
oldMainId := block . MainId ( )
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "add_external_block: block id = %s is already added. New main id = %s, old main id = %s" , templateId , newMainId , oldMainId )
2023-05-22 09:11:55 +00:00
if newMainId != oldMainId && otherBlock . Verified . Load ( ) && ! otherBlock . Invalid . Load ( ) {
//other sections have been verified already, check PoW for new Main blocks
//specifically check Main id for nonce changes! p2pool does not do this
if _ , err := block . PowHashWithError ( c . Consensus ( ) . GetHasher ( ) , c . getSeedByHeightFunc ( ) ) ; err != nil {
return nil , err , false
} else {
if isHigherMainChain , err := block . IsProofHigherThanMainDifficultyWithError ( c . Consensus ( ) . GetHasher ( ) , c . server . GetDifficultyByHeight , c . getSeedByHeightFunc ( ) ) ; err != nil {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "add_external_block: couldn't get mainchain difficulty for height = %d: %s" , block . Main . Coinbase . GenHeight , err )
2023-05-22 09:11:55 +00:00
} else if isHigherMainChain {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "add_external_block: ALTERNATE block %s has enough PoW for Monero height %d, submitting it" , templateId . String ( ) , block . Main . Coinbase . GenHeight )
2023-05-22 09:11:55 +00:00
c . server . SubmitBlock ( & block . Main )
}
if isHigher , err := block . IsProofHigherThanDifficultyWithError ( c . Consensus ( ) . GetHasher ( ) , c . getSeedByHeightFunc ( ) ) ; err != nil {
return nil , err , true
} else if ! isHigher {
return nil , fmt . Errorf ( "not enough PoW for id %s, height = %d, mainchain height %d" , templateId . String ( ) , block . Side . Height , block . Main . Coinbase . GenHeight ) , true
}
2023-05-23 04:39:52 +00:00
{
c . sidechainLock . Lock ( )
defer c . sidechainLock . Unlock ( )
2023-05-22 09:11:55 +00:00
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "add_external_block: ALTERNATE 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 ( ) , c . blocksByTemplateId . Count ( ) )
2023-05-22 09:11:55 +00:00
2023-05-23 04:39:52 +00:00
block . Verified . Store ( true )
block . Invalid . Store ( false )
if block . SideTemplateId ( c . Consensus ( ) ) == c . watchBlockSidechainId {
c . server . UpdateBlockFound ( c . watchBlock , block )
c . watchBlockSidechainId = types . ZeroHash
}
2023-05-22 09:11:55 +00:00
2023-05-23 04:39:52 +00:00
block . Depth . Store ( otherBlock . Depth . Load ( ) )
if block . WantBroadcast . Load ( ) && ! block . Broadcasted . Swap ( true ) {
//re-broadcast alternate blocks
c . server . Broadcast ( block )
}
c . server . Store ( block )
}
2023-05-22 09:11:55 +00:00
}
}
2023-04-19 18:24:55 +00:00
return nil , nil , false
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 {
2023-07-22 22:18:04 +00:00
return nil , fmt . Errorf ( "block mined by %s has too low difficulty %s, expected >= %s" , block . GetAddress ( ) . ToBase58 ( c . Consensus ( ) . NetworkType . AddressNetwork ( ) ) , block . Side . Difficulty . StringNumeric ( ) , expectedDifficulty . StringNumeric ( ) ) , false
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 {
2023-04-19 18:24:55 +00:00
return nil , fmt . Errorf ( "wrong mainchain height %d, expected %d" , block . Main . Coinbase . GenHeight , data . Height + 1 ) , true
2022-11-03 11:32:07 +00:00
}
} else {
//TODO warn unknown block, reorg
}
2023-04-30 19:49:25 +00:00
if _ , err := block . PowHashWithError ( c . Consensus ( ) . GetHasher ( ) , c . getSeedByHeightFunc ( ) ) ; err != nil {
2023-05-22 09:11:55 +00:00
c . BlockUnsee ( block )
2023-04-19 18:24:55 +00:00
return nil , err , false
2022-11-03 11:32:07 +00:00
} else {
2023-04-30 19:49:25 +00:00
if isHigherMainChain , err := block . IsProofHigherThanMainDifficultyWithError ( c . Consensus ( ) . GetHasher ( ) , c . server . GetDifficultyByHeight , c . getSeedByHeightFunc ( ) ) ; err != nil {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "add_external_block: couldn't get mainchain difficulty for height = %d: %s" , block . Main . Coinbase . GenHeight , err )
2023-03-05 14:06:49 +00:00
} else if isHigherMainChain {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "add_external_block: block %s has enough PoW for Monero height %d, submitting it" , templateId . String ( ) , block . Main . Coinbase . GenHeight )
2023-03-05 14:06:49 +00:00
c . server . SubmitBlock ( & block . Main )
}
2023-04-30 19:49:25 +00:00
if isHigher , err := block . IsProofHigherThanDifficultyWithError ( c . Consensus ( ) . GetHasher ( ) , c . getSeedByHeightFunc ( ) ) ; err != nil {
2023-04-19 18:24:55 +00:00
return nil , err , true
2022-11-03 11:32:07 +00:00
} else if ! isHigher {
2023-04-19 18:24:55 +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 ) , true
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
2023-04-19 18:24:55 +00:00
} ( ) , c . AddPoolBlock ( block ) , true
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 ( )
2023-06-09 23:31:42 +00:00
if c . blocksByTemplateId . Has ( block . SideTemplateId ( c . Consensus ( ) ) ) {
2022-11-03 11:32:07 +00:00
//already inserted
//TODO WARN
return nil
}
2023-04-21 13:41:50 +00:00
2023-05-18 03:14:03 +00:00
if _ , err := block . AppendBinaryFlags ( c . preAllocatedBuffer , false , false ) ; err != nil {
2023-04-21 13:41:50 +00:00
return fmt . Errorf ( "encoding block error: %w" , err )
}
2023-06-09 23:31:42 +00:00
c . blocksByTemplateId . Put ( block . SideTemplateId ( c . Consensus ( ) ) , block )
2022-11-03 11:32:07 +00:00
2024-02-26 18:45:13 +00:00
utils . Logf ( "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 ( ) , c . blocksByTemplateId . Count ( ) )
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
}
2023-06-09 23:31:42 +00:00
if l , ok := c . blocksByHeight . Get ( block . Side . Height ) ; ok {
c . blocksByHeight . Put ( block . Side . Height , append ( l , block ) )
2022-11-03 11:32:07 +00:00
} else {
2023-06-09 23:31:42 +00:00
c . blocksByHeight . Put ( block . Side . Height , [ ] * PoolBlock { block } )
2023-07-04 09:09:10 +00:00
if ! ( c . blocksByHeightKeysSorted && len ( c . blocksByHeightKeys ) > 0 && c . blocksByHeightKeys [ len ( c . blocksByHeightKeys ) - 1 ] + 1 == block . Side . Height ) {
c . blocksByHeightKeysSorted = false
}
c . blocksByHeightKeys = append ( c . blocksByHeightKeys , block . Side . Height )
2022-11-03 11:32:07 +00:00
}
c . updateDepths ( block )
2023-05-10 00:39:00 +00:00
defer func ( ) {
if ! block . Invalid . Load ( ) && block . Depth . Load ( ) == 0 {
c . syncTip . Store ( block )
}
} ( )
2022-11-03 11:32:07 +00:00
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 {
2024-02-26 18:45:13 +00:00
utils . Logf ( "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 ( c . Consensus ( ) . NetworkType . AddressNetwork ( ) ) , invalid . Error ( ) )
2022-11-06 10:58:19 +00:00
block . Invalid . Store ( true )
block . Verified . Store ( verification == nil )
if block == blockToVerify {
//Save error for return
err = invalid
}
} else if verification != nil {
2024-02-26 18:45:13 +00:00
//utils.Logf("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 {
2024-02-26 18:45:13 +00:00
utils . Logf ( "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 ( c . Consensus ( ) . NetworkType . AddressNetwork ( ) ) , block . Side . ExtraBuffer . SoftwareId , block . Side . ExtraBuffer . SoftwareVersion )
2023-03-11 18:08:04 +00:00
} else {
if signalingVersion := block . ShareVersionSignaling ( ) ; signalingVersion > ShareVersion_None {
2024-02-26 18:45:13 +00:00
utils . Logf ( "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 ( c . Consensus ( ) . NetworkType . AddressNetwork ( ) ) , signalingVersion )
2023-03-11 18:08:04 +00:00
} else {
2024-02-26 18:45:13 +00:00
utils . Logf ( "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 ( c . Consensus ( ) . NetworkType . AddressNetwork ( ) ) )
2023-03-11 18:08:04 +00:00
}
}
2022-11-03 11:32:07 +00:00
// This block is now verified
2023-07-01 12:17:24 +00:00
// Fill cache here
2023-07-05 09:37:27 +00:00
if parent := c . getParent ( block ) ; parent != nil {
block . iterationCache = & IterationCache {
Parent : parent ,
Uncles : nil ,
}
if len ( block . Side . Uncles ) > 0 {
block . iterationCache . Uncles = make ( [ ] * PoolBlock , 0 , len ( block . Side . Uncles ) )
for _ , uncleId := range block . Side . Uncles {
if uncle := c . getPoolBlockByTemplateId ( uncleId ) ; uncle == nil {
block . iterationCache = nil
break
} else {
block . iterationCache . Uncles = append ( block . iterationCache . Uncles , uncle )
}
}
2023-07-01 12:17:24 +00:00
}
}
c . fillPoolBlockTransactionParentIndices ( block )
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 {
2024-02-26 18:45:13 +00:00
utils . Logf ( "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
}
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 ++ {
2023-06-09 23:31:42 +00:00
blocksAtHeight , _ := c . blocksByHeight . Get ( block . Side . Height + i )
blocksToVerify = append ( blocksToVerify , blocksAtHeight ... )
2022-11-03 11:32:07 +00:00
}
}
}
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 ||
2023-06-05 21:41:32 +00:00
( block . ShareVersion ( ) > ShareVersion_V1 && block . Side . CoinbasePrivateKeySeed != c . Consensus ( ) . Id ) {
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
2023-04-22 17:03:24 +00:00
// If a block is deeper than (m_chainWindowSize - 1) * 2 + UNCLE_BLOCK_DEPTH it can't influence blocks in PPLNS window
2022-11-03 11:32:07 +00:00
// 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
2023-04-22 17:03:24 +00:00
if block . Depth . Load ( ) > ( ( c . Consensus ( ) . ChainWindowSize - 1 ) * 2 + UncleBlockDepth ) {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "block at height = %d, id = %s skipped verification" , block . Side . Height , block . SideTemplateId ( c . Consensus ( ) ) )
2022-11-06 10:58:19 +00:00
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
2023-06-27 09:36:00 +00:00
minedBlocks := c . preAllocatedMinedBlocks [ : 0 ]
2022-11-03 11:32:07 +00:00
{
tmp := parent
2023-06-27 09:36:00 +00:00
n := min ( UncleBlockDepth , block . Side . Height + 1 )
2022-11-03 11:32:07 +00:00
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 {
2024-02-26 18:45:13 +00:00
utils . Logf ( "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-04-26 07:32:52 +00:00
} else if diff , verification , invalid = c . getDifficulty ( parent ) ; verification != nil || invalid != nil {
return verification , invalid
} else if 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-05-14 07:22:01 +00:00
if shares , _ := c . getShares ( block , c . preAllocatedShares ) ; len ( shares ) == 0 {
2022-11-06 10:58:19 +00:00
return nil , errors . New ( "could not get outputs" )
2023-05-14 07:22:01 +00:00
} else if len ( shares ) != len ( block . Main . Coinbase . Outputs ) {
return nil , fmt . Errorf ( "invalid number of outputs, got %d, expected %d" , len ( block . Main . Coinbase . Outputs ) , len ( shares ) )
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-07-12 10:32:02 +00:00
} else if rewards := SplitReward ( c . preAllocatedRewards , totalReward , shares ) ; 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 ( )
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
var anyErr atomic . Value
defer func ( ) {
for _ , h := range hashers {
crypto . PutKeccak256Hasher ( h )
}
} ( )
if ! utils . SplitWork ( - 2 , uint64 ( len ( rewards ) ) , func ( workIndex uint64 , workerIndex int ) error {
2022-11-06 20:11:03 +00:00
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
2023-05-14 07:22:01 +00:00
if ephPublicKey , viewTag := c . derivationCache . GetEphemeralPublicKey ( & shares [ workIndex ] . Address , txPrivateKeySlice , txPrivateKeyScalar , workIndex , hashers [ workerIndex ] ) ; 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
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
} , func ( routineIndex int , err error ) {
anyErr . Store ( err )
} ) {
return nil , anyErr . Load ( ) . ( error )
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 ) {
2023-07-21 14:20:41 +00:00
preCalcDepth := c . Consensus ( ) . ChainWindowSize + UncleBlockDepth - 1
updateDepth := func ( b * PoolBlock , newDepth uint64 ) {
oldDepth := b . Depth . Load ( )
if oldDepth < newDepth {
b . Depth . Store ( newDepth )
if oldDepth < preCalcDepth && newDepth >= preCalcDepth {
//TODO launchPrecalc
}
}
}
2022-11-03 11:32:07 +00:00
for i := uint64 ( 1 ) ; i <= UncleBlockDepth ; i ++ {
2023-06-09 23:31:42 +00:00
blocksAtHeight , _ := c . blocksByHeight . Get ( block . Side . Height + i )
for _ , child := range blocksAtHeight {
2023-07-05 09:37:27 +00:00
if child . Side . Parent == block . SideTemplateId ( c . Consensus ( ) ) {
2022-11-03 11:32:07 +00:00
if i != 1 {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "Block %s side height %d is inconsistent with child's side_height %d" , block . SideTemplateId ( c . Consensus ( ) ) , block . Side . Height , child . Side . Height )
2023-05-29 11:13:57 +00:00
return
2022-11-03 11:32:07 +00:00
} else {
2023-07-21 14:20:41 +00:00
updateDepth ( block , child . Depth . Load ( ) + 1 )
2022-11-03 11:32:07 +00:00
}
}
if ix := slices . Index ( child . Side . Uncles , block . SideTemplateId ( c . Consensus ( ) ) ) ; ix != 1 {
2023-07-21 14:20:41 +00:00
updateDepth ( block , child . Depth . Load ( ) + 1 )
2022-11-03 11:32:07 +00:00
}
}
}
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 ) {
2023-05-29 08:23:15 +00:00
_ = c . verifyLoop ( block )
2022-11-03 11:32:07 +00:00
}
2023-07-21 14:20:41 +00:00
for i := uint64 ( 1 ) ; i <= UncleBlockDepth ; i ++ {
for _ , child := range c . getPoolBlocksByHeight ( block . Side . Height + i ) {
oldDepth := child . Depth . Load ( )
if child . Side . Parent == block . SideTemplateId ( c . Consensus ( ) ) {
if i != 1 {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "Block %s side height %d is inconsistent with child's side_height %d" , block . SideTemplateId ( c . Consensus ( ) ) , block . Side . Height , child . Side . Height )
2023-07-21 14:20:41 +00:00
return
} else if blockDepth > 0 {
updateDepth ( child , blockDepth - 1 )
}
}
if slices . Contains ( child . Side . Uncles , block . SideTemplateId ( c . Consensus ( ) ) ) {
if blockDepth > i {
updateDepth ( child , blockDepth - i )
}
}
if child . Depth . Load ( ) > oldDepth {
blocksToUpdate = append ( blocksToUpdate , child )
}
}
}
2023-07-05 09:37:27 +00:00
if parent := block . iteratorGetParent ( c . getPoolBlockByTemplateId ) ; parent != nil {
2022-11-03 11:32:07 +00:00
if parent . Side . Height + 1 != block . Side . Height {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "Block %s side height %d is inconsistent with parent's side_height %d" , block . SideTemplateId ( c . Consensus ( ) ) , block . Side . Height , parent . Side . Height )
2023-05-29 08:23:15 +00:00
return
2022-11-03 11:32:07 +00:00
}
if parent . Depth . Load ( ) < blockDepth + 1 {
2023-07-21 14:20:41 +00:00
updateDepth ( parent , blockDepth + 1 )
2022-11-03 11:32:07 +00:00
blocksToUpdate = append ( blocksToUpdate , parent )
}
}
2023-07-05 09:37:27 +00:00
var returnFromUncles bool
2022-11-03 11:32:07 +00:00
2023-07-05 09:37:27 +00:00
_ = block . iteratorUncles ( c . getPoolBlockByTemplateId , func ( uncle * PoolBlock ) {
if uncle . Side . Height >= block . Side . Height || ( uncle . Side . Height + UncleBlockDepth < block . Side . Height ) {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "Block %s side height %d is inconsistent with uncle's side_height %d" , block . SideTemplateId ( c . Consensus ( ) ) , block . Side . Height , uncle . Side . Height )
2023-07-05 09:37:27 +00:00
returnFromUncles = true
return
}
d := block . Side . Height - uncle . Side . Height
if uncle . Depth . Load ( ) < blockDepth + d {
2023-07-21 14:20:41 +00:00
updateDepth ( uncle , blockDepth + d )
2023-07-05 09:37:27 +00:00
blocksToUpdate = append ( blocksToUpdate , uncle )
2022-11-03 11:32:07 +00:00
}
2023-07-05 09:37:27 +00:00
} )
if returnFromUncles {
return
2022-11-03 11:32:07 +00:00
}
}
}
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 {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "Trying to update chain tip to the same block again. Ignoring it." )
2023-03-07 17:57:06 +00:00
return
}
2022-11-06 12:17:26 +00:00
if isLongerChain , isAlternative := c . isLongerChain ( tip , block ) ; isLongerChain {
2023-04-26 07:32:52 +00:00
if diff , _ , _ := c . getDifficulty ( block ) ; diff != types . ZeroDifficulty {
2022-11-03 11:32:07 +00:00
c . chainTip . Store ( block )
2023-05-10 00:39:00 +00:00
c . syncTip . Store ( block )
2022-11-03 11:32:07 +00:00
c . currentDifficulty . Store ( & diff )
//TODO log
block . WantBroadcast . Store ( true )
2023-04-22 23:38:33 +00:00
c . server . UpdateTip ( block )
2022-11-03 11:32:07 +00:00
if isAlternative {
2024-02-22 16:12:14 +00:00
c . precalcFinished . Store ( true )
2022-12-09 12:13:00 +00:00
c . derivationCache . Clear ( )
2024-02-26 18:45:13 +00:00
utils . Logf ( "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 {
2024-02-26 18:45:13 +00:00
utils . Logf ( "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 {
2024-02-26 18:45:13 +00:00
utils . Logf ( "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
2023-07-04 09:09:10 +00:00
if ! c . blocksByHeightKeysSorted {
slices . Sort ( c . blocksByHeightKeys )
c . blocksByHeightKeysSorted = true
}
2022-12-09 12:13:00 +00:00
numBlocksPruned := 0
2023-07-04 09:09:10 +00:00
for keyIndex , height := range c . blocksByHeightKeys {
// Early exit
2022-12-09 12:13:00 +00:00
if height > h {
2023-07-04 09:09:10 +00:00
break
2022-12-09 12:13:00 +00:00
}
2023-07-04 09:09:10 +00:00
v , _ := c . blocksByHeight . Get ( height )
2022-12-09 12:13:00 +00:00
// 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 ) ) {
2023-07-01 12:17:24 +00:00
templateId := block . SideTemplateId ( c . Consensus ( ) )
if c . blocksByTemplateId . Has ( templateId ) {
c . blocksByTemplateId . Delete ( templateId )
2022-12-09 12:13:00 +00:00
numBlocksPruned ++
} else {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "blocksByHeight and blocksByTemplateId are inconsistent at height = %d, id = %s" , height , block . SideTemplateId ( c . Consensus ( ) ) )
2022-12-09 12:13:00 +00:00
}
v = slices . Delete ( v , i , i + 1 )
2023-07-01 12:17:24 +00:00
// Empty cache here
block . iterationCache = nil
2022-12-09 12:13:00 +00:00
}
}
if len ( v ) == 0 {
2023-06-09 23:31:42 +00:00
c . blocksByHeight . Delete ( height )
2023-07-04 09:09:10 +00:00
c . blocksByHeightKeys = slices . Delete ( c . blocksByHeightKeys , keyIndex , keyIndex + 1 )
2022-12-09 12:13:00 +00:00
} else {
2023-06-09 23:31:42 +00:00
c . blocksByHeight . Put ( height , v )
2022-12-09 12:13:00 +00:00
}
2023-07-04 09:09:10 +00:00
}
2022-12-09 12:13:00 +00:00
if numBlocksPruned > 0 {
2024-02-26 18:45:13 +00:00
utils . Logf ( "SideChain" , "pruned %d old blocks at heights <= %d" , numBlocksPruned , h )
2022-12-09 12:13:00 +00:00
if ! c . precalcFinished . Swap ( true ) {
c . derivationCache . Clear ( )
}
2023-05-23 04:39:52 +00:00
numSeenBlocksPruned := c . cleanupSeenBlocks ( )
if numSeenBlocksPruned > 0 {
2024-02-26 18:45:13 +00:00
//utils.Logf("SideChain", "pruned %d seen blocks", numBlocksPruned)
2023-05-23 04:39:52 +00:00
}
}
}
func ( c * SideChain ) cleanupSeenBlocks ( ) ( cleaned int ) {
2023-05-24 07:14:27 +00:00
c . seenBlocksLock . Lock ( )
defer c . seenBlocksLock . Unlock ( )
2023-06-09 23:31:42 +00:00
c . seenBlocks . Iter ( func ( k FullId , _ struct { } ) ( stop bool ) {
2023-05-23 04:39:52 +00:00
if c . getPoolBlockByTemplateId ( k . TemplateId ( ) ) == nil {
2023-06-09 23:31:42 +00:00
c . seenBlocks . Delete ( k )
2023-05-23 04:39:52 +00:00
cleaned ++
}
2023-06-09 23:31:42 +00:00
return false
} )
2023-05-23 04:39:52 +00:00
return cleaned
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 )
2023-06-09 23:31:42 +00:00
c . blocksByTemplateId . Iter ( func ( _ types . Hash , b * PoolBlock ) ( stop bool ) {
2022-12-12 15:58:45 +00:00
if b . Verified . Load ( ) {
2023-06-09 23:31:42 +00:00
return false
2022-12-12 15:58:45 +00:00
}
2023-07-05 09:37:27 +00:00
if b . Side . Parent != types . ZeroHash && c . getPoolBlockByTemplateId ( b . Side . Parent ) == nil {
2022-12-12 15:58:45 +00:00
missingBlocks = append ( missingBlocks , b . Side . Parent )
}
missingUncles := 0
for _ , uncleId := range b . Side . Uncles {
2023-07-05 09:37:27 +00:00
if uncleId != types . ZeroHash && c . getPoolBlockByTemplateId ( uncleId ) == nil {
2022-12-12 15:58:45 +00:00
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 {
2023-06-09 23:31:42 +00:00
return true
2022-12-12 15:58:45 +00:00
}
}
}
2023-06-09 23:31:42 +00:00
return false
} )
2022-12-12 15:58:45 +00:00
return missingBlocks
}
2023-07-12 10:32:02 +00:00
// calculateOutputs
// Deprecated
2023-03-21 10:01:51 +00:00
func ( c * SideChain ) calculateOutputs ( block * PoolBlock ) ( outputs transaction . Outputs , bottomHeight uint64 ) {
2023-05-14 07:46:28 +00:00
preAllocatedShares := c . preAllocatedSharesPool . Get ( )
2023-05-14 07:22:01 +00:00
defer c . preAllocatedSharesPool . Put ( preAllocatedShares )
2023-07-12 10:32:02 +00:00
return CalculateOutputs ( block , c . Consensus ( ) , c . server . GetDifficultyByHeight , c . getPoolBlockByTemplateId , c . derivationCache , preAllocatedShares , c . preAllocatedRewards )
2022-11-03 11:32:07 +00:00
}
2023-05-30 06:49:52 +00:00
func ( c * SideChain ) Server ( ) P2PoolInterface {
return c . server
}
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
}
2023-04-26 07:32:52 +00:00
func ( c * SideChain ) GetDifficulty ( tip * PoolBlock ) ( difficulty types . Difficulty , verifyError , invalidError error ) {
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 )
}
2023-04-26 07:32:52 +00:00
func ( c * SideChain ) getDifficulty ( tip * PoolBlock ) ( difficulty types . Difficulty , verifyError , invalidError error ) {
2023-07-20 05:37:10 +00:00
return GetDifficultyForNextBlock ( tip , c . Consensus ( ) , c . getPoolBlockByTemplateId , c . preAllocatedDifficultyData , c . preAllocatedTimestampData )
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 {
2023-07-21 14:20:41 +00:00
return block . iteratorGetParent ( c . getPoolBlockByTemplateId )
2022-11-03 11:32:07 +00:00
}
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 {
2023-06-09 23:31:42 +00:00
b , _ := c . blocksByTemplateId . Get ( id )
return b
2022-12-11 12:31:43 +00:00
}
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 {
2023-06-09 23:31:42 +00:00
b , _ := c . blocksByHeight . Get ( height )
return b
2022-12-11 12:31:43 +00:00
}
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 ) {
2023-06-27 09:36:00 +00:00
chain = make ( [ ] * PoolBlock , 0 , min ( depth , c . Consensus ( ) . ChainWindowSize * 2 + monero . BlockTime / c . Consensus ( ) . TargetBlockTime ) )
2023-03-18 20:19:54 +00:00
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 ( )
2023-06-09 23:31:42 +00:00
return c . blocksByTemplateId . Count ( )
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
}
2023-05-10 00:39:00 +00:00
func ( c * SideChain ) GetHighestKnownTip ( ) * PoolBlock {
if t := c . chainTip . Load ( ) ; t != nil {
return t
}
return c . syncTip . Load ( )
}
2022-11-03 11:32:07 +00:00
func ( c * SideChain ) GetChainTip ( ) * PoolBlock {
return c . chainTip . Load ( )
}
2023-04-19 13:16:56 +00:00
func ( c * SideChain ) LastUpdated ( ) uint64 {
if tip := c . chainTip . Load ( ) ; tip != nil {
return tip . LocalTimestamp
}
return 0
}
2022-11-03 11:32:07 +00:00
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
}