2022-10-08 18:55:01 +00:00
package main
import (
2023-01-18 14:41:50 +00:00
"flag"
2022-10-08 18:55:01 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/database"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
2023-03-18 20:19:54 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
p2poolapi "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/api"
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
2022-10-08 18:55:01 +00:00
"log"
"os"
"time"
)
2023-03-18 20:19:54 +00:00
func blockId ( b * sidechain . PoolBlock ) types . Hash {
return types . HashFromBytes ( b . CoinbaseExtra ( sidechain . SideTemplateId ) )
}
2022-10-08 18:55:01 +00:00
func main ( ) {
2023-01-18 14:41:50 +00:00
startFromHeight := flag . Uint64 ( "from" , 0 , "Start sync from this height" )
dbString := flag . String ( "db" , "" , "" )
2023-03-18 20:19:54 +00:00
p2poolApiHost := flag . String ( "api-host" , "" , "Host URL for p2pool go observer consensus" )
2023-01-18 14:41:50 +00:00
flag . Parse ( )
2022-12-16 19:32:57 +00:00
client . SetDefaultClientSettings ( os . Getenv ( "MONEROD_RPC_URL" ) )
2023-01-18 14:41:50 +00:00
db , err := database . NewDatabase ( * dbString )
2022-10-08 18:55:01 +00:00
if err != nil {
log . Panic ( err )
}
defer db . Close ( )
2023-03-20 10:47:09 +00:00
p2api := p2poolapi . NewP2PoolApi ( * p2poolApiHost )
api , err := p2poolapi . New ( db , p2api )
2022-10-08 18:55:01 +00:00
if err != nil {
log . Panic ( err )
}
//TODO: force-insert section
tip := db . GetChainTip ( )
isFresh := tip == nil
2023-03-18 20:19:54 +00:00
var tipHeight uint64
2022-10-08 18:55:01 +00:00
if tip != nil {
tipHeight = tip . Height
}
log . Printf ( "[CHAIN] Last known database tip is %d\n" , tipHeight )
2023-03-18 20:19:54 +00:00
for status := p2api . Status ( ) ; ! p2api . Status ( ) . Synchronized ; status = p2api . Status ( ) {
log . Printf ( "[API] Not synchronized (height %d, id %s), waiting five seconds" , status . Height , status . Id )
time . Sleep ( time . Second * 5 )
2022-10-08 18:55:01 +00:00
}
2023-03-18 20:19:54 +00:00
log . Printf ( "[CHAIN] Consensus id = %s\n" , p2api . Consensus ( ) . Id ( ) )
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
getSeedByHeight := func ( height uint64 ) ( hash types . Hash ) {
seedHeight := randomx . SeedHeight ( height )
if h := p2api . MainHeaderByHeight ( seedHeight ) ; h == nil {
return types . ZeroHash
} else {
return h . Id
}
}
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
tipChain , tipUncles := p2api . StateFromTip ( )
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
if len ( tipChain ) == 0 || len ( tipChain ) < int ( p2api . Consensus ( ) . ChainWindowSize * 2 ) {
log . Panicf ( "[CHAIN] Too small length %d" , len ( tipChain ) )
2022-10-08 18:55:01 +00:00
}
2023-03-18 20:19:54 +00:00
p2poolTip := tipChain [ 0 ] . Side . Height
log . Printf ( "[CHAIN] Last known p2pool tip is %d\n" , p2poolTip )
startFrom := utils . Max ( tipHeight , tipChain [ len ( tipChain ) - 1 ] . Side . Height )
//TODO: archive
2023-01-18 14:41:50 +00:00
if * startFromHeight != 0 {
log . Printf ( "[CHAIN] Forcing start tip to %d\n" , * startFromHeight )
startFrom = * startFromHeight
}
2023-03-18 20:19:54 +00:00
if isFresh || startFrom != p2poolTip {
if startFrom > p2poolTip {
startFrom = p2poolTip
2022-10-08 18:55:01 +00:00
}
2023-03-18 20:19:54 +00:00
2023-03-19 09:07:46 +00:00
if isFresh {
currentIndex := int ( p2poolTip - startFrom )
if currentIndex > ( len ( tipChain ) - 1 ) {
currentIndex = len ( tipChain ) - 1
}
for ; currentIndex >= 0 ; currentIndex -- {
b := tipChain [ currentIndex ]
2023-03-18 20:19:54 +00:00
2023-03-19 09:07:46 +00:00
if block , uncles , err := database . NewBlockFromBinaryBlock ( getSeedByHeight , p2api . MainDifficultyByHeight , db , b , tipUncles , true ) ; err != nil {
log . Panicf ( "[CHAIN] Could not find share %s to insert at height %d. Check disk or uncles: %s\n" , blockId ( b ) . String ( ) , b . Side . Height , err )
} else {
log . Printf ( "[CHAIN] Inserting share %s at height %d\n" , blockId ( b ) . String ( ) , b . Side . Height )
if err = db . InsertBlock ( block , nil ) ; err != nil {
2023-03-18 20:19:54 +00:00
log . Panic ( err )
}
2023-03-19 09:07:46 +00:00
for _ , uncle := range uncles {
log . Printf ( "[CHAIN] Inserting uncle share %s at height %d\n" , uncle . Block . Id . String ( ) , b . Side . Height )
if err = db . InsertUncleBlock ( uncle , nil ) ; err != nil {
log . Panic ( err )
}
}
2023-03-18 20:19:54 +00:00
}
2023-03-19 09:07:46 +00:00
startFrom = b . Side . Height
}
}
2022-10-08 18:55:01 +00:00
}
//TODO: handle jumps in blocks (missing data)
knownTip := startFrom
log . Printf ( "[CHAIN] Starting tip from height %d\n" , knownTip )
runs := 0
//Fix blocks without height
for b := range db . GetBlocksByQuery ( "WHERE miner_main_difficulty = 'ffffffffffffffffffffffffffffffff' ORDER BY main_height ASC;" ) {
cacheHeightDifficulty ( b . Main . Height )
if diff , ok := getHeightDifficulty ( b . Main . Height ) ; ok {
log . Printf ( "[CHAIN] Filling main difficulty for share %d, main height %d\n" , b . Height , b . Main . Height )
_ = db . SetBlockMainDifficulty ( b . Id , diff )
b = db . GetBlockById ( b . Id )
if ! b . Main . Found && b . IsProofHigherThanDifficulty ( ) {
log . Printf ( "[CHAIN] BLOCK FOUND! Main height %d, main id %s\n" , b . Main . Height , b . Main . Id . String ( ) )
2022-12-16 19:32:57 +00:00
if tx , _ := client . GetDefaultClient ( ) . GetCoinbaseTransaction ( b . Coinbase . Id ) ; tx != nil {
2022-10-08 18:55:01 +00:00
_ = db . SetBlockFound ( b . Id , true )
processFoundBlockWithTransaction ( api , b , tx )
}
}
}
}
//Fix uncle without height
for u := range db . GetUncleBlocksByQuery ( "WHERE miner_main_difficulty = 'ffffffffffffffffffffffffffffffff' ORDER BY main_height ASC;" ) {
cacheHeightDifficulty ( u . Block . Main . Height )
if diff , ok := getHeightDifficulty ( u . Block . Main . Height ) ; ok {
log . Printf ( "[CHAIN] Filling main difficulty for uncle share %d, main height %d\n" , u . Block . Height , u . Block . Main . Height )
_ = db . SetBlockMainDifficulty ( u . Block . Id , diff )
u = db . GetUncleById ( u . Block . Id )
if ! u . Block . Main . Found && u . Block . IsProofHigherThanDifficulty ( ) {
log . Printf ( "[CHAIN] BLOCK FOUND! Main height %d, main id %s\n" , u . Block . Main . Height , u . Block . Main . Id . String ( ) )
2022-12-16 19:32:57 +00:00
if tx , _ := client . GetDefaultClient ( ) . GetCoinbaseTransaction ( u . Block . Coinbase . Id ) ; tx != nil {
2022-10-08 18:55:01 +00:00
_ = db . SetBlockFound ( u . Block . Id , true )
processFoundBlockWithTransaction ( api , u , tx )
}
}
}
}
2023-03-20 07:59:13 +00:00
var chain sidechain . UniquePoolBlockSlice
2022-10-08 18:55:01 +00:00
for {
2023-03-18 20:19:54 +00:00
toStart :
2023-03-20 07:59:13 +00:00
if len ( chain ) == 0 {
time . Sleep ( time . Second * 1 )
}
2022-10-08 18:55:01 +00:00
runs ++
2023-03-20 07:59:13 +00:00
var p2tip * sidechain . PoolBlock
if len ( chain ) > 0 {
p2tip = chain [ len ( chain ) - 1 ]
chain = chain [ : len ( chain ) - 1 ]
} else {
p2tip = p2api . Tip ( )
}
2023-03-18 20:19:54 +00:00
if p2tip == nil {
log . Panicf ( "[CHAIN] could not find tip, at %d" , knownTip )
2022-12-01 13:16:37 +00:00
}
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
if p2tip . Side . Height < knownTip {
log . Panicf ( "[CHAIN] tip went backwards! %d -> %d" , knownTip , p2tip . Side . Height )
} else if p2tip . Side . Height > ( knownTip + 1 ) {
log . Printf ( "[CHAIN] tip went forwards! Rolling back %d -> %d" , knownTip , p2tip . Side . Height )
for i := p2tip . Side . Height - ( knownTip + 1 ) ; i > 0 && p2tip != nil ; i -- {
2023-03-20 07:59:13 +00:00
chain = append ( chain , p2tip )
2023-03-18 20:19:54 +00:00
p2tip = p2api . ByTemplateId ( p2tip . Side . Parent )
}
}
if p2tip == nil {
log . Panicf ( "[CHAIN] could not find tip after depth, at %d" , knownTip )
2022-10-08 18:55:01 +00:00
}
dbTip := db . GetBlockByHeight ( knownTip )
2023-03-20 07:59:13 +00:00
if dbTip != nil && dbTip . Id == blockId ( p2tip ) { // no changes
2023-03-18 20:19:54 +00:00
continue
}
2023-03-20 07:59:13 +00:00
if dbTip != nil && dbTip . Id != p2tip . Side . Parent { //Reorg has happened, delete old values
2022-10-24 12:36:50 +00:00
log . Printf ( "[REORG] Reorg happened, deleting blocks to match from height %d.\n" , dbTip . Height )
2023-03-18 20:19:54 +00:00
diskBlock := p2tip
2022-10-08 18:55:01 +00:00
for h := knownTip ; h > 0 ; h -- {
dbBlock := db . GetBlockByHeight ( h )
2023-03-18 20:19:54 +00:00
diskBlock = p2api . ByTemplateId ( diskBlock . Side . Parent )
2023-03-19 08:36:38 +00:00
if dbBlock . Id == blockId ( diskBlock ) {
2022-10-08 18:55:01 +00:00
log . Printf ( "[REORG] Found matching head %s at height %d\n" , dbBlock . PreviousId . String ( ) , dbBlock . Height - 1 )
2022-10-26 16:50:44 +00:00
deleted , err := db . DeleteBlockById ( dbBlock . Id )
if err != nil {
log . Panic ( err )
}
2022-10-26 16:46:41 +00:00
log . Printf ( "[REORG] Deleted %d shares(s).\n" , deleted )
2023-03-18 20:19:54 +00:00
log . Printf ( "[REORG] Next tip %s : %d.\n" , blockId ( diskBlock ) , diskBlock . Side . Height )
2022-10-08 18:55:01 +00:00
knownTip = dbBlock . Height - 1
break
}
}
continue
}
2023-03-18 20:19:54 +00:00
tipUncles = tipUncles [ : 0 ]
for _ , uncleId := range p2tip . Side . Uncles {
if u := p2api . ByTemplateId ( uncleId ) ; u == nil {
goto toStart
} else {
tipUncles = append ( tipUncles , u )
2022-10-08 18:55:01 +00:00
}
2023-03-18 20:19:54 +00:00
}
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
diskBlock , uncles , err := database . NewBlockFromBinaryBlock ( getSeedByHeight , p2api . MainDifficultyByHeight , db , p2tip , tipUncles , true )
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
if err != nil {
log . Printf ( "[CHAIN] Could not find share %s to insert at height %d. Check disk or uncles\n" , blockId ( p2tip ) , p2tip . Side . Height )
continue
}
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
prevBlock := db . GetBlockByHeight ( p2tip . Side . Height - 1 )
2023-03-20 07:59:13 +00:00
if prevBlock != nil && diskBlock . PreviousId != prevBlock . Id {
2023-03-18 20:19:54 +00:00
log . Printf ( "[CHAIN] Possible reorg occurred, aborting insertion at height %d: prev id %s != id %s\n" , p2tip . Side . Height , diskBlock . PreviousId . String ( ) , prevBlock . Id . String ( ) )
continue
}
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
log . Printf ( "[CHAIN] Inserting share %s at height %d\n" , diskBlock . Id . String ( ) , diskBlock . Height )
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
cacheHeightDifficulty ( diskBlock . Main . Height )
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
diff , ok := getHeightDifficulty ( diskBlock . Main . Height )
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
if ok {
err = db . InsertBlock ( diskBlock , & diff )
} else {
err = db . InsertBlock ( diskBlock , nil )
}
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
if err == nil {
for _ , uncle := range uncles {
log . Printf ( "[CHAIN] Inserting uncle %s @ %s at %d" , uncle . Block . Main . Id . String ( ) , diskBlock . Id . String ( ) , diskBlock . Height )
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
diff , ok := getHeightDifficulty ( uncle . Block . Main . Height )
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
if ok {
err = db . InsertUncleBlock ( uncle , & diff )
} else {
err = db . InsertUncleBlock ( uncle , nil )
}
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
if uncle . Block . Main . Found {
log . Printf ( "[CHAIN] BLOCK FOUND! (uncle) Main height %d, main id %s" , uncle . Block . Main . Height , uncle . Block . Main . Id . String ( ) )
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
if b := tipUncles . Get ( uncle . Block . Id ) ; b != nil {
processFoundBlockWithTransaction ( api , uncle , b . Main . Coinbase )
}
2022-10-08 18:55:01 +00:00
}
}
2023-03-18 20:19:54 +00:00
knownTip = diskBlock . Height
}
2022-10-08 18:55:01 +00:00
2023-03-18 20:19:54 +00:00
if diskBlock . Main . Found {
log . Printf ( "[CHAIN] BLOCK FOUND! Main height %d, main id %s" , diskBlock . Main . Height , diskBlock . Main . Id . String ( ) )
if b := p2api . ByTemplateId ( diskBlock . Id ) ; b != nil {
processFoundBlockWithTransaction ( api , diskBlock , b . Main . Coinbase )
2022-10-08 18:55:01 +00:00
}
}
if runs % 10 == 0 { //Every 10 seconds or so
2022-10-11 10:39:16 +00:00
for foundBlock := range db . GetAllFound ( 10 , 0 ) {
2022-10-08 18:55:01 +00:00
//Scan last 10 found blocks and set status accordingly if found/not found
// Look between +1 block and +4 blocks
2023-03-18 20:19:54 +00:00
if ( p2tip . Main . Coinbase . GenHeight - 1 ) > foundBlock . GetBlock ( ) . Main . Height && ( p2tip . Main . Coinbase . GenHeight - 5 ) < foundBlock . GetBlock ( ) . Main . Height || db . GetCoinbaseTransaction ( foundBlock . GetBlock ( ) ) == nil {
2022-12-16 19:32:57 +00:00
if tx , _ := client . GetDefaultClient ( ) . GetCoinbaseTransaction ( foundBlock . GetBlock ( ) . Coinbase . Id ) ; tx == nil {
2022-10-08 18:55:01 +00:00
// If more than two minutes have passed before we get utxo, remove from found
log . Printf ( "[CHAIN] Block that was found at main height %d, cannot find output, marking not found\n" , foundBlock . GetBlock ( ) . Main . Height )
_ = db . SetBlockFound ( foundBlock . GetBlock ( ) . Id , false )
} else {
processFoundBlockWithTransaction ( api , foundBlock , tx )
}
}
}
}
if isFresh {
//TODO: Do migration tasks
isFresh = false
}
}
}