474 lines
14 KiB
Go
474 lines
14 KiB
Go
package database
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
|
p2poolBlock "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/block"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
|
"github.com/holiman/uint256"
|
|
"golang.org/x/exp/slices"
|
|
"lukechampine.com/uint128"
|
|
"math/bits"
|
|
"sync"
|
|
)
|
|
|
|
var NilHash types.Hash
|
|
var UndefinedHash types.Hash
|
|
var UndefinedDifficulty types.Difficulty
|
|
|
|
func init() {
|
|
copy(NilHash[:], bytes.Repeat([]byte{0}, types.HashSize))
|
|
copy(UndefinedHash[:], bytes.Repeat([]byte{0xff}, types.HashSize))
|
|
UndefinedDifficulty.Uint128 = uint128.FromBytes(bytes.Repeat([]byte{0xff}, types.DifficultySize))
|
|
}
|
|
|
|
type BlockInterface interface {
|
|
GetBlock() *Block
|
|
}
|
|
|
|
type BlockCoinbase struct {
|
|
Id types.Hash `json:"id"`
|
|
Reward uint64 `json:"reward"`
|
|
PrivateKey types.Hash `json:"private_key"`
|
|
//Payouts extra JSON field, do not use
|
|
Payouts []*JSONCoinbaseOutput `json:"payouts,omitempty"`
|
|
}
|
|
|
|
type BlockMainData struct {
|
|
Id types.Hash `json:"id"`
|
|
Height uint64 `json:"height"`
|
|
Found bool `json:"found"`
|
|
|
|
//Orphan extra JSON field, do not use
|
|
Orphan bool `json:"orphan,omitempty"`
|
|
}
|
|
|
|
type JSONBlockParent struct {
|
|
Id types.Hash `json:"id"`
|
|
Height uint64 `json:"height"`
|
|
}
|
|
|
|
type JSONUncleBlockSimple struct {
|
|
Id types.Hash `json:"id"`
|
|
Height uint64 `json:"height"`
|
|
Weight uint64 `json:"weight"`
|
|
}
|
|
|
|
type JSONCoinbaseOutput struct {
|
|
Amount uint64 `json:"amount"`
|
|
Index uint64 `json:"index"`
|
|
Address string `json:"address"`
|
|
}
|
|
|
|
type JSONUncleBlockExtra struct {
|
|
Id types.Hash `json:"id"`
|
|
Height uint64 `json:"height"`
|
|
Difficulty types.Difficulty `json:"difficulty"`
|
|
Timestamp uint64 `json:"timestamp"`
|
|
Miner string `json:"miner"`
|
|
PowHash types.Hash `json:"pow"`
|
|
Weight uint64 `json:"weight"`
|
|
}
|
|
|
|
type Block struct {
|
|
Id types.Hash `json:"id"`
|
|
Height uint64 `json:"height"`
|
|
PreviousId types.Hash `json:"previous_id"`
|
|
Coinbase BlockCoinbase `json:"coinbase"`
|
|
Difficulty types.Difficulty `json:"difficulty"`
|
|
Timestamp uint64 `json:"timestamp"`
|
|
MinerId uint64 `json:"-"`
|
|
//Address extra JSON field, do not use
|
|
Address string `json:"miner,omitempty"`
|
|
PowHash types.Hash `json:"pow"`
|
|
|
|
Main BlockMainData `json:"main"`
|
|
|
|
Template struct {
|
|
Id types.Hash `json:"id"`
|
|
Difficulty types.Difficulty `json:"difficulty"`
|
|
} `json:"template"`
|
|
|
|
//Lock extra JSON field, do not use
|
|
Lock sync.Mutex `json:"-"`
|
|
|
|
//Weight extra JSON field, do not use
|
|
Weight uint64 `json:"weight"`
|
|
//Parent extra JSON field, do not use
|
|
Parent *JSONBlockParent `json:"parent,omitempty"`
|
|
//Uncles extra JSON field, do not use
|
|
Uncles []any `json:"uncles,omitempty"`
|
|
|
|
//Orphan extra JSON field, do not use
|
|
Orphan bool `json:"orphan,omitempty"`
|
|
|
|
//Invalid extra JSON field, do not use
|
|
Invalid *bool `json:"invalid,omitempty"`
|
|
}
|
|
|
|
func NewBlockFromBinaryBlock(db *Database, b *p2poolBlock.Block, knownUncles []*p2poolBlock.Block, errOnUncles bool) (block *Block, uncles []*UncleBlock, err error) {
|
|
miner := db.GetOrCreateMinerByAddress(b.GetAddress().ToBase58())
|
|
if miner == nil {
|
|
return nil, nil, errors.New("could not get or create miner")
|
|
}
|
|
|
|
block = &Block{
|
|
Id: b.Main.CoinbaseExtra.SideId,
|
|
Height: b.Side.Height,
|
|
PreviousId: b.Side.Parent,
|
|
Coinbase: BlockCoinbase{
|
|
Id: b.Main.Coinbase.Id(),
|
|
Reward: func() (v uint64) {
|
|
for _, o := range b.Main.Coinbase.Outputs {
|
|
v += o.Reward
|
|
}
|
|
return
|
|
}(),
|
|
PrivateKey: b.Side.CoinbasePrivateKey,
|
|
},
|
|
Difficulty: b.Side.Difficulty,
|
|
Timestamp: b.Main.Timestamp,
|
|
MinerId: miner.Id(),
|
|
PowHash: b.Extra.PowHash,
|
|
Main: BlockMainData{
|
|
Id: b.Extra.MainId,
|
|
Height: b.Main.Coinbase.GenHeight,
|
|
Found: b.IsProofHigherThanDifficulty(),
|
|
},
|
|
Template: struct {
|
|
Id types.Hash `json:"id"`
|
|
Difficulty types.Difficulty `json:"difficulty"`
|
|
}{
|
|
Id: b.Main.Parent,
|
|
Difficulty: b.Extra.MainDifficulty,
|
|
},
|
|
}
|
|
|
|
for _, u := range b.Side.Uncles {
|
|
if i := slices.IndexFunc(knownUncles, func(uncle *p2poolBlock.Block) bool {
|
|
return uncle.Main.CoinbaseExtra.SideId == u
|
|
}); i != -1 {
|
|
uncle := knownUncles[i]
|
|
uncleMiner := db.GetOrCreateMinerByAddress(uncle.GetAddress().ToBase58())
|
|
if uncleMiner == nil {
|
|
return nil, nil, errors.New("could not get or create miner")
|
|
}
|
|
uncles = append(uncles, &UncleBlock{
|
|
Block: Block{
|
|
Id: uncle.Main.CoinbaseExtra.SideId,
|
|
Height: uncle.Side.Height,
|
|
PreviousId: uncle.Side.Parent,
|
|
Coinbase: BlockCoinbase{
|
|
Id: uncle.Main.Coinbase.Id(),
|
|
Reward: func() (v uint64) {
|
|
for _, o := range uncle.Main.Coinbase.Outputs {
|
|
v += o.Reward
|
|
}
|
|
return
|
|
}(),
|
|
PrivateKey: uncle.Side.CoinbasePrivateKey,
|
|
},
|
|
Difficulty: uncle.Side.Difficulty,
|
|
Timestamp: uncle.Main.Timestamp,
|
|
MinerId: uncleMiner.Id(),
|
|
PowHash: uncle.Extra.PowHash,
|
|
Main: BlockMainData{
|
|
Id: uncle.Extra.MainId,
|
|
Height: uncle.Main.Coinbase.GenHeight,
|
|
Found: uncle.IsProofHigherThanDifficulty(),
|
|
},
|
|
Template: struct {
|
|
Id types.Hash `json:"id"`
|
|
Difficulty types.Difficulty `json:"difficulty"`
|
|
}{
|
|
Id: uncle.Main.Parent,
|
|
Difficulty: uncle.Extra.MainDifficulty,
|
|
},
|
|
},
|
|
ParentId: block.Id,
|
|
ParentHeight: block.Height,
|
|
})
|
|
} else if errOnUncles {
|
|
return nil, nil, fmt.Errorf("could not find uncle %s", hex.EncodeToString(u[:]))
|
|
}
|
|
}
|
|
|
|
return block, uncles, nil
|
|
}
|
|
|
|
type JsonBlock2 struct {
|
|
MinerMainId types.Hash `json:"miner_main_id"`
|
|
CoinbaseReward uint64 `json:"coinbase_reward,string"`
|
|
CoinbaseId types.Hash `json:"coinbase_id"`
|
|
Version uint64 `json:"version,string"`
|
|
Diff types.Difficulty `json:"diff"`
|
|
Wallet *address.Address `json:"wallet"`
|
|
MinerMainDiff types.Difficulty `json:"miner_main_diff"`
|
|
Id types.Hash `json:"id"`
|
|
Height uint64 `json:"height,string"`
|
|
PowHash types.Hash `json:"pow_hash"`
|
|
MainId types.Hash `json:"main_id"`
|
|
MainHeight uint64 `json:"main_height,string"`
|
|
Ts uint64 `json:"ts,string"`
|
|
PrevId types.Hash `json:"prev_id"`
|
|
CoinbasePriv types.Hash `json:"coinbase_priv"`
|
|
Lts uint64 `json:"lts,string"`
|
|
MainFound string `json:"main_found,omitempty"`
|
|
|
|
Uncles []struct {
|
|
MinerMainId types.Hash `json:"miner_main_id"`
|
|
CoinbaseReward uint64 `json:"coinbase_reward,string"`
|
|
CoinbaseId types.Hash `json:"coinbase_id"`
|
|
Version uint64 `json:"version,string"`
|
|
Diff types.Difficulty `json:"diff"`
|
|
Wallet *address.Address `json:"wallet"`
|
|
MinerMainDiff types.Difficulty `json:"miner_main_diff"`
|
|
Id types.Hash `json:"id"`
|
|
Height uint64 `json:"height,string"`
|
|
PowHash types.Hash `json:"pow_hash"`
|
|
MainId types.Hash `json:"main_id"`
|
|
MainHeight uint64 `json:"main_height,string"`
|
|
Ts uint64 `json:"ts,string"`
|
|
PrevId types.Hash `json:"prev_id"`
|
|
CoinbasePriv types.Hash `json:"coinbase_priv"`
|
|
Lts uint64 `json:"lts,string"`
|
|
MainFound string `json:"main_found,omitempty"`
|
|
} `json:"uncles,omitempty"`
|
|
}
|
|
|
|
type JsonBlock1 struct {
|
|
Wallet *address.Address `json:"wallet"`
|
|
Height uint64 `json:"height,string"`
|
|
MHeight uint64 `json:"mheight,string"`
|
|
PrevId types.Hash `json:"prev_id"`
|
|
Ts uint64 `json:"ts,string"`
|
|
PowHash types.Hash `json:"pow_hash"`
|
|
Id types.Hash `json:"id"`
|
|
PrevHash types.Hash `json:"prev_hash"`
|
|
Diff uint64 `json:"diff,string"`
|
|
TxCoinbase types.Hash `json:"tx_coinbase"`
|
|
Lts uint64 `json:"lts,string"`
|
|
MHash types.Hash `json:"mhash"`
|
|
TxPriv types.Hash `json:"tx_priv"`
|
|
TxPub types.Hash `json:"tx_pub"`
|
|
BlockFound string `json:"main_found,omitempty"`
|
|
|
|
Uncles []struct {
|
|
Diff uint64 `json:"diff,string"`
|
|
PrevId types.Hash `json:"prev_id"`
|
|
Ts uint64 `json:"ts,string"`
|
|
MHeight uint64 `json:"mheight,string"`
|
|
PrevHash types.Hash `json:"prev_hash"`
|
|
Height uint64 `json:"height,string"`
|
|
Wallet *address.Address `json:"wallet"`
|
|
Id types.Hash `json:"id"`
|
|
} `json:"uncles,omitempty"`
|
|
}
|
|
|
|
type versionBlock struct {
|
|
Version uint64 `json:"version,string"`
|
|
}
|
|
|
|
func NewBlockFromJSONBlock(db *Database, data []byte) (block *Block, uncles []*UncleBlock, err error) {
|
|
var version versionBlock
|
|
|
|
if err = json.Unmarshal(data, &version); err != nil {
|
|
return nil, nil, err
|
|
} else {
|
|
if version.Version == 2 {
|
|
|
|
var b JsonBlock2
|
|
if err = json.Unmarshal(data, &b); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
miner := db.GetOrCreateMinerByAddress(b.Wallet.ToBase58())
|
|
if miner == nil {
|
|
return nil, nil, errors.New("could not get or create miner")
|
|
}
|
|
|
|
block = &Block{
|
|
Id: b.Id,
|
|
Height: b.Height,
|
|
PreviousId: b.PrevId,
|
|
Coinbase: BlockCoinbase{
|
|
Id: b.CoinbaseId,
|
|
Reward: b.CoinbaseReward,
|
|
PrivateKey: b.CoinbasePriv,
|
|
},
|
|
Difficulty: b.Diff,
|
|
Timestamp: b.Ts,
|
|
MinerId: miner.Id(),
|
|
PowHash: b.PowHash,
|
|
Main: BlockMainData{
|
|
Id: b.MainId,
|
|
Height: b.MainHeight,
|
|
Found: b.MainFound == "true",
|
|
},
|
|
Template: struct {
|
|
Id types.Hash `json:"id"`
|
|
Difficulty types.Difficulty `json:"difficulty"`
|
|
}{
|
|
Id: b.MinerMainId,
|
|
Difficulty: b.MinerMainDiff,
|
|
},
|
|
}
|
|
|
|
if block.IsProofHigherThanDifficulty() {
|
|
block.Main.Found = true
|
|
}
|
|
|
|
for _, u := range b.Uncles {
|
|
uncleMiner := db.GetOrCreateMinerByAddress(u.Wallet.ToBase58())
|
|
if uncleMiner == nil {
|
|
return nil, nil, errors.New("could not get or create miner")
|
|
}
|
|
|
|
uncle := &UncleBlock{
|
|
Block: Block{
|
|
Id: u.Id,
|
|
Height: u.Height,
|
|
PreviousId: u.PrevId,
|
|
Coinbase: BlockCoinbase{
|
|
Id: u.CoinbaseId,
|
|
Reward: u.CoinbaseReward,
|
|
PrivateKey: u.CoinbasePriv,
|
|
},
|
|
Difficulty: u.Diff,
|
|
Timestamp: u.Ts,
|
|
MinerId: uncleMiner.Id(),
|
|
PowHash: u.PowHash,
|
|
Main: BlockMainData{
|
|
Id: u.MainId,
|
|
Height: u.MainHeight,
|
|
Found: u.MainFound == "true",
|
|
},
|
|
Template: struct {
|
|
Id types.Hash `json:"id"`
|
|
Difficulty types.Difficulty `json:"difficulty"`
|
|
}{
|
|
Id: u.MinerMainId,
|
|
Difficulty: u.MinerMainDiff,
|
|
},
|
|
},
|
|
ParentId: block.Id,
|
|
ParentHeight: block.Height,
|
|
}
|
|
|
|
if uncle.Block.IsProofHigherThanDifficulty() {
|
|
uncle.Block.Main.Found = true
|
|
}
|
|
|
|
uncles = append(uncles, uncle)
|
|
}
|
|
return block, uncles, nil
|
|
} else if version.Version == 0 || version.Version == 1 {
|
|
|
|
var b JsonBlock1
|
|
if err = json.Unmarshal(data, &b); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
miner := db.GetOrCreateMinerByAddress(b.Wallet.ToBase58())
|
|
if miner == nil {
|
|
return nil, nil, errors.New("could not get or create miner")
|
|
}
|
|
|
|
block = &Block{
|
|
Id: b.Id,
|
|
Height: b.Height,
|
|
PreviousId: b.PrevId,
|
|
Coinbase: BlockCoinbase{
|
|
Id: b.TxCoinbase,
|
|
Reward: 0,
|
|
PrivateKey: b.TxPriv,
|
|
},
|
|
Difficulty: types.Difficulty{Uint128: uint128.From64(b.Diff)},
|
|
Timestamp: b.Ts,
|
|
MinerId: miner.Id(),
|
|
PowHash: b.PowHash,
|
|
Main: BlockMainData{
|
|
Id: b.MHash,
|
|
Height: b.MHeight,
|
|
Found: b.BlockFound == "true",
|
|
},
|
|
Template: struct {
|
|
Id types.Hash `json:"id"`
|
|
Difficulty types.Difficulty `json:"difficulty"`
|
|
}{
|
|
Id: UndefinedHash,
|
|
Difficulty: UndefinedDifficulty,
|
|
},
|
|
}
|
|
|
|
for _, u := range b.Uncles {
|
|
uncleMiner := db.GetOrCreateMinerByAddress(u.Wallet.ToBase58())
|
|
if uncleMiner == nil {
|
|
return nil, nil, errors.New("could not get or create miner")
|
|
}
|
|
|
|
uncle := &UncleBlock{
|
|
Block: Block{
|
|
Id: u.Id,
|
|
Height: u.Height,
|
|
PreviousId: u.PrevId,
|
|
Coinbase: BlockCoinbase{
|
|
Id: NilHash,
|
|
Reward: 0,
|
|
PrivateKey: NilHash,
|
|
},
|
|
Difficulty: types.Difficulty{Uint128: uint128.From64(b.Diff)},
|
|
Timestamp: u.Ts,
|
|
MinerId: uncleMiner.Id(),
|
|
PowHash: NilHash,
|
|
Main: BlockMainData{
|
|
Id: NilHash,
|
|
Height: 0,
|
|
Found: false,
|
|
},
|
|
Template: struct {
|
|
Id types.Hash `json:"id"`
|
|
Difficulty types.Difficulty `json:"difficulty"`
|
|
}{
|
|
Id: UndefinedHash,
|
|
Difficulty: UndefinedDifficulty,
|
|
},
|
|
},
|
|
ParentId: block.Id,
|
|
ParentHeight: block.Height,
|
|
}
|
|
|
|
uncles = append(uncles, uncle)
|
|
}
|
|
return block, uncles, nil
|
|
} else {
|
|
return nil, nil, fmt.Errorf("unknown version %d", version.Version)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *Block) GetBlock() *Block {
|
|
return b
|
|
}
|
|
|
|
func (b *Block) IsProofHigherThanDifficulty() bool {
|
|
return b.GetProofDifficulty().Cmp(b.Template.Difficulty.Uint128) >= 0
|
|
}
|
|
|
|
func (b *Block) GetProofDifficulty() types.Difficulty {
|
|
base := uint256.NewInt(0).SetBytes32(bytes.Repeat([]byte{0xff}, 32))
|
|
pow := uint256.NewInt(0).SetBytes32(b.PowHash[:])
|
|
pow = &uint256.Int{bits.ReverseBytes64(pow[3]), bits.ReverseBytes64(pow[2]), bits.ReverseBytes64(pow[1]), bits.ReverseBytes64(pow[0])}
|
|
|
|
if pow.Eq(uint256.NewInt(0)) {
|
|
return types.Difficulty{}
|
|
}
|
|
|
|
powResult := uint256.NewInt(0).Div(base, pow).Bytes32()
|
|
return types.Difficulty{Uint128: uint128.FromBytes(powResult[16:]).ReverseBytes()}
|
|
}
|