502 lines
13 KiB
Go
502 lines
13 KiB
Go
package sidechain
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
|
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
|
p2poolcrypto "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/crypto"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
|
"io"
|
|
"log"
|
|
"sync"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
)
|
|
|
|
type CoinbaseExtraTag int
|
|
|
|
const SideExtraNonceSize = 4
|
|
const SideExtraNonceMaxSize = SideExtraNonceSize + 10
|
|
|
|
const (
|
|
SideCoinbasePublicKey = transaction.TxExtraTagPubKey
|
|
SideExtraNonce = transaction.TxExtraTagNonce
|
|
SideTemplateId = transaction.TxExtraTagMergeMining
|
|
)
|
|
|
|
type ShareVersion int
|
|
|
|
const (
|
|
ShareVersion_None ShareVersion = 0
|
|
ShareVersion_V1 ShareVersion = 1
|
|
ShareVersion_V2 ShareVersion = 2
|
|
)
|
|
|
|
const ShareVersion_V2MainNetTimestamp uint64 = 1679173200 // 2023-03-18 21:00 UTC
|
|
const ShareVersion_V2TestNetTimestamp uint64 = 1674507600 // 2023-01-23 21:00 UTC
|
|
|
|
type PoolBlock struct {
|
|
Main mainblock.Block
|
|
|
|
Side SideData
|
|
|
|
//Temporary data structures
|
|
cache poolBlockCache
|
|
Depth atomic.Uint64
|
|
Verified atomic.Bool
|
|
Invalid atomic.Bool
|
|
|
|
WantBroadcast atomic.Bool
|
|
Broadcasted atomic.Bool
|
|
|
|
NetworkType NetworkType
|
|
LocalTimestamp uint64
|
|
}
|
|
|
|
// NewShareFromExportedBytes TODO deprecate this in favor of standard serialized shares
|
|
// Deprecated
|
|
func NewShareFromExportedBytes(buf []byte, networkType NetworkType) (*PoolBlock, error) {
|
|
b := &PoolBlock{
|
|
NetworkType: networkType,
|
|
}
|
|
|
|
if len(buf) < 32 {
|
|
return nil, errors.New("invalid block data")
|
|
}
|
|
|
|
reader := bytes.NewReader(buf)
|
|
|
|
var (
|
|
err error
|
|
version uint64
|
|
|
|
mainDataSize uint64
|
|
mainData []byte
|
|
|
|
sideDataSize uint64
|
|
sideData []byte
|
|
)
|
|
|
|
if err = binary.Read(reader, binary.BigEndian, &version); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch version {
|
|
case 1:
|
|
|
|
if _, err = io.ReadFull(reader, b.cache.mainId[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err = io.ReadFull(reader, b.cache.powHash[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = binary.Read(reader, binary.BigEndian, &b.cache.mainDifficulty.Hi); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = binary.Read(reader, binary.BigEndian, &b.cache.mainDifficulty.Lo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b.cache.mainDifficulty.ReverseBytes()
|
|
|
|
if err = binary.Read(reader, binary.BigEndian, &mainDataSize); err != nil {
|
|
return nil, err
|
|
}
|
|
mainData = make([]byte, mainDataSize)
|
|
if _, err = io.ReadFull(reader, mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = binary.Read(reader, binary.BigEndian, &sideDataSize); err != nil {
|
|
return nil, err
|
|
}
|
|
sideData = make([]byte, sideDataSize)
|
|
if _, err = io.ReadFull(reader, sideData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
/*
|
|
//Ignore error when unable to read peer
|
|
_ = func() error {
|
|
var peerSize uint64
|
|
|
|
if err = binary.Read(reader, binary.BigEndian, &peerSize); err != nil {
|
|
return err
|
|
}
|
|
b.Extra.Peer = make([]byte, peerSize)
|
|
if _, err = io.ReadFull(reader, b.Extra.Peer); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}()
|
|
*/
|
|
|
|
case 0:
|
|
if err = binary.Read(reader, binary.BigEndian, &mainDataSize); err != nil {
|
|
return nil, err
|
|
}
|
|
mainData = make([]byte, mainDataSize)
|
|
if _, err = io.ReadFull(reader, mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
if sideData, err = io.ReadAll(reader); err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unknown block version %d", version)
|
|
}
|
|
|
|
if err = b.Main.UnmarshalBinary(mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = b.Side.UnmarshalBinary(sideData, b.ShareVersion()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b.cache.templateId = types.HashFromBytes(b.CoinbaseExtra(SideTemplateId))
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func (b *PoolBlock) ShareVersion() ShareVersion {
|
|
// P2Pool forks to v2 at 2023-03-18 21:00 UTC
|
|
// Different miners can have different timestamps,
|
|
// so a temporary mix of v1 and v2 blocks is allowed
|
|
switch b.NetworkType {
|
|
case NetworkInvalid:
|
|
log.Panicf("invalid network type for determining share version")
|
|
case NetworkMainnet:
|
|
if b.Main.Timestamp >= ShareVersion_V2MainNetTimestamp {
|
|
return ShareVersion_V2
|
|
}
|
|
case NetworkTestnet:
|
|
if b.Main.Timestamp >= ShareVersion_V2TestNetTimestamp {
|
|
return ShareVersion_V2
|
|
}
|
|
case NetworkStagenet:
|
|
return ShareVersion_V2
|
|
}
|
|
if b.Main.Timestamp >= ShareVersion_V2MainNetTimestamp {
|
|
return ShareVersion_V2
|
|
}
|
|
return ShareVersion_V1
|
|
}
|
|
|
|
func (b *PoolBlock) ShareVersionSignaling() ShareVersion {
|
|
if b.ShareVersion() == ShareVersion_V1 && ((binary.LittleEndian.Uint32(b.CoinbaseExtra(SideExtraNonce)))&0xFF100000 == 0xFF000000) {
|
|
return ShareVersion_V2
|
|
}
|
|
return ShareVersion_None
|
|
}
|
|
|
|
func (b *PoolBlock) CoinbaseExtra(tag CoinbaseExtraTag) []byte {
|
|
switch tag {
|
|
case SideExtraNonce:
|
|
if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
|
|
if len(t.Data) < SideExtraNonceSize || len(t.Data) > SideExtraNonceMaxSize {
|
|
return nil
|
|
}
|
|
return t.Data
|
|
}
|
|
case SideTemplateId:
|
|
if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
|
|
if len(t.Data) != types.HashSize {
|
|
return nil
|
|
}
|
|
return t.Data
|
|
}
|
|
case SideCoinbasePublicKey:
|
|
if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
|
|
if len(t.Data) != crypto.PublicKeySize {
|
|
return nil
|
|
}
|
|
return t.Data
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *PoolBlock) MainId() types.Hash {
|
|
if hash, ok := func() (types.Hash, bool) {
|
|
b.cache.lock.RLock()
|
|
defer b.cache.lock.RUnlock()
|
|
|
|
if b.cache.mainId != types.ZeroHash {
|
|
return b.cache.mainId, true
|
|
}
|
|
return types.ZeroHash, false
|
|
}(); ok {
|
|
return hash
|
|
} else {
|
|
b.cache.lock.Lock()
|
|
defer b.cache.lock.Unlock()
|
|
if b.cache.mainId == types.ZeroHash { //check again for race
|
|
b.cache.mainId = b.Main.Id()
|
|
}
|
|
return b.cache.mainId
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) FullId(consensus *Consensus) FullId {
|
|
if fullId, ok := func() (FullId, bool) {
|
|
b.cache.lock.RLock()
|
|
defer b.cache.lock.RUnlock()
|
|
|
|
if b.cache.fullId != zeroFullId {
|
|
return b.cache.fullId, true
|
|
}
|
|
return zeroFullId, false
|
|
}(); ok {
|
|
return fullId
|
|
} else {
|
|
b.cache.lock.Lock()
|
|
defer b.cache.lock.Unlock()
|
|
if b.cache.fullId == zeroFullId { //check again for race
|
|
b.cache.fullId = b.CalculateFullId(consensus)
|
|
}
|
|
return b.cache.fullId
|
|
}
|
|
}
|
|
|
|
const FullIdSize = int(types.HashSize + unsafe.Sizeof(uint32(0)) + SideExtraNonceSize)
|
|
|
|
var zeroFullId FullId
|
|
|
|
type FullId [FullIdSize]byte
|
|
|
|
func (b *PoolBlock) CalculateFullId(consensus *Consensus) FullId {
|
|
var buf FullId
|
|
sidechainId := b.SideTemplateId(consensus)
|
|
copy(buf[:], sidechainId[:])
|
|
binary.LittleEndian.PutUint32(buf[types.HashSize:], b.Main.Nonce)
|
|
copy(buf[types.HashSize+unsafe.Sizeof(b.Main.Nonce):], b.CoinbaseExtra(SideExtraNonce)[:SideExtraNonceSize])
|
|
return buf
|
|
}
|
|
|
|
func (b *PoolBlock) MainDifficulty(f mainblock.GetDifficultyByHeightFunc) types.Difficulty {
|
|
if difficulty, ok := func() (types.Difficulty, bool) {
|
|
b.cache.lock.RLock()
|
|
defer b.cache.lock.RUnlock()
|
|
|
|
if b.cache.mainDifficulty != types.ZeroDifficulty {
|
|
return b.cache.mainDifficulty, true
|
|
}
|
|
return types.ZeroDifficulty, false
|
|
}(); ok {
|
|
return difficulty
|
|
} else {
|
|
b.cache.lock.Lock()
|
|
defer b.cache.lock.Unlock()
|
|
if b.cache.mainDifficulty == types.ZeroDifficulty { //check again for race
|
|
b.cache.mainDifficulty = b.Main.Difficulty(f)
|
|
}
|
|
return b.cache.mainDifficulty
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) SideTemplateId(consensus *Consensus) types.Hash {
|
|
if hash, ok := func() (types.Hash, bool) {
|
|
b.cache.lock.RLock()
|
|
defer b.cache.lock.RUnlock()
|
|
|
|
if b.cache.templateId != types.ZeroHash {
|
|
return b.cache.templateId, true
|
|
}
|
|
return types.ZeroHash, false
|
|
}(); ok {
|
|
return hash
|
|
} else {
|
|
b.cache.lock.Lock()
|
|
defer b.cache.lock.Unlock()
|
|
if b.cache.templateId == types.ZeroHash { //check again for race
|
|
b.cache.templateId = consensus.CalculateSideTemplateId(b)
|
|
}
|
|
return b.cache.templateId
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) PowHash(f mainblock.GetSeedByHeightFunc) types.Hash {
|
|
h, _ := b.PowHashWithError(f)
|
|
return h
|
|
}
|
|
|
|
func (b *PoolBlock) PowHashWithError(f mainblock.GetSeedByHeightFunc) (powHash types.Hash, err error) {
|
|
if hash, ok := func() (types.Hash, bool) {
|
|
b.cache.lock.RLock()
|
|
defer b.cache.lock.RUnlock()
|
|
|
|
if b.cache.powHash != types.ZeroHash {
|
|
return b.cache.powHash, true
|
|
}
|
|
return types.ZeroHash, false
|
|
}(); ok {
|
|
return hash, nil
|
|
} else {
|
|
b.cache.lock.Lock()
|
|
defer b.cache.lock.Unlock()
|
|
if b.cache.powHash == types.ZeroHash { //check again for race
|
|
b.cache.powHash, err = b.Main.PowHashWithError(f)
|
|
}
|
|
return b.cache.powHash, err
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) UnmarshalBinary(data []byte) error {
|
|
reader := bytes.NewReader(data)
|
|
return b.FromReader(reader)
|
|
}
|
|
|
|
func (b *PoolBlock) MarshalBinary() ([]byte, error) {
|
|
if mainData, err := b.Main.MarshalBinary(); err != nil {
|
|
return nil, err
|
|
} else if sideData, err := b.Side.MarshalBinary(b.ShareVersion()); err != nil {
|
|
return nil, err
|
|
} else {
|
|
data := make([]byte, 0, len(mainData)+len(sideData))
|
|
data = append(data, mainData...)
|
|
data = append(data, sideData...)
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) MarshalBinaryFlags(pruned, compact bool) ([]byte, error) {
|
|
if mainData, err := b.Main.MarshalBinaryFlags(pruned, compact); err != nil {
|
|
return nil, err
|
|
} else if sideData, err := b.Side.MarshalBinary(b.ShareVersion()); err != nil {
|
|
return nil, err
|
|
} else {
|
|
data := make([]byte, 0, len(mainData)+len(sideData))
|
|
data = append(data, mainData...)
|
|
data = append(data, sideData...)
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) FromReader(reader readerAndByteReader) (err error) {
|
|
if err = b.Main.FromReader(reader); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = b.Side.FromReader(reader, b.ShareVersion()); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FromCompactReader used in Protocol 1.1 and above
|
|
func (b *PoolBlock) FromCompactReader(reader readerAndByteReader) (err error) {
|
|
if err = b.Main.FromCompactReader(reader); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = b.Side.FromReader(reader, b.ShareVersion()); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *PoolBlock) IsProofHigherThanMainDifficulty(difficultyFunc mainblock.GetDifficultyByHeightFunc, seedFunc mainblock.GetSeedByHeightFunc) bool {
|
|
r, _ := b.IsProofHigherThanMainDifficultyWithError(difficultyFunc, seedFunc)
|
|
return r
|
|
}
|
|
|
|
func (b *PoolBlock) IsProofHigherThanMainDifficultyWithError(difficultyFunc mainblock.GetDifficultyByHeightFunc, seedFunc mainblock.GetSeedByHeightFunc) (bool, error) {
|
|
if mainDifficulty := b.MainDifficulty(difficultyFunc); mainDifficulty == types.ZeroDifficulty {
|
|
return false, errors.New("could not get main difficulty")
|
|
} else if powHash, err := b.PowHashWithError(seedFunc); err != nil {
|
|
return false, err
|
|
} else {
|
|
return mainDifficulty.CheckPoW(powHash), nil
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) IsProofHigherThanDifficulty(f mainblock.GetSeedByHeightFunc) bool {
|
|
r, _ := b.IsProofHigherThanDifficultyWithError(f)
|
|
return r
|
|
}
|
|
|
|
func (b *PoolBlock) IsProofHigherThanDifficultyWithError(f mainblock.GetSeedByHeightFunc) (bool, error) {
|
|
if powHash, err := b.PowHashWithError(f); err != nil {
|
|
return false, err
|
|
} else {
|
|
return b.Side.Difficulty.CheckPoW(powHash), nil
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) GetPrivateKeySeed() types.Hash {
|
|
if b.ShareVersion() > ShareVersion_V1 {
|
|
return b.Side.CoinbasePrivateKeySeed
|
|
}
|
|
return types.Hash(b.Side.PublicSpendKey.AsBytes())
|
|
}
|
|
|
|
func (b *PoolBlock) CalculateTransactionPrivateKeySeed() types.Hash {
|
|
if b.ShareVersion() > ShareVersion_V1 {
|
|
mainData, _ := b.Main.SideChainHashingBlob()
|
|
sideData, _ := b.Side.MarshalBinary(b.ShareVersion())
|
|
return p2poolcrypto.CalculateTransactionPrivateKeySeed(
|
|
mainData,
|
|
sideData,
|
|
)
|
|
}
|
|
|
|
return types.Hash(b.Side.PublicSpendKey.AsBytes())
|
|
}
|
|
|
|
func (b *PoolBlock) GetAddress() *address.PackedAddress {
|
|
a := address.NewPackedAddressFromBytes(b.Side.PublicSpendKey, b.Side.PublicViewKey)
|
|
return &a
|
|
}
|
|
|
|
type poolBlockCache struct {
|
|
lock sync.RWMutex
|
|
mainId types.Hash
|
|
mainDifficulty types.Difficulty
|
|
templateId types.Hash
|
|
powHash types.Hash
|
|
fullId FullId
|
|
}
|
|
|
|
func (c *poolBlockCache) FromReader(reader readerAndByteReader) (err error) {
|
|
buf := make([]byte, types.HashSize*3+types.DifficultySize+FullIdSize)
|
|
if _, err = reader.Read(buf); err != nil {
|
|
return err
|
|
}
|
|
return c.UnmarshalBinary(buf)
|
|
}
|
|
|
|
func (c *poolBlockCache) UnmarshalBinary(buf []byte) error {
|
|
if len(buf) < types.HashSize*3+types.DifficultySize+FullIdSize {
|
|
return io.ErrUnexpectedEOF
|
|
}
|
|
copy(c.mainId[:], buf)
|
|
c.mainDifficulty = types.DifficultyFromBytes(buf[types.HashSize:])
|
|
copy(c.templateId[:], buf[types.HashSize+types.DifficultySize:])
|
|
copy(c.powHash[:], buf[types.HashSize+types.DifficultySize+types.HashSize:])
|
|
copy(c.fullId[:], buf[types.HashSize+types.DifficultySize+types.HashSize+types.HashSize:])
|
|
return nil
|
|
}
|
|
|
|
func (c *poolBlockCache) MarshalBinary() ([]byte, error) {
|
|
buf := make([]byte, 0, types.HashSize*3+types.DifficultySize+FullIdSize)
|
|
buf = append(buf, c.mainId[:]...)
|
|
buf = append(buf, c.mainDifficulty.Bytes()...)
|
|
buf = append(buf, c.templateId[:]...)
|
|
buf = append(buf, c.powHash[:]...)
|
|
buf = append(buf, c.fullId[:]...)
|
|
|
|
return buf, nil
|
|
}
|