consensus/cmd/daemon/daemon.go

325 lines
10 KiB
Go

package main
import (
"flag"
"git.gammaspectra.live/P2Pool/p2pool-observer/database"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
"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"
"log"
"os"
"time"
)
func blockId(b *sidechain.PoolBlock) types.Hash {
return types.HashFromBytes(b.CoinbaseExtra(sidechain.SideTemplateId))
}
func main() {
startFromHeight := flag.Uint64("from", 0, "Start sync from this height")
dbString := flag.String("db", "", "")
p2poolApiHost := flag.String("api-host", "", "Host URL for p2pool go observer consensus")
flag.Parse()
client.SetDefaultClientSettings(os.Getenv("MONEROD_RPC_URL"))
db, err := database.NewDatabase(*dbString)
if err != nil {
log.Panic(err)
}
defer db.Close()
p2api := p2poolapi.NewP2PoolApi(*p2poolApiHost)
api, err := p2poolapi.New(db, p2api)
if err != nil {
log.Panic(err)
}
//TODO: force-insert section
tip := db.GetChainTip()
isFresh := tip == nil
var tipHeight uint64
if tip != nil {
tipHeight = tip.Height
}
log.Printf("[CHAIN] Last known database tip is %d\n", tipHeight)
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)
}
log.Printf("[CHAIN] Consensus id = %s\n", p2api.Consensus().Id())
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
}
}
tipChain, tipUncles := p2api.StateFromTip()
if len(tipChain) == 0 || len(tipChain) < int(p2api.Consensus().ChainWindowSize*2) {
log.Panicf("[CHAIN] Too small length %d", len(tipChain))
}
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
if *startFromHeight != 0 {
log.Printf("[CHAIN] Forcing start tip to %d\n", *startFromHeight)
startFrom = *startFromHeight
}
if isFresh || startFrom != p2poolTip {
if startFrom > p2poolTip {
startFrom = p2poolTip
}
if isFresh {
currentIndex := int(p2poolTip - startFrom)
if currentIndex > (len(tipChain) - 1) {
currentIndex = len(tipChain) - 1
}
for ; currentIndex >= 0; currentIndex-- {
b := tipChain[currentIndex]
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 {
log.Panic(err)
}
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)
}
}
}
startFrom = b.Side.Height
}
}
}
//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())
if tx, _ := client.GetDefaultClient().GetCoinbaseTransaction(b.Coinbase.Id); tx != nil {
_ = 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())
if tx, _ := client.GetDefaultClient().GetCoinbaseTransaction(u.Block.Coinbase.Id); tx != nil {
_ = db.SetBlockFound(u.Block.Id, true)
processFoundBlockWithTransaction(api, u, tx)
}
}
}
}
var chain sidechain.UniquePoolBlockSlice
for {
toStart:
if len(chain) == 0 {
time.Sleep(time.Second * 1)
}
runs++
var p2tip *sidechain.PoolBlock
if len(chain) > 0 {
p2tip = chain[len(chain)-1]
chain = chain[:len(chain)-1]
} else {
p2tip = p2api.Tip()
}
if p2tip == nil {
log.Panicf("[CHAIN] could not find tip, at %d", knownTip)
}
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-- {
chain = append(chain, p2tip)
p2tip = p2api.ByTemplateId(p2tip.Side.Parent)
}
}
if p2tip == nil {
log.Panicf("[CHAIN] could not find tip after depth, at %d", knownTip)
}
dbTip := db.GetBlockByHeight(knownTip)
if dbTip != nil && dbTip.Id == blockId(p2tip) { // no changes
continue
}
if dbTip != nil && dbTip.Id != p2tip.Side.Parent { //Reorg has happened, delete old values
log.Printf("[REORG] Reorg happened, deleting blocks to match from height %d.\n", dbTip.Height)
diskBlock := p2tip
for h := knownTip; h > 0; h-- {
dbBlock := db.GetBlockByHeight(h)
diskBlock = p2api.ByTemplateId(diskBlock.Side.Parent)
if dbBlock.Id == blockId(diskBlock) {
log.Printf("[REORG] Found matching head %s at height %d\n", dbBlock.PreviousId.String(), dbBlock.Height-1)
deleted, err := db.DeleteBlockById(dbBlock.Id)
if err != nil {
log.Panic(err)
}
log.Printf("[REORG] Deleted %d shares(s).\n", deleted)
log.Printf("[REORG] Next tip %s : %d.\n", blockId(diskBlock), diskBlock.Side.Height)
knownTip = dbBlock.Height - 1
break
}
}
continue
}
tipUncles = tipUncles[:0]
for _, uncleId := range p2tip.Side.Uncles {
if u := p2api.ByTemplateId(uncleId); u == nil {
goto toStart
} else {
tipUncles = append(tipUncles, u)
}
}
diskBlock, uncles, err := database.NewBlockFromBinaryBlock(getSeedByHeight, p2api.MainDifficultyByHeight, db, p2tip, tipUncles, true)
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
}
prevBlock := db.GetBlockByHeight(p2tip.Side.Height - 1)
if prevBlock != nil && diskBlock.PreviousId != prevBlock.Id {
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
}
log.Printf("[CHAIN] Inserting share %s at height %d\n", diskBlock.Id.String(), diskBlock.Height)
cacheHeightDifficulty(diskBlock.Main.Height)
diff, ok := getHeightDifficulty(diskBlock.Main.Height)
if ok {
err = db.InsertBlock(diskBlock, &diff)
} else {
err = db.InsertBlock(diskBlock, nil)
}
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)
diff, ok := getHeightDifficulty(uncle.Block.Main.Height)
if ok {
err = db.InsertUncleBlock(uncle, &diff)
} else {
err = db.InsertUncleBlock(uncle, nil)
}
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())
if b := tipUncles.Get(uncle.Block.Id); b != nil {
processFoundBlockWithTransaction(api, uncle, b.Main.Coinbase)
}
}
}
knownTip = diskBlock.Height
}
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)
}
}
if runs%10 == 0 { //Every 10 seconds or so
for foundBlock := range db.GetAllFound(10, 0) {
//Scan last 10 found blocks and set status accordingly if found/not found
// Look between +1 block and +4 blocks
if (p2tip.Main.Coinbase.GenHeight-1) > foundBlock.GetBlock().Main.Height && (p2tip.Main.Coinbase.GenHeight-5) < foundBlock.GetBlock().Main.Height || db.GetCoinbaseTransaction(foundBlock.GetBlock()) == nil {
if tx, _ := client.GetDefaultClient().GetCoinbaseTransaction(foundBlock.GetBlock().Coinbase.Id); tx == nil {
// 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
}
}
}