925 lines
27 KiB
Go
925 lines
27 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
|
_ "github.com/lib/pq"
|
|
"log"
|
|
"reflect"
|
|
"sync"
|
|
)
|
|
|
|
type Database struct {
|
|
handle *sql.DB
|
|
statements struct {
|
|
GetMinerById *sql.Stmt
|
|
GetMinerByAddress *sql.Stmt
|
|
GetMinerByAddressBounds *sql.Stmt
|
|
InsertMiner *sql.Stmt
|
|
}
|
|
|
|
cacheLock sync.RWMutex
|
|
minerCache map[uint64]*Miner
|
|
unclesByParentCache map[types.Hash][]*UncleBlock
|
|
}
|
|
|
|
func NewDatabase(connStr string) (db *Database, err error) {
|
|
db = &Database{
|
|
minerCache: make(map[uint64]*Miner, 1024),
|
|
unclesByParentCache: make(map[types.Hash][]*UncleBlock, p2pool.PPLNSWindow*16),
|
|
}
|
|
if db.handle, err = sql.Open("postgres", connStr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if db.statements.GetMinerById, err = db.handle.Prepare("SELECT id, address FROM miners WHERE id = $1;"); err != nil {
|
|
return nil, err
|
|
}
|
|
if db.statements.GetMinerByAddress, err = db.handle.Prepare("SELECT id, address FROM miners WHERE address = $1;"); err != nil {
|
|
return nil, err
|
|
}
|
|
if db.statements.GetMinerByAddressBounds, err = db.handle.Prepare("SELECT id, address FROM miners WHERE address LIKE $1 AND address LIKE $2;"); err != nil {
|
|
return nil, err
|
|
}
|
|
if db.statements.InsertMiner, err = db.handle.Prepare("INSERT INTO miners (address) VALUES ($1) RETURNING id, address;"); err != nil {
|
|
return nil, err
|
|
}
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
//cache methods
|
|
|
|
func (db *Database) GetMiner(miner uint64) *Miner {
|
|
if m := func() *Miner {
|
|
db.cacheLock.RLock()
|
|
defer db.cacheLock.RUnlock()
|
|
return db.minerCache[miner]
|
|
}(); m != nil {
|
|
return m
|
|
} else if m = db.getMiner(miner); m != nil {
|
|
db.cacheLock.Lock()
|
|
defer db.cacheLock.Unlock()
|
|
db.minerCache[miner] = m
|
|
return m
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (db *Database) GetUnclesByParentId(id types.Hash) chan *UncleBlock {
|
|
if c := func() chan *UncleBlock {
|
|
db.cacheLock.RLock()
|
|
defer db.cacheLock.RUnlock()
|
|
if s, ok := db.unclesByParentCache[id]; ok {
|
|
c := make(chan *UncleBlock, len(s))
|
|
defer close(c)
|
|
for _, u := range s {
|
|
c <- u
|
|
}
|
|
return c
|
|
}
|
|
return nil
|
|
}(); c != nil {
|
|
return c
|
|
} else if c = db.getUnclesByParentId(id); c != nil {
|
|
c2 := make(chan *UncleBlock)
|
|
go func() {
|
|
val := make([]*UncleBlock, 0, 6)
|
|
defer func() {
|
|
db.cacheLock.Lock()
|
|
defer db.cacheLock.Unlock()
|
|
if len(db.unclesByParentCache) >= p2pool.PPLNSWindow*16 {
|
|
for k := range db.unclesByParentCache {
|
|
delete(db.unclesByParentCache, k)
|
|
break
|
|
}
|
|
}
|
|
db.unclesByParentCache[id] = val
|
|
}()
|
|
defer close(c2)
|
|
for u := range c {
|
|
val = append(val, u)
|
|
c2 <- u
|
|
}
|
|
}()
|
|
return c2
|
|
} else {
|
|
return c
|
|
}
|
|
}
|
|
|
|
func (db *Database) getMiner(miner uint64) *Miner {
|
|
if rows, err := db.statements.GetMinerById.Query(miner); err != nil {
|
|
return nil
|
|
} else {
|
|
defer rows.Close()
|
|
if rows.Next() {
|
|
m := &Miner{}
|
|
if err = rows.Scan(&m.id, &m.addr); err != nil {
|
|
return nil
|
|
}
|
|
return m
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (db *Database) GetMinerByAddress(addr string) *Miner {
|
|
if rows, err := db.statements.GetMinerByAddress.Query(addr); err != nil {
|
|
return nil
|
|
} else {
|
|
defer rows.Close()
|
|
if rows.Next() {
|
|
m := &Miner{}
|
|
if err = rows.Scan(&m.id, &m.addr); err != nil {
|
|
return nil
|
|
}
|
|
return m
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (db *Database) GetOrCreateMinerByAddress(addr string) *Miner {
|
|
if m := db.GetMinerByAddress(addr); m != nil {
|
|
return m
|
|
} else {
|
|
if rows, err := db.statements.InsertMiner.Query(addr); err != nil {
|
|
return nil
|
|
} else {
|
|
defer rows.Close()
|
|
if rows.Next() {
|
|
m = &Miner{}
|
|
if err = rows.Scan(&m.id, &m.addr); err != nil {
|
|
return nil
|
|
}
|
|
return m
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (db *Database) GetMinerByAddressBounds(addrStart, addrEnd string) *Miner {
|
|
if rows, err := db.statements.GetMinerByAddress.Query(addrStart+"%", "%"+addrEnd); err != nil {
|
|
return nil
|
|
} else {
|
|
defer rows.Close()
|
|
if rows.Next() {
|
|
m := &Miner{}
|
|
if err = rows.Scan(&m.id, &m.addr); err != nil {
|
|
return nil
|
|
}
|
|
return m
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
type RowScanInterface interface {
|
|
Scan(dest ...any) error
|
|
}
|
|
|
|
func (db *Database) Query(query string, callback func(row RowScanInterface) error, params ...any) error {
|
|
if stmt, err := db.handle.Prepare(query); err != nil {
|
|
return err
|
|
} else {
|
|
defer stmt.Close()
|
|
|
|
return db.QueryStatement(stmt, callback, params...)
|
|
}
|
|
}
|
|
|
|
func (db *Database) QueryStatement(stmt *sql.Stmt, callback func(row RowScanInterface) error, params ...any) error {
|
|
if rows, err := stmt.Query(params...); err != nil {
|
|
return err
|
|
} else {
|
|
defer rows.Close()
|
|
for callback != nil && rows.Next() {
|
|
//callback will call sql.Rows.Scan
|
|
if err = callback(rows); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (db *Database) GetBlocksByQuery(where string, params ...any) chan *Block {
|
|
returnChannel := make(chan *Block)
|
|
go func() {
|
|
defer close(returnChannel)
|
|
err := db.Query(fmt.Sprintf("SELECT decode(id, 'hex'), height, decode(previous_id, 'hex'), decode(coinbase_id, 'hex'), coinbase_reward, decode(coinbase_privkey, 'hex'), difficulty, timestamp, miner, decode(pow_hash, 'hex'), main_height, decode(main_id, 'hex'), main_found, decode(miner_main_id, 'hex'), miner_main_difficulty FROM blocks %s;", where), func(row RowScanInterface) (err error) {
|
|
block := &Block{}
|
|
|
|
var difficultyHex, minerMainDifficultyHex string
|
|
|
|
var (
|
|
IdPtr = block.Id[:]
|
|
PreviousIdPtr = block.PreviousId[:]
|
|
CoinbaseIdPtr = block.Coinbase.Id[:]
|
|
CoinbasePrivateKeyPtr = block.Coinbase.PrivateKey[:]
|
|
PowHashPtr = block.PowHash[:]
|
|
MainIdPtr = block.Main.Id[:]
|
|
MinerMainId = block.Template.Id[:]
|
|
)
|
|
|
|
if err = row.Scan(&IdPtr, &block.Height, &PreviousIdPtr, &CoinbaseIdPtr, &block.Coinbase.Reward, &CoinbasePrivateKeyPtr, &difficultyHex, &block.Timestamp, &block.MinerId, &PowHashPtr, &block.Main.Height, &MainIdPtr, &block.Main.Found, &MinerMainId, &minerMainDifficultyHex); err != nil {
|
|
return err
|
|
}
|
|
|
|
copy(block.Id[:], IdPtr)
|
|
copy(block.PreviousId[:], PreviousIdPtr)
|
|
copy(block.Coinbase.Id[:], CoinbaseIdPtr)
|
|
copy(block.Coinbase.PrivateKey[:], CoinbasePrivateKeyPtr)
|
|
copy(block.PowHash[:], PowHashPtr)
|
|
copy(block.Main.Id[:], MainIdPtr)
|
|
copy(block.Template.Id[:], MinerMainId)
|
|
|
|
block.Difficulty, _ = types.DifficultyFromString(difficultyHex)
|
|
block.Template.Difficulty, _ = types.DifficultyFromString(minerMainDifficultyHex)
|
|
|
|
returnChannel <- block
|
|
|
|
return nil
|
|
}, params...)
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
}()
|
|
|
|
return returnChannel
|
|
}
|
|
|
|
func (db *Database) GetUncleBlocksByQuery(where string, params ...any) chan *UncleBlock {
|
|
returnChannel := make(chan *UncleBlock)
|
|
go func() {
|
|
defer close(returnChannel)
|
|
err := db.Query(fmt.Sprintf("SELECT decode(parent_id, 'hex'), parent_height, decode(id, 'hex'), height, decode(previous_id, 'hex'), decode(coinbase_id, 'hex'), coinbase_reward, decode(coinbase_privkey, 'hex'), difficulty, timestamp, miner, decode(pow_hash, 'hex'), main_height, decode(main_id, 'hex'), main_found, decode(miner_main_id, 'hex'), miner_main_difficulty FROM uncles %s;", where), func(row RowScanInterface) (err error) {
|
|
uncle := &UncleBlock{}
|
|
block := &uncle.Block
|
|
|
|
var difficultyHex, minerMainDifficultyHex string
|
|
|
|
var (
|
|
ParentId = uncle.ParentId[:]
|
|
IdPtr = block.Id[:]
|
|
PreviousIdPtr = block.PreviousId[:]
|
|
CoinbaseIdPtr = block.Coinbase.Id[:]
|
|
CoinbasePrivateKeyPtr = block.Coinbase.PrivateKey[:]
|
|
PowHashPtr = block.PowHash[:]
|
|
MainIdPtr = block.Main.Id[:]
|
|
MinerMainId = block.Template.Id[:]
|
|
)
|
|
|
|
if err = row.Scan(&ParentId, &uncle.ParentHeight, &IdPtr, &block.Height, &PreviousIdPtr, &CoinbaseIdPtr, &block.Coinbase.Reward, &CoinbasePrivateKeyPtr, &difficultyHex, &block.Timestamp, &block.MinerId, &PowHashPtr, &block.Main.Height, &MainIdPtr, &block.Main.Found, &MinerMainId, &minerMainDifficultyHex); err != nil {
|
|
return err
|
|
}
|
|
|
|
copy(uncle.ParentId[:], ParentId)
|
|
copy(block.Id[:], IdPtr)
|
|
copy(block.PreviousId[:], PreviousIdPtr)
|
|
copy(block.Coinbase.Id[:], CoinbaseIdPtr)
|
|
copy(block.Coinbase.PrivateKey[:], CoinbasePrivateKeyPtr)
|
|
copy(block.PowHash[:], PowHashPtr)
|
|
copy(block.Main.Id[:], MainIdPtr)
|
|
copy(block.Template.Id[:], MinerMainId)
|
|
|
|
block.Difficulty, _ = types.DifficultyFromString(difficultyHex)
|
|
block.Template.Difficulty, _ = types.DifficultyFromString(minerMainDifficultyHex)
|
|
|
|
returnChannel <- uncle
|
|
|
|
return nil
|
|
}, params...)
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
}()
|
|
|
|
return returnChannel
|
|
}
|
|
|
|
func (db *Database) GetBlockById(id types.Hash) *Block {
|
|
r := db.GetBlocksByQuery("WHERE id = $1;", id.String())
|
|
defer func() {
|
|
for range r {
|
|
|
|
}
|
|
}()
|
|
return <-r
|
|
}
|
|
|
|
func (db *Database) GetBlockByPreviousId(id types.Hash) *Block {
|
|
r := db.GetBlocksByQuery("WHERE previous_id = $1;", id.String())
|
|
defer func() {
|
|
for range r {
|
|
|
|
}
|
|
}()
|
|
return <-r
|
|
}
|
|
|
|
func (db *Database) GetBlockByHeight(height uint64) *Block {
|
|
r := db.GetBlocksByQuery("WHERE height = $1;", height)
|
|
defer func() {
|
|
for range r {
|
|
|
|
}
|
|
}()
|
|
return <-r
|
|
}
|
|
|
|
func (db *Database) GetBlocksInWindow(startHeight *uint64, windowSize uint64) chan *Block {
|
|
if windowSize == 0 {
|
|
windowSize = p2pool.PPLNSWindow
|
|
}
|
|
|
|
if startHeight == nil {
|
|
return db.GetBlocksByQuery("WHERE height > ((SELECT MAX(height) FROM blocks) - $1) AND height <= ((SELECT MAX(height) FROM blocks)) ORDER BY height DESC", windowSize)
|
|
} else {
|
|
return db.GetBlocksByQuery("WHERE height > ($1) AND height <= ($2) ORDER BY height DESC", *startHeight-windowSize, *startHeight)
|
|
}
|
|
}
|
|
|
|
func (db *Database) GetBlocksByMinerIdInWindow(minerId uint64, startHeight *uint64, windowSize uint64) chan *Block {
|
|
if windowSize == 0 {
|
|
windowSize = p2pool.PPLNSWindow
|
|
}
|
|
|
|
if startHeight == nil {
|
|
return db.GetBlocksByQuery("WHERE height > ((SELECT MAX(height) FROM blocks) - $2) AND height <= ((SELECT MAX(height) FROM blocks)) AND miner = $1 ORDER BY height DESC", minerId, windowSize)
|
|
} else {
|
|
return db.GetBlocksByQuery("WHERE height > ($2) AND height <= ($3) AND miner = $1 ORDER BY height DESC", minerId, *startHeight-windowSize, *startHeight)
|
|
}
|
|
}
|
|
|
|
func (db *Database) GetChainTip() *Block {
|
|
r := db.GetBlocksByQuery("ORDER BY height DESC LIMIT 1;")
|
|
defer func() {
|
|
for range r {
|
|
|
|
}
|
|
}()
|
|
return <-r
|
|
}
|
|
|
|
func (db *Database) GetLastFound() BlockInterface {
|
|
r := db.GetAllFound(1)
|
|
defer func() {
|
|
for range r {
|
|
|
|
}
|
|
}()
|
|
return <-r
|
|
}
|
|
|
|
func (db *Database) GetUncleById(id types.Hash) *UncleBlock {
|
|
r := db.GetUncleBlocksByQuery("WHERE id = $1;", id.String())
|
|
defer func() {
|
|
for range r {
|
|
|
|
}
|
|
}()
|
|
return <-r
|
|
}
|
|
|
|
func (db *Database) getUnclesByParentId(id types.Hash) chan *UncleBlock {
|
|
return db.GetUncleBlocksByQuery("WHERE parent_id = $1;", id.String())
|
|
}
|
|
|
|
func (db *Database) GetUnclesByParentHeight(height uint64) chan *UncleBlock {
|
|
return db.GetUncleBlocksByQuery("WHERE parent_height = $1;", height)
|
|
}
|
|
|
|
func (db *Database) GetUnclesInWindow(startHeight *uint64, windowSize uint64) chan *UncleBlock {
|
|
if windowSize == 0 {
|
|
windowSize = p2pool.PPLNSWindow
|
|
}
|
|
|
|
//TODO add p2pool.UncleBlockDepth ?
|
|
if startHeight == nil {
|
|
return db.GetUncleBlocksByQuery("WHERE parent_height > ((SELECT MAX(height) FROM blocks) - $1) AND parent_height <= ((SELECT MAX(height) FROM blocks)) AND height > ((SELECT MAX(height) FROM blocks) - $1) AND height <= ((SELECT MAX(height) FROM blocks)) ORDER BY height DESC", windowSize)
|
|
} else {
|
|
return db.GetUncleBlocksByQuery("WHERE parent_height > ($1) AND parent_height <= ($2) AND height > ($1) AND height <= ($2) ORDER BY height DESC", *startHeight-windowSize, *startHeight)
|
|
}
|
|
}
|
|
|
|
func (db *Database) GetUnclesByMinerIdInWindow(minerId uint64, startHeight *uint64, windowSize uint64) chan *UncleBlock {
|
|
if windowSize == 0 {
|
|
windowSize = p2pool.PPLNSWindow
|
|
}
|
|
|
|
//TODO add p2pool.UncleBlockDepth ?
|
|
if startHeight == nil {
|
|
return db.GetUncleBlocksByQuery("WHERE parent_height > ((SELECT MAX(height) FROM blocks) - $2) AND parent_height <= ((SELECT MAX(height) FROM blocks)) AND height > ((SELECT MAX(height) FROM blocks) - $2) AND height <= ((SELECT MAX(height) FROM blocks)) AND miner = $1 ORDER BY height DESC", minerId, windowSize)
|
|
} else {
|
|
return db.GetUncleBlocksByQuery("WHERE parent_height > ($3 - $2) AND parent_height <= ($3) AND height > ($2) AND height <= ($3) AND miner = $1 ORDER BY height DESC", minerId, *startHeight-windowSize, *startHeight)
|
|
}
|
|
}
|
|
|
|
func (db *Database) GetAllFound(limit uint64) chan BlockInterface {
|
|
blocks := db.GetFound(limit)
|
|
uncles := db.GetFoundUncles(limit)
|
|
|
|
result := make(chan BlockInterface)
|
|
go func() {
|
|
defer close(result)
|
|
defer func() {
|
|
for range blocks {
|
|
|
|
}
|
|
}()
|
|
defer func() {
|
|
for range uncles {
|
|
|
|
}
|
|
}()
|
|
|
|
var i uint64
|
|
|
|
var currentBlock *Block
|
|
var currentUncle *UncleBlock
|
|
|
|
for {
|
|
var current BlockInterface
|
|
|
|
if limit != 0 && i >= limit {
|
|
break
|
|
}
|
|
|
|
if currentBlock == nil {
|
|
currentBlock = <-blocks
|
|
}
|
|
if currentUncle == nil {
|
|
currentUncle = <-uncles
|
|
}
|
|
|
|
if currentBlock != nil {
|
|
if current == nil || currentBlock.Main.Height > current.GetBlock().Main.Height {
|
|
current = currentBlock
|
|
}
|
|
}
|
|
|
|
if currentUncle != nil {
|
|
if current == nil || currentUncle.Block.Main.Height > current.GetBlock().Main.Height {
|
|
current = currentUncle
|
|
}
|
|
}
|
|
|
|
if current == nil {
|
|
break
|
|
}
|
|
|
|
if currentBlock == current {
|
|
currentBlock = nil
|
|
} else if currentUncle == current {
|
|
currentUncle = nil
|
|
}
|
|
|
|
result <- current
|
|
|
|
i++
|
|
}
|
|
}()
|
|
return result
|
|
}
|
|
|
|
func (db *Database) GetShares(limit uint64, minerId uint64, onlyBlocks bool) chan BlockInterface {
|
|
var blocks chan *Block
|
|
var uncles chan *UncleBlock
|
|
|
|
if limit == 0 {
|
|
if minerId != 0 {
|
|
blocks = db.GetBlocksByQuery("WHERE miner = $1 ORDER BY height DESC;", limit, minerId)
|
|
if !onlyBlocks {
|
|
uncles = db.GetUncleBlocksByQuery("WHERE miner = $1ORDER BY height DESC, timestamp DESC;", minerId)
|
|
}
|
|
} else {
|
|
blocks = db.GetBlocksByQuery("ORDER BY height DESC;")
|
|
if !onlyBlocks {
|
|
uncles = db.GetUncleBlocksByQuery("ORDER BY height DESC, timestamp DESC;")
|
|
}
|
|
}
|
|
} else {
|
|
if minerId != 0 {
|
|
blocks = db.GetBlocksByQuery("WHERE miner = $2 ORDER BY height DESC LIMIT $1;", limit, minerId)
|
|
if !onlyBlocks {
|
|
uncles = db.GetUncleBlocksByQuery("WHERE miner = $2 ORDER BY height DESC, timestamp DESC LIMIT $1;", limit, minerId)
|
|
}
|
|
} else {
|
|
blocks = db.GetBlocksByQuery("ORDER BY height DESC LIMIT $1;", limit)
|
|
if !onlyBlocks {
|
|
uncles = db.GetUncleBlocksByQuery("ORDER BY height DESC, timestamp DESC LIMIT $1;", limit)
|
|
}
|
|
}
|
|
}
|
|
|
|
result := make(chan BlockInterface)
|
|
go func() {
|
|
defer close(result)
|
|
defer func() {
|
|
for range blocks {
|
|
|
|
}
|
|
}()
|
|
defer func() {
|
|
for range uncles {
|
|
|
|
}
|
|
}()
|
|
|
|
var i uint64
|
|
|
|
var currentBlock *Block
|
|
var currentUncle *UncleBlock
|
|
|
|
for {
|
|
var current BlockInterface
|
|
|
|
if limit != 0 && i >= limit {
|
|
break
|
|
}
|
|
|
|
if currentBlock == nil {
|
|
currentBlock = <-blocks
|
|
}
|
|
if !onlyBlocks && currentUncle == nil {
|
|
currentUncle = <-uncles
|
|
}
|
|
|
|
if currentBlock != nil {
|
|
if current == nil || currentBlock.Height > current.GetBlock().Height {
|
|
current = currentBlock
|
|
}
|
|
}
|
|
|
|
if !onlyBlocks && currentUncle != nil {
|
|
if current == nil || currentUncle.Block.Height > current.GetBlock().Height {
|
|
current = currentUncle
|
|
}
|
|
}
|
|
|
|
if current == nil {
|
|
break
|
|
}
|
|
|
|
if currentBlock == current {
|
|
currentBlock = nil
|
|
} else if !onlyBlocks && currentUncle == current {
|
|
currentUncle = nil
|
|
}
|
|
|
|
result <- current
|
|
|
|
i++
|
|
}
|
|
}()
|
|
return result
|
|
}
|
|
|
|
func (db *Database) GetFound(limit uint64) chan *Block {
|
|
if limit == 0 {
|
|
return db.GetBlocksByQuery("WHERE main_found IS TRUE ORDER BY main_height DESC;")
|
|
} else {
|
|
return db.GetBlocksByQuery("WHERE main_found IS TRUE ORDER BY main_height DESC LIMIT $1;", limit)
|
|
}
|
|
}
|
|
|
|
func (db *Database) GetFoundUncles(limit uint64) chan *UncleBlock {
|
|
if limit == 0 {
|
|
return db.GetUncleBlocksByQuery("WHERE main_found IS TRUE ORDER BY main_height DESC;")
|
|
} else {
|
|
return db.GetUncleBlocksByQuery("WHERE main_found IS TRUE ORDER BY main_height DESC LIMIT $1;", limit)
|
|
}
|
|
}
|
|
|
|
func (db *Database) DeleteBlockById(id types.Hash) (n int, err error) {
|
|
if block := db.GetBlockById(id); block == nil {
|
|
return 0, nil
|
|
} else {
|
|
for {
|
|
if err = db.Query("DELETE FROM coinbase_outputs WHERE id = (SELECT coinbase_id FROM blocks WHERE id = $1) OR id = (SELECT coinbase_id FROM uncles WHERE id = $1);", nil, id.String()); err != nil {
|
|
return n, err
|
|
} else if err = db.Query("DELETE FROM uncles WHERE parent_id = $1;", nil, id.String()); err != nil {
|
|
return n, err
|
|
} else if err = db.Query("DELETE FROM blocks WHERE id = $1;", nil, id.String()); err != nil {
|
|
return n, err
|
|
}
|
|
block = db.GetBlockByPreviousId(block.Id)
|
|
if block == nil {
|
|
return n, nil
|
|
}
|
|
n++
|
|
}
|
|
}
|
|
}
|
|
|
|
func (db *Database) SetBlockMainDifficulty(id types.Hash, difficulty types.Difficulty) error {
|
|
if err := db.Query("UPDATE blocks SET miner_main_difficulty = $2 WHERE id = $1;", nil, id.String(), difficulty.String()); err != nil {
|
|
return err
|
|
} else if err = db.Query("UPDATE uncles SET miner_main_difficulty = $2 WHERE id = $1;", nil, id.String(), difficulty.String()); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *Database) SetBlockFound(id types.Hash, found bool) error {
|
|
if err := db.Query("UPDATE blocks SET main_found = $2 WHERE id = $1;", nil, id.String(), found); err != nil {
|
|
return err
|
|
} else if err = db.Query("UPDATE uncles SET main_found = $2 WHERE id = $1;", nil, id.String(), found); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !found {
|
|
if err := db.Query("DELETE FROM coinbase_outputs WHERE id = (SELECT coinbase_id FROM blocks WHERE id = $1) OR id = (SELECT coinbase_id FROM uncles WHERE id = $1);", nil, id.String()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *Database) CoinbaseTransactionExists(block *Block) bool {
|
|
var count uint64
|
|
if err := db.Query("SELECT COUNT(*) as count FROM coinbase_outputs WHERE id = $1;", func(row RowScanInterface) error {
|
|
return row.Scan(&count)
|
|
}, block.Coinbase.Id.String()); err != nil {
|
|
return false
|
|
}
|
|
|
|
return count > 0
|
|
}
|
|
|
|
func (db *Database) GetCoinbaseTransaction(block *Block) *CoinbaseTransaction {
|
|
var outputs []*CoinbaseTransactionOutput
|
|
if err := db.Query("SELECT index, amount, miner FROM coinbase_outputs WHERE id = $1 ORDER BY index DESC;", func(row RowScanInterface) error {
|
|
output := &CoinbaseTransactionOutput{
|
|
id: block.Coinbase.Id,
|
|
}
|
|
|
|
if err := row.Scan(&output.index, &output.amount, &output.miner); err != nil {
|
|
return err
|
|
}
|
|
outputs = append(outputs, output)
|
|
return nil
|
|
}, block.Coinbase.Id.String()); err != nil {
|
|
return nil
|
|
}
|
|
|
|
return &CoinbaseTransaction{
|
|
id: block.Coinbase.Id,
|
|
privateKey: block.Coinbase.PrivateKey,
|
|
outputs: outputs,
|
|
}
|
|
}
|
|
|
|
func (db *Database) GetCoinbaseTransactionOutputByIndex(coinbaseId types.Hash, index uint64) *CoinbaseTransactionOutput {
|
|
output := &CoinbaseTransactionOutput{
|
|
id: coinbaseId,
|
|
index: index,
|
|
}
|
|
if err := db.Query("SELECT amount, miner FROM coinbase_outputs WHERE id = $1 AND index = $2 ORDER BY index DESC;", func(row RowScanInterface) error {
|
|
if err := row.Scan(&output.amount, &output.miner); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}, coinbaseId.String(), index); err != nil {
|
|
return nil
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
func (db *Database) GetCoinbaseTransactionOutputByMinerId(coinbaseId types.Hash, minerId uint64) *CoinbaseTransactionOutput {
|
|
output := &CoinbaseTransactionOutput{
|
|
id: coinbaseId,
|
|
miner: minerId,
|
|
}
|
|
if err := db.Query("SELECT amount, index FROM coinbase_outputs WHERE id = $1 AND miner = $2 ORDER BY index DESC;", func(row RowScanInterface) error {
|
|
if err := row.Scan(&output.amount, &output.index); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}, coinbaseId.String(), minerId); err != nil {
|
|
return nil
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
type Payout struct {
|
|
Id types.Hash `json:"id"`
|
|
Height uint64 `json:"height"`
|
|
Main struct {
|
|
Id types.Hash `json:"id"`
|
|
Height uint64 `json:"height"`
|
|
} `json:"main"`
|
|
Timestamp uint64 `json:"timestamp"`
|
|
Uncle bool `json:"uncle"`
|
|
Coinbase struct {
|
|
Id types.Hash `json:"id"`
|
|
Reward uint64 `json:"reward"`
|
|
PrivateKey types.Hash `json:"private_key"`
|
|
Miner uint64 `json:"miner"`
|
|
Index uint64 `json:"index"`
|
|
} `json:"coinbase"`
|
|
}
|
|
|
|
func (db *Database) GetPayoutsByMinerId(minerId uint64, limit uint64) chan *Payout {
|
|
out := make(chan *Payout)
|
|
|
|
go func() {
|
|
defer close(out)
|
|
resultFunc := func(row RowScanInterface) error {
|
|
var blockId, mainId, privKey, coinbaseId []byte
|
|
var height, mainHeight, timestamp, amount, index uint64
|
|
var uncle bool
|
|
|
|
if err := row.Scan(&blockId, &mainId, &height, &mainHeight, ×tamp, &privKey, &uncle, &coinbaseId, &amount, &index); err != nil {
|
|
return err
|
|
}
|
|
|
|
out <- &Payout{
|
|
Id: types.HashFromBytes(blockId),
|
|
Height: height,
|
|
Timestamp: timestamp,
|
|
Main: struct {
|
|
Id types.Hash `json:"id"`
|
|
Height uint64 `json:"height"`
|
|
}{Id: types.HashFromBytes(mainId), Height: mainHeight},
|
|
Uncle: uncle,
|
|
Coinbase: struct {
|
|
Id types.Hash `json:"id"`
|
|
Reward uint64 `json:"reward"`
|
|
PrivateKey types.Hash `json:"private_key"`
|
|
Miner uint64 `json:"miner"`
|
|
Index uint64 `json:"index"`
|
|
}{Id: types.HashFromBytes(coinbaseId), Reward: amount, PrivateKey: types.HashFromBytes(privKey), Index: index, Miner: minerId},
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if limit == 0 {
|
|
if err := db.Query("SELECT decode(b.id, 'hex') AS id, decode(b.main_id, 'hex') AS main_id, b.height AS height, b.main_height AS main_height, b.timestamp AS timestamp, decode(b.coinbase_privkey, 'hex') AS coinbase_privkey, b.uncle AS uncle, decode(o.id, 'hex') AS coinbase_id, o.amount AS amount, o.index AS index FROM (SELECT id, amount, index FROM coinbase_outputs WHERE miner = $1) o LEFT JOIN LATERAL\n(SELECT id, coinbase_id, coinbase_privkey, height, main_height, main_id, timestamp, FALSE AS uncle FROM blocks WHERE coinbase_id = o.id UNION SELECT id, coinbase_id, coinbase_privkey, height, main_height, main_id, timestamp, TRUE AS uncle FROM uncles WHERE coinbase_id = o.id) b ON b.coinbase_id = o.id ORDER BY main_height DESC;", resultFunc, minerId); err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
if err := db.Query("SELECT decode(b.id, 'hex') AS id, decode(b.main_id, 'hex') AS main_id, b.height AS height, b.main_height AS main_height, b.timestamp AS timestamp, decode(b.coinbase_privkey, 'hex') AS coinbase_privkey, b.uncle AS uncle, decode(o.id, 'hex') AS coinbase_id, o.amount AS amount, o.index AS index FROM (SELECT id, amount, index FROM coinbase_outputs WHERE miner = $1) o LEFT JOIN LATERAL\n(SELECT id, coinbase_id, coinbase_privkey, height, main_height, main_id, timestamp, FALSE AS uncle FROM blocks WHERE coinbase_id = o.id UNION SELECT id, coinbase_id, coinbase_privkey, height, main_height, main_id, timestamp, TRUE AS uncle FROM uncles WHERE coinbase_id = o.id) b ON b.coinbase_id = o.id ORDER BY main_height DESC LIMIT $2;", resultFunc, minerId, limit); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return out
|
|
}
|
|
|
|
func (db *Database) InsertCoinbaseTransaction(coinbase *CoinbaseTransaction) error {
|
|
if tx, err := db.handle.BeginTx(context.Background(), nil); err != nil {
|
|
return err
|
|
} else if stmt, err := tx.Prepare("INSERT INTO coinbase_outputs (id, index, miner, amount) VALUES ($1, $2, $3, $4);"); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
} else {
|
|
defer stmt.Close()
|
|
for _, o := range coinbase.Outputs() {
|
|
if rows, err := stmt.Query(o.Id().String(), o.Index(), o.Miner(), o.Amount()); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
} else {
|
|
if err = rows.Close(); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
}
|
|
|
|
func (db *Database) InsertBlock(b *Block, fallbackDifficulty *types.Difficulty) error {
|
|
|
|
block := db.GetBlockById(b.Id)
|
|
mainDiff := b.Template.Difficulty
|
|
if mainDiff == UndefinedDifficulty && fallbackDifficulty != nil {
|
|
mainDiff = *fallbackDifficulty
|
|
}
|
|
|
|
if block != nil { //Update found status if existent
|
|
if block.Template.Difficulty != mainDiff && mainDiff != UndefinedDifficulty {
|
|
if err := db.SetBlockMainDifficulty(block.Id, mainDiff); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if b.Main.Found && !block.Main.Found {
|
|
if err := db.SetBlockFound(block.Id, true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return db.Query(
|
|
"INSERT INTO blocks (id, height, previous_id, coinbase_id, coinbase_reward, coinbase_privkey, difficulty, timestamp, miner, pow_hash, main_height, main_id, main_found, miner_main_id, miner_main_difficulty) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15);",
|
|
nil,
|
|
b.Id.String(),
|
|
b.Height,
|
|
b.PreviousId.String(),
|
|
b.Coinbase.Id.String(),
|
|
b.Coinbase.Reward,
|
|
b.Coinbase.PrivateKey.String(),
|
|
b.Difficulty.String(),
|
|
b.Timestamp,
|
|
b.MinerId,
|
|
b.PowHash.String(),
|
|
b.Main.Height,
|
|
b.Main.Id.String(),
|
|
b.Main.Found,
|
|
b.Template.Id.String(),
|
|
b.Template.Difficulty.String(),
|
|
)
|
|
}
|
|
|
|
func (db *Database) InsertUncleBlock(u *UncleBlock, fallbackDifficulty *types.Difficulty) error {
|
|
|
|
if b := db.GetBlockById(u.ParentId); b == nil {
|
|
return errors.New("parent does not exist")
|
|
}
|
|
|
|
uncle := db.GetUncleById(u.Block.Id)
|
|
|
|
mainDiff := u.Block.Template.Difficulty
|
|
if mainDiff == UndefinedDifficulty && fallbackDifficulty != nil {
|
|
mainDiff = *fallbackDifficulty
|
|
}
|
|
|
|
if uncle != nil { //Update found status if existent
|
|
if uncle.Block.Template.Difficulty != mainDiff && mainDiff != UndefinedDifficulty {
|
|
if err := db.SetBlockMainDifficulty(u.Block.Id, mainDiff); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if u.Block.Main.Found && !uncle.Block.Main.Found {
|
|
if err := db.SetBlockFound(u.Block.Id, true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return db.Query(
|
|
"INSERT INTO uncles (parent_id, parent_height, id, height, previous_id, coinbase_id, coinbase_reward, coinbase_privkey, difficulty, timestamp, miner, pow_hash, main_height, main_id, main_found, miner_main_id, miner_main_difficulty) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17);",
|
|
nil,
|
|
u.ParentId.String(),
|
|
u.ParentHeight,
|
|
u.Block.Id.String(),
|
|
u.Block.Height,
|
|
u.Block.PreviousId.String(),
|
|
u.Block.Coinbase.Id.String(),
|
|
u.Block.Coinbase.Reward,
|
|
u.Block.Coinbase.PrivateKey.String(),
|
|
u.Block.Difficulty.String(),
|
|
u.Block.Timestamp,
|
|
u.Block.MinerId,
|
|
u.Block.PowHash.String(),
|
|
u.Block.Main.Height,
|
|
u.Block.Main.Id.String(),
|
|
u.Block.Main.Found,
|
|
u.Block.Template.Id.String(),
|
|
u.Block.Template.Difficulty.String(),
|
|
)
|
|
}
|
|
|
|
func (db *Database) Close() error {
|
|
|
|
//cleanup statements
|
|
v := reflect.ValueOf(db.statements)
|
|
for i := 0; i < v.NumField(); i++ {
|
|
if stmt, ok := v.Field(i).Interface().(*sql.Stmt); ok && stmt != nil {
|
|
//v.Field(i).Elem().Set(reflect.ValueOf((*sql.Stmt)(nil)))
|
|
stmt.Close()
|
|
}
|
|
}
|
|
|
|
return db.handle.Close()
|
|
}
|