Initial sidechain verification
This commit is contained in:
parent
a9e0ce96b0
commit
03a026c026
|
@ -9,9 +9,7 @@ import (
|
|||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"github.com/holiman/uint256"
|
||||
"golang.org/x/exp/slices"
|
||||
"math/bits"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -139,7 +137,7 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.PoolBlock, knownUncles [
|
|||
Main: BlockMainData{
|
||||
Id: b.MainId(),
|
||||
Height: b.Main.Coinbase.GenHeight,
|
||||
Found: b.IsProofHigherThanDifficulty(),
|
||||
Found: b.IsProofHigherThanMainDifficulty(),
|
||||
},
|
||||
Template: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
|
@ -181,7 +179,7 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.PoolBlock, knownUncles [
|
|||
Main: BlockMainData{
|
||||
Id: uncle.MainId(),
|
||||
Height: uncle.Main.Coinbase.GenHeight,
|
||||
Found: uncle.IsProofHigherThanDifficulty(),
|
||||
Found: uncle.IsProofHigherThanMainDifficulty(),
|
||||
},
|
||||
Template: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
|
@ -458,18 +456,5 @@ func (b *Block) GetBlock() *Block {
|
|||
}
|
||||
|
||||
func (b *Block) IsProofHigherThanDifficulty() bool {
|
||||
return b.GetProofDifficulty().Cmp(b.Template.Difficulty) >= 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.DifficultyFromBytes(powResult[16:])
|
||||
return b.Template.Difficulty.CheckPoW(b.PowHash)
|
||||
}
|
||||
|
|
5
go.mod
5
go.mod
|
@ -8,8 +8,8 @@ require (
|
|||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221025112134-5190471ef823
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20221007140323-a2daa2d5fc48
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221027134633-11f5607e6752
|
||||
github.com/Code-Hex/go-generics-cache v1.2.1
|
||||
github.com/ake-persson/mapslice-json v0.0.0-20210720081907-22c8edf57807
|
||||
github.com/floatdrop/lru v1.3.0
|
||||
github.com/go-faster/xor v0.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/holiman/uint256 v1.2.1
|
||||
|
@ -17,12 +17,13 @@ require (
|
|||
github.com/lib/pq v1.10.7
|
||||
github.com/tyler-sommer/stick v1.0.4
|
||||
golang.org/x/crypto v0.1.0
|
||||
golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326
|
||||
golang.org/x/net v0.1.0
|
||||
lukechampine.com/uint128 v1.2.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
)
|
||||
|
|
10
go.sum
10
go.sum
|
@ -8,12 +8,14 @@ git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20221007140323-a2daa2d5fc48 h1:Ex
|
|||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20221007140323-a2daa2d5fc48/go.mod h1:XeSC8jK8RXnnzVAmp9e9AQZCDIbML3UoCRkxxGA+lpU=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221027134633-11f5607e6752 h1:4r4KXpFLbixah+OGrBT9ZEflSZoFHD7aVJpXL3ukVIo=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221027134633-11f5607e6752/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
github.com/Code-Hex/go-generics-cache v1.2.1 h1:jKof8Hk8mr28lcEAo9g90oj7H3vb8y2DdccoGpL4lyE=
|
||||
github.com/Code-Hex/go-generics-cache v1.2.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4=
|
||||
github.com/ake-persson/mapslice-json v0.0.0-20210720081907-22c8edf57807 h1:w3nrGk00TWs/4iZ3Q0k9c0vL0e/wRziArKU4e++d/nA=
|
||||
github.com/ake-persson/mapslice-json v0.0.0-20210720081907-22c8edf57807/go.mod h1:fGnnfniJiO/ajHAVHqMSUSL8sE9LmU9rzclCtoeB+y8=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
|
||||
github.com/go-faster/xor v0.3.0 h1:tc0bdVe31Wj999e5rEj7K3DhHyQNp2VydYyLFj3YSN8=
|
||||
github.com/go-faster/xor v0.3.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ=
|
||||
|
@ -43,8 +45,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e h1:SkwG94eNiiYJhbeDE018Grw09HIN/KB9NlRmZsrzfWs=
|
||||
golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE=
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
|
|
|
@ -8,13 +8,34 @@ import (
|
|||
"git.gammaspectra.live/P2Pool/moneroutil"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type PackedAddress struct {
|
||||
SpendPub types.Hash
|
||||
ViewPub types.Hash
|
||||
}
|
||||
|
||||
func (p PackedAddress) Compare(b PackedAddress) int {
|
||||
return bytes.Compare(unsafe.Slice((*byte)(unsafe.Pointer(&p)), unsafe.Sizeof(p)), unsafe.Slice((*byte)(unsafe.Pointer(&b)), unsafe.Sizeof(b)))
|
||||
/*
|
||||
if r := bytes.Compare(p.SpendPub[:], b.SpendPub[:]); r != 0 {
|
||||
return r
|
||||
} else {
|
||||
return bytes.Compare(p.ViewPub[:], b.ViewPub[:])
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func (p PackedAddress) ToAddress() *Address {
|
||||
return FromRawAddress(moneroutil.MainNetwork, p.SpendPub, p.ViewPub)
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
Network uint8
|
||||
SpendPub *edwards25519.Point
|
||||
ViewPub *edwards25519.Point
|
||||
Checksum []byte
|
||||
checksum []byte
|
||||
// IsSubAddress Always false
|
||||
IsSubAddress bool
|
||||
|
||||
|
@ -33,7 +54,7 @@ func FromBase58(address string) *Address {
|
|||
}
|
||||
a := &Address{
|
||||
Network: raw[0],
|
||||
Checksum: checksum[:],
|
||||
checksum: checksum[:],
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -53,10 +74,11 @@ func FromRawAddress(network uint8, spend, view types.Hash) *Address {
|
|||
copy(nice[1:], spend[:])
|
||||
copy(nice[33:], view[:])
|
||||
|
||||
//TODO: cache checksum?
|
||||
checksum := moneroutil.GetChecksum(nice[:65])
|
||||
a := &Address{
|
||||
Network: nice[0],
|
||||
Checksum: checksum[:],
|
||||
checksum: checksum[:],
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -71,7 +93,11 @@ func FromRawAddress(network uint8, spend, view types.Hash) *Address {
|
|||
}
|
||||
|
||||
func (a *Address) ToBase58() string {
|
||||
return moneroutil.EncodeMoneroBase58([]byte{a.Network}, a.SpendPub.Bytes(), a.ViewPub.Bytes(), a.Checksum[:])
|
||||
return moneroutil.EncodeMoneroBase58([]byte{a.Network}, a.SpendPub.Bytes(), a.ViewPub.Bytes(), a.checksum[:])
|
||||
}
|
||||
|
||||
func (a *Address) ToPacked() PackedAddress {
|
||||
return PackedAddress{SpendPub: types.HashFromBytes(a.SpendPub.Bytes()), ViewPub: types.HashFromBytes(a.ViewPub.Bytes())}
|
||||
}
|
||||
|
||||
func (a *Address) MarshalJSON() ([]byte, error) {
|
||||
|
@ -88,7 +114,7 @@ func (a *Address) UnmarshalJSON(b []byte) error {
|
|||
a.Network = addr.Network
|
||||
a.SpendPub = addr.SpendPub
|
||||
a.ViewPub = addr.ViewPub
|
||||
a.Checksum = addr.Checksum
|
||||
a.checksum = addr.checksum
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("invalid address")
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
package address
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"filippo.io/edwards25519"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"github.com/Code-Hex/go-generics-cache/policy/lfu"
|
||||
)
|
||||
|
||||
type derivationCacheKey [types.HashSize * 2]byte
|
||||
type sharedDataCacheKey [types.HashSize + 8]byte
|
||||
|
||||
type sharedDataWithTag struct {
|
||||
SharedData *edwards25519.Scalar
|
||||
ViewTag uint8
|
||||
}
|
||||
|
||||
type DerivationCache struct {
|
||||
deterministicKeyCache *lfu.Cache[derivationCacheKey, *crypto.KeyPair]
|
||||
derivationCache *lfu.Cache[derivationCacheKey, *edwards25519.Point]
|
||||
sharedDataCache *lfu.Cache[sharedDataCacheKey, sharedDataWithTag]
|
||||
ephemeralPublicKeyCache *lfu.Cache[derivationCacheKey, types.Hash]
|
||||
}
|
||||
|
||||
func NewDerivationCache() *DerivationCache {
|
||||
d := &DerivationCache{}
|
||||
d.Clear()
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DerivationCache) Clear() {
|
||||
//keep a few recent blocks from the past few for uncles, and reused window miners
|
||||
//~10s per share, keys change every Monero block (2m). around 2160 max shares per 6h (window), plus uncles. 6 shares per minute.
|
||||
//each share can have up to 2160 outputs, plus uncles. each miner has its own private key per Monero block
|
||||
d.deterministicKeyCache = lfu.NewCache[derivationCacheKey, *crypto.KeyPair](lfu.WithCapacity(4096))
|
||||
d.derivationCache = lfu.NewCache[derivationCacheKey, *edwards25519.Point](lfu.WithCapacity(4096))
|
||||
d.sharedDataCache = lfu.NewCache[sharedDataCacheKey, sharedDataWithTag](lfu.WithCapacity(4096 * 2160))
|
||||
d.ephemeralPublicKeyCache = lfu.NewCache[derivationCacheKey, types.Hash](lfu.WithCapacity(4096 * 2160))
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetEphemeralPublicKey(address *Address, txKey types.Hash, outputIndex uint64) (types.Hash, uint8) {
|
||||
sharedData, viewTag := d.GetSharedData(address, txKey, outputIndex)
|
||||
|
||||
var key derivationCacheKey
|
||||
copy(key[:], address.SpendPub.Bytes())
|
||||
copy(key[types.HashSize:], sharedData.Bytes())
|
||||
if ephemeralPubKey, ok := d.ephemeralPublicKeyCache.Get(key); !ok {
|
||||
copy(ephemeralPubKey[:], address.GetPublicKeyForSharedData(sharedData).Bytes())
|
||||
d.ephemeralPublicKeyCache.Set(key, ephemeralPubKey)
|
||||
return ephemeralPubKey, viewTag
|
||||
} else {
|
||||
return ephemeralPubKey, viewTag
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetSharedData(address *Address, txKey types.Hash, outputIndex uint64) (*edwards25519.Scalar, uint8) {
|
||||
derivation := d.GetDerivation(address, txKey)
|
||||
|
||||
var key sharedDataCacheKey
|
||||
copy(key[:], derivation.Bytes())
|
||||
binary.LittleEndian.PutUint64(key[types.HashSize:], outputIndex)
|
||||
|
||||
if sharedData, ok := d.sharedDataCache.Get(key); !ok {
|
||||
sharedData.SharedData = crypto.GetDerivationSharedDataForOutputIndex(derivation, outputIndex)
|
||||
sharedData.ViewTag = crypto.GetDerivationViewTagForOutputIndex(derivation, outputIndex)
|
||||
d.sharedDataCache.Set(key, sharedData)
|
||||
return sharedData.SharedData, sharedData.ViewTag
|
||||
} else {
|
||||
return sharedData.SharedData, sharedData.ViewTag
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetDeterministicTransactionKey(address *Address, prevId types.Hash) *crypto.KeyPair {
|
||||
var key derivationCacheKey
|
||||
copy(key[:], address.SpendPub.Bytes())
|
||||
copy(key[types.HashSize:], prevId[:])
|
||||
|
||||
if kp, ok := d.deterministicKeyCache.Get(key); !ok {
|
||||
kp = crypto.NewKeyPairFromPrivate(address.GetDeterministicTransactionPrivateKey(prevId))
|
||||
d.deterministicKeyCache.Set(key, kp)
|
||||
return kp
|
||||
} else {
|
||||
return kp
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetDerivation(address *Address, txKey types.Hash) *edwards25519.Point {
|
||||
var key derivationCacheKey
|
||||
copy(key[:], address.ViewPub.Bytes())
|
||||
copy(key[types.HashSize:], txKey[:])
|
||||
|
||||
if derivation, ok := d.derivationCache.Get(key); !ok {
|
||||
pK, _ := edwards25519.NewScalar().SetCanonicalBytes(txKey[:])
|
||||
derivation = address.GetDerivationForPrivateKey(pK)
|
||||
d.derivationCache.Set(key, derivation)
|
||||
return derivation
|
||||
} else {
|
||||
return derivation
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ import (
|
|||
"filippo.io/edwards25519"
|
||||
"git.gammaspectra.live/P2Pool/moneroutil"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool"
|
||||
p2poolcrypto "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"strings"
|
||||
)
|
||||
|
@ -15,7 +15,7 @@ func (a *Address) GetDerivationForPrivateKey(privateKey *edwards25519.Scalar) *e
|
|||
}
|
||||
|
||||
func (a *Address) GetDeterministicTransactionPrivateKey(prevId types.Hash) *edwards25519.Scalar {
|
||||
return p2pool.GetDeterministicTransactionPrivateKey(a.SpendPub, prevId)
|
||||
return p2poolcrypto.GetDeterministicTransactionPrivateKey(a.SpendPub, prevId)
|
||||
}
|
||||
|
||||
func (a *Address) GetPublicKeyForSharedData(sharedData *edwards25519.Scalar) *edwards25519.Point {
|
||||
|
|
|
@ -23,6 +23,18 @@ type Block struct {
|
|||
Transactions []types.Hash
|
||||
}
|
||||
|
||||
type BlockHeader struct {
|
||||
MajorVersion uint8
|
||||
MinorVersion uint8
|
||||
Timestamp uint64
|
||||
PreviousId types.Hash
|
||||
Height uint64
|
||||
Nonce uint32
|
||||
Reward uint64
|
||||
Difficulty types.Difficulty
|
||||
Id types.Hash
|
||||
}
|
||||
|
||||
type readerAndByteReader interface {
|
||||
io.Reader
|
||||
io.ByteReader
|
||||
|
@ -107,7 +119,21 @@ func (b *Block) UnmarshalBinary(data []byte) error {
|
|||
return b.FromReader(reader)
|
||||
}
|
||||
|
||||
func (b *Block) Header() []byte {
|
||||
func (b *Block) Header() *BlockHeader {
|
||||
return &BlockHeader{
|
||||
MajorVersion: b.MajorVersion,
|
||||
MinorVersion: b.MinorVersion,
|
||||
Timestamp: b.Timestamp,
|
||||
PreviousId: b.PreviousId,
|
||||
Height: b.Coinbase.GenHeight,
|
||||
Nonce: b.Nonce,
|
||||
Reward: b.Coinbase.TotalReward,
|
||||
Id: b.Id(),
|
||||
Difficulty: types.ZeroDifficulty,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Block) HeaderBlob() []byte {
|
||||
//TODO: cache
|
||||
buf := make([]byte, 0, 1+1+binary.MaxVarintLen64+types.HashSize+4+types.HashSize+binary.MaxVarintLen64) //predict its use on HashingBlob
|
||||
buf = append(buf, b.MajorVersion)
|
||||
|
@ -143,7 +169,7 @@ func (b *Block) SideChainHashingBlob() (buf []byte, err error) {
|
|||
|
||||
func (b *Block) HashingBlob() []byte {
|
||||
//TODO: cache
|
||||
buf := b.Header()
|
||||
buf := b.HeaderBlob()
|
||||
|
||||
txTreeHash := b.TxTreeHash()
|
||||
buf = append(buf, txTreeHash[:]...)
|
||||
|
@ -226,6 +252,11 @@ func (b *Block) PowHash() types.Hash {
|
|||
return h
|
||||
}
|
||||
|
||||
func (b *Block) PowHashWithError() (types.Hash, error) {
|
||||
//not cached
|
||||
return HashBlob(b.Coinbase.GenHeight, b.HashingBlob())
|
||||
}
|
||||
|
||||
func (b *Block) Id() types.Hash {
|
||||
//cached by sidechain.Share
|
||||
buf := b.HashingBlob()
|
||||
|
@ -239,15 +270,6 @@ func (b *Block) Id() types.Hash {
|
|||
|
||||
var hasher = randomx.NewRandomX()
|
||||
|
||||
func HashBlob(height uint64, blob []byte) (hash types.Hash, err error) {
|
||||
|
||||
if seed, err := client.GetClient().GetSeedByHeight(height); err != nil {
|
||||
return types.ZeroHash, err
|
||||
} else {
|
||||
return hasher.Hash(seed[:], blob)
|
||||
}
|
||||
}
|
||||
|
||||
func keccakl(data []byte, len int) []byte {
|
||||
h := sha3.NewLegacyKeccak256()
|
||||
h.Write(data[:len])
|
||||
|
|
76
monero/block/rpc.go
Normal file
76
monero/block/rpc.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package block
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
)
|
||||
|
||||
func HashBlob(height uint64, blob []byte) (hash types.Hash, err error) {
|
||||
|
||||
if seed, err := client.GetClient().GetSeedByHeight(height); err != nil {
|
||||
return types.ZeroHash, err
|
||||
} else {
|
||||
return hasher.Hash(seed[:], blob)
|
||||
}
|
||||
}
|
||||
|
||||
func GetBlockHeaderByHeight(height uint64) *BlockHeader {
|
||||
//TODO: cache
|
||||
if header, err := client.GetClient().GetBlockHeaderByHeight(height); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
prevHash, _ := types.HashFromString(header.BlockHeader.PrevHash)
|
||||
h, _ := types.HashFromString(header.BlockHeader.Hash)
|
||||
return &BlockHeader{
|
||||
MajorVersion: uint8(header.BlockHeader.MajorVersion),
|
||||
MinorVersion: uint8(header.BlockHeader.MinorVersion),
|
||||
Timestamp: uint64(header.BlockHeader.Timestamp),
|
||||
PreviousId: prevHash,
|
||||
Height: header.BlockHeader.Difficulty,
|
||||
Nonce: uint32(header.BlockHeader.Nonce),
|
||||
Reward: header.BlockHeader.Reward,
|
||||
Id: h,
|
||||
Difficulty: types.DifficultyFrom64(header.BlockHeader.Difficulty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetBlockHeaderByHash(hash types.Hash) *BlockHeader {
|
||||
if header, err := client.GetClient().GetBlockHeaderByHash(hash); err != nil || len(header.BlockHeaders) != 1 {
|
||||
return nil
|
||||
} else {
|
||||
prevHash, _ := types.HashFromString(header.BlockHeaders[0].PrevHash)
|
||||
h, _ := types.HashFromString(header.BlockHeaders[0].Hash)
|
||||
return &BlockHeader{
|
||||
MajorVersion: uint8(header.BlockHeaders[0].MajorVersion),
|
||||
MinorVersion: uint8(header.BlockHeaders[0].MinorVersion),
|
||||
Timestamp: uint64(header.BlockHeaders[0].Timestamp),
|
||||
PreviousId: prevHash,
|
||||
Height: header.BlockHeaders[0].Height,
|
||||
Nonce: uint32(header.BlockHeaders[0].Nonce),
|
||||
Reward: header.BlockHeaders[0].Reward,
|
||||
Id: h,
|
||||
Difficulty: types.DifficultyFrom64(header.BlockHeaders[0].Difficulty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetLastBlockHeader() *BlockHeader {
|
||||
if header, err := client.GetClient().GetLastBlockHeader(); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
prevHash, _ := types.HashFromString(header.BlockHeader.PrevHash)
|
||||
h, _ := types.HashFromString(header.BlockHeader.Hash)
|
||||
return &BlockHeader{
|
||||
MajorVersion: uint8(header.BlockHeader.MajorVersion),
|
||||
MinorVersion: uint8(header.BlockHeader.MinorVersion),
|
||||
Timestamp: uint64(header.BlockHeader.Timestamp),
|
||||
PreviousId: prevHash,
|
||||
Height: header.BlockHeader.Difficulty,
|
||||
Nonce: uint32(header.BlockHeader.Nonce),
|
||||
Reward: header.BlockHeader.Reward,
|
||||
Id: h,
|
||||
Difficulty: types.DifficultyFrom64(header.BlockHeader.Difficulty),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ import (
|
|||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"github.com/Code-Hex/go-generics-cache/policy/lru"
|
||||
"github.com/floatdrop/lru"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
@ -57,11 +57,11 @@ type Client struct {
|
|||
c *rpc.Client
|
||||
d *daemon.Client
|
||||
|
||||
difficultyCache *lru.Cache[uint64, types.Difficulty]
|
||||
difficultyCache *lru.LRU[uint64, types.Difficulty]
|
||||
|
||||
seedCache *lru.Cache[uint64, types.Hash]
|
||||
seedCache *lru.LRU[uint64, types.Hash]
|
||||
|
||||
coinbaseTransactionCache *lru.Cache[types.Hash, *transaction.CoinbaseTransaction]
|
||||
coinbaseTransactionCache *lru.LRU[types.Hash, *transaction.CoinbaseTransaction]
|
||||
|
||||
throttler <-chan time.Time
|
||||
}
|
||||
|
@ -74,15 +74,15 @@ func newClient() (*Client, error) {
|
|||
return &Client{
|
||||
c: c,
|
||||
d: daemon.NewClient(c),
|
||||
difficultyCache: lru.NewCache[uint64, types.Difficulty](lru.WithCapacity(1024)),
|
||||
seedCache: lru.NewCache[uint64, types.Hash](lru.WithCapacity(1024)),
|
||||
coinbaseTransactionCache: lru.NewCache[types.Hash, *transaction.CoinbaseTransaction](lru.WithCapacity(1024)),
|
||||
difficultyCache: lru.New[uint64, types.Difficulty](1024),
|
||||
seedCache: lru.New[uint64, types.Hash](1024),
|
||||
coinbaseTransactionCache: lru.New[types.Hash, *transaction.CoinbaseTransaction](1024),
|
||||
throttler: time.Tick(time.Second / 2),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetCoinbaseTransaction(txId types.Hash) (*transaction.CoinbaseTransaction, error) {
|
||||
if tx, ok := c.coinbaseTransactionCache.Get(txId); !ok {
|
||||
if tx := c.coinbaseTransactionCache.Get(txId); tx == nil {
|
||||
<-c.throttler
|
||||
if result, err := c.d.GetTransactions(context.Background(), []string{txId.String()}); err != nil {
|
||||
return nil, err
|
||||
|
@ -94,7 +94,7 @@ func (c *Client) GetCoinbaseTransaction(txId types.Hash) (*transaction.CoinbaseT
|
|||
if buf, err := hex.DecodeString(result.Txs[0].PrunedAsHex); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
tx = &transaction.CoinbaseTransaction{}
|
||||
tx := &transaction.CoinbaseTransaction{}
|
||||
if err = tx.UnmarshalBinary(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ func (c *Client) GetCoinbaseTransaction(txId types.Hash) (*transaction.CoinbaseT
|
|||
}
|
||||
}
|
||||
} else {
|
||||
return tx, nil
|
||||
return *tx, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,7 +133,7 @@ func (c *Client) GetSeedByHeight(height uint64) (types.Hash, error) {
|
|||
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
|
||||
if seed, ok := c.seedCache.Get(seedHeight); !ok {
|
||||
if seed := c.seedCache.Get(seedHeight); seed == nil {
|
||||
if seed, err := c.GetBlockIdByHeight(seedHeight); err != nil {
|
||||
return types.ZeroHash, err
|
||||
} else {
|
||||
|
@ -141,29 +141,47 @@ func (c *Client) GetSeedByHeight(height uint64) (types.Hash, error) {
|
|||
return seed, nil
|
||||
}
|
||||
} else {
|
||||
return seed, nil
|
||||
return *seed, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetDifficultyByHeight(height uint64) (types.Difficulty, error) {
|
||||
if difficulty, ok := c.difficultyCache.Get(height); !ok {
|
||||
if difficulty := c.difficultyCache.Get(height); difficulty == nil {
|
||||
if header, err := c.GetBlockHeaderByHeight(height); err != nil {
|
||||
if template, err := c.GetBlockTemplate(types.DonationAddress); err != nil {
|
||||
return types.Difficulty{}, err
|
||||
return types.ZeroDifficulty, err
|
||||
} else if uint64(template.Height) == height {
|
||||
difficulty = types.DifficultyFrom64(uint64(template.Difficulty))
|
||||
difficulty := types.DifficultyFrom64(uint64(template.Difficulty))
|
||||
c.difficultyCache.Set(height, difficulty)
|
||||
return difficulty, nil
|
||||
} else {
|
||||
return types.Difficulty{}, errors.New("height not found and is not next template")
|
||||
return types.ZeroDifficulty, errors.New("height not found and is not next template")
|
||||
}
|
||||
} else {
|
||||
difficulty = types.DifficultyFrom64(uint64(header.BlockHeader.Difficulty))
|
||||
difficulty := types.DifficultyFrom64(uint64(header.BlockHeader.Difficulty))
|
||||
c.difficultyCache.Set(height, difficulty)
|
||||
return difficulty, nil
|
||||
}
|
||||
} else {
|
||||
return difficulty, nil
|
||||
return *difficulty, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetBlockHeaderByHash(hash types.Hash) (*daemon.GetBlockHeaderByHashResult, error) {
|
||||
<-c.throttler
|
||||
if result, err := c.d.GetBlockHeaderByHash(context.Background(), []string{hash.String()}); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetLastBlockHeader() (*daemon.GetLastBlockHeaderResult, error) {
|
||||
<-c.throttler
|
||||
if result, err := c.d.GetLastBlockHeader(context.Background()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package monero
|
||||
|
||||
const (
|
||||
BlockTime = 60 * 2
|
||||
BlockTime = 60 * 2
|
||||
MinerRewardUnlock = 60
|
||||
HardForkViewTagsVersion = 15
|
||||
HardForkSupportedVersion = 16
|
||||
)
|
||||
|
|
|
@ -27,6 +27,11 @@ type CoinbaseTransaction struct {
|
|||
GenHeight uint64
|
||||
Outputs []*CoinbaseTransactionOutput
|
||||
|
||||
// OutputsBlobSize length of serialized Outputs. Used by p2pool serialized pruned blocks, filled regardless
|
||||
OutputsBlobSize uint64
|
||||
// TotalReward amount of reward existing Outputs. Used by p2pool serialized pruned blocks, filled regardless
|
||||
TotalReward uint64
|
||||
|
||||
Extra ExtraTags
|
||||
|
||||
ExtraBaseRCT uint8
|
||||
|
@ -47,6 +52,9 @@ func (c *CoinbaseTransaction) FromReader(reader readerAndByteReader) (err error)
|
|||
txExtraSize uint64
|
||||
)
|
||||
|
||||
c.TotalReward = 0
|
||||
c.OutputsBlobSize = 0
|
||||
|
||||
if c.Version, err = reader.ReadByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -81,39 +89,57 @@ func (c *CoinbaseTransaction) FromReader(reader readerAndByteReader) (err error)
|
|||
return err
|
||||
}
|
||||
|
||||
if outputCount < 8192 {
|
||||
c.Outputs = make([]*CoinbaseTransactionOutput, 0, outputCount)
|
||||
}
|
||||
|
||||
for index := 0; index < int(outputCount); index++ {
|
||||
o := &CoinbaseTransactionOutput{
|
||||
Index: uint64(index),
|
||||
if outputCount > 0 {
|
||||
if outputCount < 8192 {
|
||||
c.Outputs = make([]*CoinbaseTransactionOutput, 0, outputCount)
|
||||
}
|
||||
|
||||
if o.Reward, err = binary.ReadUvarint(reader); err != nil {
|
||||
return err
|
||||
}
|
||||
for index := 0; index < int(outputCount); index++ {
|
||||
o := &CoinbaseTransactionOutput{
|
||||
Index: uint64(index),
|
||||
}
|
||||
|
||||
if err = binary.Read(reader, binary.BigEndian, &o.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch o.Type {
|
||||
case TxOutToTaggedKey, TxOutToKey:
|
||||
if _, err = io.ReadFull(reader, o.EphemeralPublicKey[:]); err != nil {
|
||||
if o.Reward, err = binary.ReadUvarint(reader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.Type == TxOutToTaggedKey {
|
||||
if err = binary.Read(reader, binary.BigEndian, &o.ViewTag); err != nil {
|
||||
if err = binary.Read(reader, binary.BigEndian, &o.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch o.Type {
|
||||
case TxOutToTaggedKey, TxOutToKey:
|
||||
if _, err = io.ReadFull(reader, o.EphemeralPublicKey[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.Type == TxOutToTaggedKey {
|
||||
if err = binary.Read(reader, binary.BigEndian, &o.ViewTag); err != nil {
|
||||
return err
|
||||
}
|
||||
c.OutputsBlobSize += 1 + types.HashSize + 1
|
||||
} else {
|
||||
c.OutputsBlobSize += 1 + types.HashSize
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown %d TXOUT key", o.Type)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown %d TXOUT key", o.Type)
|
||||
|
||||
c.TotalReward += o.Reward
|
||||
c.Outputs = append(c.Outputs, o)
|
||||
}
|
||||
} else {
|
||||
// Outputs are not in the buffer and must be calculated from sidechain data
|
||||
// We only have total reward and outputs blob size here
|
||||
//special case, pruned block. outputs have to be generated from chain
|
||||
|
||||
if c.TotalReward, err = binary.ReadUvarint(reader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Outputs = append(c.Outputs, o)
|
||||
if c.OutputsBlobSize, err = binary.ReadUvarint(reader); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if txExtraSize, err = binary.ReadUvarint(reader); err != nil {
|
||||
|
@ -178,6 +204,30 @@ func (c *CoinbaseTransaction) MarshalBinary() ([]byte, error) {
|
|||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *CoinbaseTransaction) OutputsBlob() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
varIntBuf := make([]byte, binary.MaxVarintLen64)
|
||||
|
||||
for _, o := range c.Outputs {
|
||||
_, _ = buf.Write(varIntBuf[:binary.PutUvarint(varIntBuf, o.Reward)])
|
||||
_ = binary.Write(buf, binary.BigEndian, o.Type)
|
||||
|
||||
switch o.Type {
|
||||
case TxOutToTaggedKey, TxOutToKey:
|
||||
_, _ = buf.Write(o.EphemeralPublicKey[:])
|
||||
|
||||
if o.Type == TxOutToTaggedKey {
|
||||
_ = binary.Write(buf, binary.BigEndian, o.ViewTag)
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unknown output type")
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *CoinbaseTransaction) SideChainHashingBlob() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
package p2pool
|
||||
|
||||
const (
|
||||
PPLNSWindow = 2160
|
||||
BlockTime = 10
|
||||
UnclePenalty = 20
|
||||
UncleBlockDepth = 3
|
||||
PPLNSWindow = 2160
|
||||
BlockTime = 10
|
||||
UnclePenalty = 20
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package p2pool
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"filippo.io/edwards25519"
|
|
@ -3,6 +3,7 @@ package p2p
|
|||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"git.gammaspectra.live/P2Pool/moneroutil"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"sync/atomic"
|
||||
|
@ -48,3 +49,15 @@ func FindChallengeSolution(challenge HandshakeChallenge, consensusId types.Hash,
|
|||
salt++
|
||||
}
|
||||
}
|
||||
|
||||
func CalculateChallengeHash(challenge HandshakeChallenge, consensusId types.Hash, solution uint64) (hash types.Hash, ok bool) {
|
||||
|
||||
var buf [HandshakeChallengeSize*2 + types.HashSize]byte
|
||||
copy(buf[:], challenge[:])
|
||||
copy(buf[HandshakeChallengeSize:], consensusId[:])
|
||||
binary.LittleEndian.PutUint64(buf[types.HashSize+HandshakeChallengeSize:], solution)
|
||||
|
||||
hash = types.Hash(moneroutil.Keccak256(buf[:]))
|
||||
|
||||
return hash, types.DifficultyFrom64(binary.LittleEndian.Uint64(hash[types.HashSize-int(unsafe.Sizeof(uint64(0))):])).Mul64(HandshakeChallengeDifficulty).Hi == 0
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package p2p
|
|||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"golang.org/x/exp/slices"
|
||||
"log"
|
||||
|
@ -10,36 +11,293 @@ import (
|
|||
"net/netip"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const DefaultBanTime = time.Second * 600
|
||||
|
||||
type Client struct {
|
||||
Owner *Server
|
||||
Connection net.Conn
|
||||
Closed atomic.Bool
|
||||
AddressPort netip.AddrPort
|
||||
LastActive time.Time
|
||||
LastBroadcast time.Time
|
||||
LastBlockRequest time.Time
|
||||
PeerId uint64
|
||||
IsIncomingConnection bool
|
||||
HandshakeComplete bool
|
||||
ListenPort uint32
|
||||
|
||||
BlockPendingRequests int64
|
||||
ChainTipBlockRequest bool
|
||||
|
||||
ExpectedMessage MessageId
|
||||
|
||||
HandshakeChallenge HandshakeChallenge
|
||||
}
|
||||
|
||||
func NewClient(owner *Server, conn net.Conn) *Client {
|
||||
c := &Client{
|
||||
Owner: owner,
|
||||
Connection: conn,
|
||||
AddressPort: netip.MustParseAddrPort(conn.RemoteAddr().String()),
|
||||
LastActive: time.Now(),
|
||||
Owner: owner,
|
||||
Connection: conn,
|
||||
AddressPort: netip.MustParseAddrPort(conn.RemoteAddr().String()),
|
||||
LastActive: time.Now(),
|
||||
ExpectedMessage: MessageHandshakeChallenge,
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) Ban(duration time.Duration) {
|
||||
c.Owner.Ban(c.AddressPort.Addr(), duration)
|
||||
c.Close()
|
||||
}
|
||||
|
||||
func (c *Client) OnAfterHandshake() {
|
||||
c.SendListenPort()
|
||||
c.SendBlockRequest(types.ZeroHash)
|
||||
}
|
||||
|
||||
func (c *Client) SendListenPort() {
|
||||
var buf [1 + int(unsafe.Sizeof(uint32(0)))]byte
|
||||
buf[0] = byte(MessageListenPort)
|
||||
binary.LittleEndian.PutUint32(buf[1:], uint32(c.Owner.listenAddress.Port()))
|
||||
_, _ = c.Write(buf[:])
|
||||
}
|
||||
|
||||
func (c *Client) SendBlockRequest(hash types.Hash) {
|
||||
var buf [1 + types.HashSize]byte
|
||||
buf[0] = byte(MessageBlockRequest)
|
||||
copy(buf[1:], hash[:])
|
||||
|
||||
_, _ = c.Write(buf[:])
|
||||
|
||||
c.BlockPendingRequests++
|
||||
if hash == types.ZeroHash {
|
||||
c.ChainTipBlockRequest = true
|
||||
}
|
||||
c.LastBroadcast = time.Now()
|
||||
}
|
||||
|
||||
func (c *Client) SendBlockResponse(block *sidechain.PoolBlock) {
|
||||
if block != nil {
|
||||
blockData, _ := block.MarshalBinary()
|
||||
_, _ = c.Write(append([]byte{byte(MessageBlockResponse)}, blockData...))
|
||||
} else {
|
||||
_, _ = c.Write([]byte{byte(MessageBlockResponse)})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SendPeerListRequest() {
|
||||
return
|
||||
//TODO
|
||||
_, _ = c.Write([]byte{byte(MessagePeerListRequest)})
|
||||
}
|
||||
|
||||
func (c *Client) OnConnection() {
|
||||
c.LastActive = time.Now()
|
||||
|
||||
c.sendHandshakeChallenge()
|
||||
|
||||
for !c.Closed.Load() {
|
||||
var messageId MessageId
|
||||
if err := binary.Read(c, binary.LittleEndian, &messageId); err != nil {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.HandshakeComplete && messageId != c.ExpectedMessage {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
switch messageId {
|
||||
case MessageHandshakeChallenge:
|
||||
if c.HandshakeComplete {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
var challenge HandshakeChallenge
|
||||
var peerId uint64
|
||||
if err := binary.Read(c, binary.LittleEndian, &challenge); err != nil {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
if err := binary.Read(c, binary.LittleEndian, &peerId); err != nil {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
if peerId == c.Owner.PeerId() {
|
||||
//tried to connect to self
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
c.PeerId = peerId
|
||||
|
||||
if func() bool {
|
||||
c.Owner.clientsLock.RLock()
|
||||
defer c.Owner.clientsLock.RUnlock()
|
||||
for _, client := range c.Owner.clients {
|
||||
if client != c && client.PeerId == peerId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}() {
|
||||
//same peer
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
c.sendHandshakeSolution(challenge)
|
||||
|
||||
c.ExpectedMessage = MessageHandshakeSolution
|
||||
|
||||
c.OnAfterHandshake()
|
||||
|
||||
case MessageHandshakeSolution:
|
||||
if c.HandshakeComplete {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
var challengeHash types.Hash
|
||||
var solution uint64
|
||||
if err := binary.Read(c, binary.LittleEndian, &challengeHash); err != nil {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
if err := binary.Read(c, binary.LittleEndian, &solution); err != nil {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
if c.IsIncomingConnection {
|
||||
if hash, ok := CalculateChallengeHash(c.HandshakeChallenge, c.Owner.Consensus().Id(), solution); !ok {
|
||||
//not enough PoW
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
} else if hash != challengeHash {
|
||||
//wrong hash
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if hash, _ := CalculateChallengeHash(c.HandshakeChallenge, c.Owner.Consensus().Id(), solution); hash != challengeHash {
|
||||
//wrong hash
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.HandshakeComplete = true
|
||||
|
||||
case MessageListenPort:
|
||||
if c.ListenPort != 0 {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
if err := binary.Read(c, binary.LittleEndian, &c.ListenPort); err != nil {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
if c.ListenPort == 0 || c.ListenPort >= 65536 {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
case MessageBlockRequest:
|
||||
c.LastBlockRequest = time.Now()
|
||||
|
||||
var templateId types.Hash
|
||||
if err := binary.Read(c, binary.LittleEndian, &templateId); err != nil {
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
var block *sidechain.PoolBlock
|
||||
//if empty, return chain tip
|
||||
if templateId == types.ZeroHash {
|
||||
//todo: don't return stale
|
||||
block = c.Owner.SideChain().GetChainTip()
|
||||
} else {
|
||||
block = c.Owner.SideChain().GetPoolBlockByTemplateId(templateId)
|
||||
}
|
||||
|
||||
c.SendBlockResponse(block)
|
||||
case MessageBlockResponse:
|
||||
block := &sidechain.PoolBlock{}
|
||||
|
||||
var blockSize uint32
|
||||
if err := binary.Read(c, binary.LittleEndian, &blockSize); err != nil {
|
||||
//TODO warn
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
} else if err = block.FromReader(c); err != nil {
|
||||
//TODO warn
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
} else {
|
||||
if c.ChainTipBlockRequest {
|
||||
c.ChainTipBlockRequest = false
|
||||
|
||||
//peerHeight := block.Main.Coinbase.GenHeight
|
||||
//ourHeight :=
|
||||
//TODO: stale block
|
||||
|
||||
c.SendPeerListRequest()
|
||||
}
|
||||
|
||||
if err = c.Owner.SideChain().AddPoolBlockExternal(block); err != nil {
|
||||
//TODO warn
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:
|
||||
}
|
||||
|
||||
case MessageBlockBroadcast:
|
||||
block := &sidechain.PoolBlock{}
|
||||
var blockSize uint32
|
||||
if err := binary.Read(c, binary.LittleEndian, &blockSize); err != nil {
|
||||
//TODO warn
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
} else if err = block.FromReader(c); err != nil {
|
||||
//TODO warn
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.Owner.SideChain().PreprocessBlock(block); err != nil {
|
||||
//TODO warn
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
|
||||
//TODO: investigate different monero block mining
|
||||
|
||||
block.WantBroadcast.Store(true)
|
||||
c.LastBroadcast = time.Now()
|
||||
if err := c.Owner.SideChain().AddPoolBlockExternal(block); err != nil {
|
||||
//TODO warn
|
||||
c.Ban(DefaultBanTime)
|
||||
return
|
||||
}
|
||||
case MessagePeerListRequest:
|
||||
//TODO
|
||||
case MessagePeerListResponse:
|
||||
//TODO
|
||||
fallthrough
|
||||
default:
|
||||
c.Ban(DefaultBanTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) sendHandshakeChallenge() {
|
||||
|
@ -49,9 +307,10 @@ func (c *Client) sendHandshakeChallenge() {
|
|||
return
|
||||
}
|
||||
|
||||
var buf [1 + HandshakeChallengeSize]byte
|
||||
var buf [1 + HandshakeChallengeSize + int(unsafe.Sizeof(uint64(0)))]byte
|
||||
buf[0] = byte(MessageHandshakeChallenge)
|
||||
copy(buf[1:], c.HandshakeChallenge[:])
|
||||
binary.LittleEndian.PutUint64(buf[1+HandshakeChallengeSize:], c.Owner.PeerId())
|
||||
|
||||
_, _ = c.Write(buf[:])
|
||||
}
|
||||
|
@ -63,15 +322,13 @@ func (c *Client) sendHandshakeSolution(challenge HandshakeChallenge) {
|
|||
stop.Store(true)
|
||||
}
|
||||
|
||||
if solution, hash, ok := FindChallengeSolution(challenge, c.Owner.Pool().Consensus.Id(), stop); ok {
|
||||
if solution, hash, ok := FindChallengeSolution(challenge, c.Owner.Consensus().Id(), stop); ok {
|
||||
var buf [1 + HandshakeChallengeSize + types.HashSize]byte
|
||||
buf[0] = byte(MessageHandshakeSolution)
|
||||
copy(buf[1:], hash[:])
|
||||
binary.LittleEndian.PutUint64(buf[1+types.HashSize:], solution)
|
||||
|
||||
if _, err := c.Write(buf[:]); err != nil {
|
||||
|
||||
}
|
||||
_, _ = c.Write(buf[:])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,6 +340,23 @@ func (c *Client) Write(buf []byte) (n int, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Read reads from underlying connection, on error it will Close
|
||||
func (c *Client) Read(buf []byte) (n int, err error) {
|
||||
if n, err = c.Connection.Read(buf); err != nil && c.Closed.Load() {
|
||||
c.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReadByte reads from underlying connection, on error it will Close
|
||||
func (c *Client) ReadByte() (b byte, err error) {
|
||||
var buf [1]byte
|
||||
if _, err = c.Connection.Read(buf[:]); err != nil && c.Closed.Load() {
|
||||
c.Close()
|
||||
}
|
||||
return buf[0], err
|
||||
}
|
||||
|
||||
func (c *Client) Close() {
|
||||
if c.Closed.Load() {
|
||||
return
|
||||
|
@ -106,4 +380,6 @@ func (c *Client) Close() {
|
|||
defer c.Owner.clientsLock.Unlock()
|
||||
c.Owner.clients = slices.Delete(c.Owner.clients, i, i)
|
||||
}
|
||||
|
||||
_ = c.Connection.Close()
|
||||
}
|
||||
|
|
|
@ -4,13 +4,15 @@ import (
|
|||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"golang.org/x/exp/slices"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
@ -29,7 +31,7 @@ const (
|
|||
)
|
||||
|
||||
type Server struct {
|
||||
pool *p2pool.P2Pool
|
||||
sidechain *sidechain.SideChain
|
||||
|
||||
peerId uint64
|
||||
|
||||
|
@ -47,7 +49,7 @@ type Server struct {
|
|||
clients []*Client
|
||||
}
|
||||
|
||||
func NewServer(p2pool *p2pool.P2Pool, listenAddress string, maxOutgoingPeers, maxIncomingPeers uint32) (*Server, error) {
|
||||
func NewServer(sidechain *sidechain.SideChain, listenAddress string, maxOutgoingPeers, maxIncomingPeers uint32) (*Server, error) {
|
||||
peerId := make([]byte, int(unsafe.Sizeof(uint64(0))))
|
||||
_, err := rand.Read(peerId)
|
||||
if err != nil {
|
||||
|
@ -60,7 +62,7 @@ func NewServer(p2pool *p2pool.P2Pool, listenAddress string, maxOutgoingPeers, ma
|
|||
}
|
||||
|
||||
s := &Server{
|
||||
pool: p2pool,
|
||||
sidechain: sidechain,
|
||||
listenAddress: addrPort,
|
||||
peerId: binary.LittleEndian.Uint64(peerId),
|
||||
MaxOutgoingPeers: utils.Min(utils.Max(maxOutgoingPeers, 10), 450),
|
||||
|
@ -120,6 +122,29 @@ func (s *Server) Listen() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Connect(addr netip.AddrPort) error {
|
||||
if conn, err := net.Dial("tcp", addr.String()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
s.clientsLock.Lock()
|
||||
defer s.clientsLock.Unlock()
|
||||
client := NewClient(s, conn)
|
||||
s.clients = append(s.clients, client)
|
||||
go client.OnConnection()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Clients() []*Client {
|
||||
s.clientsLock.RLock()
|
||||
defer s.clientsLock.RUnlock()
|
||||
return slices.Clone(s.clients)
|
||||
}
|
||||
|
||||
func (s *Server) Ban(ip netip.Addr, duration time.Duration) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
func (s *Server) Close() {
|
||||
s.close.Store(true)
|
||||
}
|
||||
|
@ -128,6 +153,14 @@ func (s *Server) PeerId() uint64 {
|
|||
return s.peerId
|
||||
}
|
||||
|
||||
func (s *Server) Pool() *p2pool.P2Pool {
|
||||
return s.pool
|
||||
func (s *Server) SideChain() *sidechain.SideChain {
|
||||
return s.sidechain
|
||||
}
|
||||
|
||||
func (s *Server) Broadcast(block *sidechain.PoolBlock) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
func (s *Server) Consensus() *sidechain.Consensus {
|
||||
return s.sidechain.Consensus()
|
||||
}
|
||||
|
|
|
@ -1,15 +1,46 @@
|
|||
package p2pool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/p2p"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
)
|
||||
|
||||
type P2Pool struct {
|
||||
Consensus *sidechain.Consensus
|
||||
consensus *sidechain.Consensus
|
||||
sidechain *sidechain.SideChain
|
||||
server *p2p.Server
|
||||
}
|
||||
|
||||
func NewP2Pool() *P2Pool {
|
||||
pool := &P2Pool{}
|
||||
func NewP2Pool(consensus *sidechain.Consensus, settings map[string]string) *P2Pool {
|
||||
pool := &P2Pool{
|
||||
consensus: consensus,
|
||||
}
|
||||
var err error
|
||||
|
||||
listenAddress := fmt.Sprintf("0.0.0.0:%d", pool.Consensus().DefaultPort())
|
||||
|
||||
pool.sidechain = sidechain.NewSideChain(pool)
|
||||
|
||||
if addr, ok := settings["listen"]; ok {
|
||||
listenAddress = addr
|
||||
}
|
||||
|
||||
if pool.server, err = p2p.NewServer(pool.sidechain, listenAddress, 10, 450); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return pool
|
||||
}
|
||||
|
||||
func (p *P2Pool) Server() *p2p.Server {
|
||||
return p.server
|
||||
}
|
||||
|
||||
func (p *P2Pool) Consensus() *sidechain.Consensus {
|
||||
return p.consensus
|
||||
}
|
||||
|
||||
func (p *P2Pool) Broadcast(block *sidechain.PoolBlock) {
|
||||
p.server.Broadcast(block)
|
||||
}
|
||||
|
|
24
p2pool/p2pool_test.go
Normal file
24
p2pool/p2pool_test.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package p2pool
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"net/netip"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
client.SetClientSettings(os.Getenv("MONEROD_RPC_URL"))
|
||||
settings := make(map[string]string)
|
||||
settings["listen"] = "127.0.0.1:39889"
|
||||
if p2pool := NewP2Pool(sidechain.ConsensusDefault, settings); p2pool == nil {
|
||||
t.Fatal()
|
||||
} else {
|
||||
if err := p2pool.server.Connect(netip.MustParseAddrPort("127.0.0.1:37889")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Fatal(p2pool.Server().Listen())
|
||||
}
|
||||
}
|
103
p2pool/sidechain/cache.go
Normal file
103
p2pool/sidechain/cache.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
package sidechain
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"filippo.io/edwards25519"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"github.com/floatdrop/lru"
|
||||
)
|
||||
|
||||
type derivationCacheKey [types.HashSize * 2]byte
|
||||
type sharedDataCacheKey [types.HashSize + 8]byte
|
||||
|
||||
type sharedDataWithTag struct {
|
||||
SharedData *edwards25519.Scalar
|
||||
ViewTag uint8
|
||||
}
|
||||
|
||||
type DerivationCache struct {
|
||||
deterministicKeyCache *lru.LRU[derivationCacheKey, *crypto.KeyPair]
|
||||
derivationCache *lru.LRU[derivationCacheKey, *edwards25519.Point]
|
||||
sharedDataCache *lru.LRU[sharedDataCacheKey, sharedDataWithTag]
|
||||
ephemeralPublicKeyCache *lru.LRU[derivationCacheKey, types.Hash]
|
||||
}
|
||||
|
||||
func NewDerivationCache() *DerivationCache {
|
||||
d := &DerivationCache{}
|
||||
d.Clear()
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DerivationCache) Clear() {
|
||||
//keep a few recent blocks from the past few for uncles, and reused window miners
|
||||
//~10s per share, keys change every Monero block (2m). around 2160 max shares per 6h (window), plus uncles. 6 shares per minute.
|
||||
//each share can have up to 2160 outputs, plus uncles. each miner has its own private key per Monero block
|
||||
d.deterministicKeyCache = lru.New[derivationCacheKey, *crypto.KeyPair](4096)
|
||||
d.derivationCache = lru.New[derivationCacheKey, *edwards25519.Point](4096)
|
||||
d.sharedDataCache = lru.New[sharedDataCacheKey, sharedDataWithTag](4096 * 2160)
|
||||
d.ephemeralPublicKeyCache = lru.New[derivationCacheKey, types.Hash](4096 * 2160)
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetEphemeralPublicKey(address *address.Address, txKey types.Hash, outputIndex uint64) (types.Hash, uint8) {
|
||||
sharedData, viewTag := d.GetSharedData(address, txKey, outputIndex)
|
||||
|
||||
var key derivationCacheKey
|
||||
copy(key[:], address.SpendPub.Bytes())
|
||||
copy(key[types.HashSize:], sharedData.Bytes())
|
||||
if ephemeralPubKey := d.ephemeralPublicKeyCache.Get(key); ephemeralPubKey == nil {
|
||||
copy((*ephemeralPubKey)[:], address.GetPublicKeyForSharedData(sharedData).Bytes())
|
||||
d.ephemeralPublicKeyCache.Set(key, *ephemeralPubKey)
|
||||
return *ephemeralPubKey, viewTag
|
||||
} else {
|
||||
return *ephemeralPubKey, viewTag
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetSharedData(address *address.Address, txKey types.Hash, outputIndex uint64) (*edwards25519.Scalar, uint8) {
|
||||
derivation := d.GetDerivation(address, txKey)
|
||||
|
||||
var key sharedDataCacheKey
|
||||
copy(key[:], derivation.Bytes())
|
||||
binary.LittleEndian.PutUint64(key[types.HashSize:], outputIndex)
|
||||
|
||||
if sharedData := d.sharedDataCache.Get(key); sharedData == nil {
|
||||
var data sharedDataWithTag
|
||||
data.SharedData = crypto.GetDerivationSharedDataForOutputIndex(derivation, outputIndex)
|
||||
data.ViewTag = crypto.GetDerivationViewTagForOutputIndex(derivation, outputIndex)
|
||||
d.sharedDataCache.Set(key, data)
|
||||
return data.SharedData, data.ViewTag
|
||||
} else {
|
||||
return sharedData.SharedData, sharedData.ViewTag
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetDeterministicTransactionKey(address *address.Address, prevId types.Hash) *crypto.KeyPair {
|
||||
var key derivationCacheKey
|
||||
copy(key[:], address.SpendPub.Bytes())
|
||||
copy(key[types.HashSize:], prevId[:])
|
||||
|
||||
if kp := d.deterministicKeyCache.Get(key); kp == nil {
|
||||
data := crypto.NewKeyPairFromPrivate(address.GetDeterministicTransactionPrivateKey(prevId))
|
||||
d.deterministicKeyCache.Set(key, data)
|
||||
return data
|
||||
} else {
|
||||
return *kp
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetDerivation(address *address.Address, txKey types.Hash) *edwards25519.Point {
|
||||
var key derivationCacheKey
|
||||
copy(key[:], address.ViewPub.Bytes())
|
||||
copy(key[types.HashSize:], txKey[:])
|
||||
|
||||
if derivation := d.derivationCache.Get(key); derivation != nil {
|
||||
pK, _ := edwards25519.NewScalar().SetCanonicalBytes(txKey[:])
|
||||
data := address.GetDerivationForPrivateKey(pK)
|
||||
d.derivationCache.Set(key, data)
|
||||
return data
|
||||
} else {
|
||||
return *derivation
|
||||
}
|
||||
}
|
|
@ -20,6 +20,17 @@ const (
|
|||
NetworkStagenet
|
||||
)
|
||||
|
||||
const (
|
||||
PPLNSWindow = 2160
|
||||
BlockTime = 10
|
||||
UnclePenalty = 20
|
||||
UncleBlockDepth = 3
|
||||
)
|
||||
|
||||
type ConsensusProvider interface {
|
||||
Consensus() *Consensus
|
||||
}
|
||||
|
||||
func (n NetworkType) String() string {
|
||||
switch n {
|
||||
case NetworkInvalid:
|
||||
|
@ -149,6 +160,13 @@ func (i *Consensus) IsMini() bool {
|
|||
return i.id == ConsensusMini.id
|
||||
}
|
||||
|
||||
func (i *Consensus) DefaultPort() uint16 {
|
||||
if i.IsMini() {
|
||||
return 37888
|
||||
}
|
||||
return 37889
|
||||
}
|
||||
|
||||
func (i *Consensus) CalculateId() types.Hash {
|
||||
var buf []byte
|
||||
buf = append(buf, i.NetworkType.String()...)
|
||||
|
|
|
@ -10,10 +10,10 @@ import (
|
|||
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"github.com/holiman/uint256"
|
||||
"io"
|
||||
"math/bits"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type CoinbaseExtraTag int
|
||||
|
@ -32,15 +32,17 @@ type PoolBlock struct {
|
|||
|
||||
Side SideData
|
||||
|
||||
c struct {
|
||||
lock sync.RWMutex
|
||||
mainId types.Hash
|
||||
mainDifficulty types.Difficulty
|
||||
templateId types.Hash
|
||||
powHash types.Hash
|
||||
}
|
||||
//Temporary data structures
|
||||
cache poolBlockCache
|
||||
Depth atomic.Uint64
|
||||
Verified atomic.Bool
|
||||
Invalid atomic.Bool
|
||||
|
||||
WantBroadcast atomic.Bool
|
||||
Broadcasted atomic.Bool
|
||||
}
|
||||
|
||||
// NewShareFromExportedBytes TODO deprecate this in favor of standard serialized shares
|
||||
func NewShareFromExportedBytes(buf []byte) (*PoolBlock, error) {
|
||||
b := &PoolBlock{}
|
||||
|
||||
|
@ -68,22 +70,22 @@ func NewShareFromExportedBytes(buf []byte) (*PoolBlock, error) {
|
|||
switch version {
|
||||
case 1:
|
||||
|
||||
if _, err = io.ReadFull(reader, b.c.mainId[:]); err != nil {
|
||||
if _, err = io.ReadFull(reader, b.cache.mainId[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(reader, b.c.powHash[:]); err != nil {
|
||||
if _, err = io.ReadFull(reader, b.cache.powHash[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = binary.Read(reader, binary.BigEndian, &b.c.mainDifficulty.Hi); err != nil {
|
||||
if err = binary.Read(reader, binary.BigEndian, &b.cache.mainDifficulty.Hi); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = binary.Read(reader, binary.BigEndian, &b.c.mainDifficulty.Lo); err != nil {
|
||||
if err = binary.Read(reader, binary.BigEndian, &b.cache.mainDifficulty.Lo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.c.mainDifficulty.ReverseBytes()
|
||||
b.cache.mainDifficulty.ReverseBytes()
|
||||
|
||||
if err = binary.Read(reader, binary.BigEndian, &mainDataSize); err != nil {
|
||||
return nil, err
|
||||
|
@ -141,7 +143,7 @@ func NewShareFromExportedBytes(buf []byte) (*PoolBlock, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
b.c.templateId = types.HashFromBytes(b.CoinbaseExtra(SideTemplateId))
|
||||
b.cache.templateId = types.HashFromBytes(b.CoinbaseExtra(SideTemplateId))
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
@ -176,85 +178,126 @@ func (b *PoolBlock) CoinbaseExtra(tag CoinbaseExtraTag) []byte {
|
|||
|
||||
func (b *PoolBlock) MainId() types.Hash {
|
||||
if hash, ok := func() (types.Hash, bool) {
|
||||
b.c.lock.RLock()
|
||||
defer b.c.lock.RUnlock()
|
||||
b.cache.lock.RLock()
|
||||
defer b.cache.lock.RUnlock()
|
||||
|
||||
if b.c.mainId != types.ZeroHash {
|
||||
return b.c.mainId, true
|
||||
if b.cache.mainId != types.ZeroHash {
|
||||
return b.cache.mainId, true
|
||||
}
|
||||
return types.ZeroHash, false
|
||||
}(); ok {
|
||||
return hash
|
||||
} else {
|
||||
b.c.lock.Lock()
|
||||
defer b.c.lock.Unlock()
|
||||
if b.c.mainId == types.ZeroHash { //check again for race
|
||||
b.c.mainId = b.Main.Id()
|
||||
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.c.mainId
|
||||
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() types.Difficulty {
|
||||
if difficulty, ok := func() (types.Difficulty, bool) {
|
||||
b.c.lock.RLock()
|
||||
defer b.c.lock.RUnlock()
|
||||
b.cache.lock.RLock()
|
||||
defer b.cache.lock.RUnlock()
|
||||
|
||||
if b.c.mainDifficulty != types.ZeroDifficulty {
|
||||
return b.c.mainDifficulty, true
|
||||
if b.cache.mainDifficulty != types.ZeroDifficulty {
|
||||
return b.cache.mainDifficulty, true
|
||||
}
|
||||
return types.ZeroDifficulty, false
|
||||
}(); ok {
|
||||
return difficulty
|
||||
} else {
|
||||
b.c.lock.Lock()
|
||||
defer b.c.lock.Unlock()
|
||||
if b.c.mainDifficulty == types.ZeroDifficulty { //check again for race
|
||||
b.c.mainDifficulty = b.Main.Difficulty()
|
||||
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()
|
||||
}
|
||||
return b.c.mainDifficulty
|
||||
return b.cache.mainDifficulty
|
||||
}
|
||||
}
|
||||
|
||||
func (b *PoolBlock) SideTemplateId(consensus *Consensus) types.Hash {
|
||||
if hash, ok := func() (types.Hash, bool) {
|
||||
b.c.lock.RLock()
|
||||
defer b.c.lock.RUnlock()
|
||||
b.cache.lock.RLock()
|
||||
defer b.cache.lock.RUnlock()
|
||||
|
||||
if b.c.templateId != types.ZeroHash {
|
||||
return b.c.templateId, true
|
||||
if b.cache.templateId != types.ZeroHash {
|
||||
return b.cache.templateId, true
|
||||
}
|
||||
return types.ZeroHash, false
|
||||
}(); ok {
|
||||
return hash
|
||||
} else {
|
||||
b.c.lock.Lock()
|
||||
defer b.c.lock.Unlock()
|
||||
if b.c.templateId == types.ZeroHash { //check again for race
|
||||
b.c.templateId = consensus.CalculateSideTemplateId(&b.Main, &b.Side)
|
||||
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.Main, &b.Side)
|
||||
}
|
||||
return b.c.templateId
|
||||
return b.cache.templateId
|
||||
}
|
||||
}
|
||||
|
||||
func (b *PoolBlock) PowHash() types.Hash {
|
||||
if hash, ok := func() (types.Hash, bool) {
|
||||
b.c.lock.RLock()
|
||||
defer b.c.lock.RUnlock()
|
||||
h, _ := b.PowHashWithError()
|
||||
return h
|
||||
}
|
||||
|
||||
if b.c.powHash != types.ZeroHash {
|
||||
return b.c.powHash, true
|
||||
func (b *PoolBlock) PowHashWithError() (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
|
||||
return hash, nil
|
||||
} else {
|
||||
b.c.lock.Lock()
|
||||
defer b.c.lock.Unlock()
|
||||
if b.c.powHash == types.ZeroHash { //check again for race
|
||||
b.c.powHash = b.Main.PowHash()
|
||||
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()
|
||||
}
|
||||
return b.c.powHash
|
||||
return b.cache.powHash, err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,30 +331,79 @@ func (b *PoolBlock) FromReader(reader readerAndByteReader) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (b *PoolBlock) IsProofHigherThanDifficulty() bool {
|
||||
func (b *PoolBlock) IsProofHigherThanMainDifficulty() bool {
|
||||
r, _ := b.IsProofHigherThanMainDifficultyWithError()
|
||||
return r
|
||||
}
|
||||
|
||||
func (b *PoolBlock) IsProofHigherThanMainDifficultyWithError() (bool, error) {
|
||||
if mainDifficulty := b.MainDifficulty(); mainDifficulty == types.ZeroDifficulty {
|
||||
//TODO: err
|
||||
return false
|
||||
return false, errors.New("could not get main difficulty")
|
||||
} else if powHash, err := b.PowHashWithError(); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return b.GetProofDifficulty().Cmp(mainDifficulty) >= 0
|
||||
return mainDifficulty.CheckPoW(powHash), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *PoolBlock) GetProofDifficulty() types.Difficulty {
|
||||
base := uint256.NewInt(0).SetBytes32(bytes.Repeat([]byte{0xff}, 32))
|
||||
func (b *PoolBlock) IsProofHigherThanDifficulty() bool {
|
||||
r, _ := b.IsProofHigherThanDifficultyWithError()
|
||||
return r
|
||||
}
|
||||
|
||||
powHash := b.PowHash()
|
||||
pow := uint256.NewInt(0).SetBytes32(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{}
|
||||
func (b *PoolBlock) IsProofHigherThanDifficultyWithError() (bool, error) {
|
||||
if powHash, err := b.PowHashWithError(); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return b.Side.Difficulty.CheckPoW(powHash), nil
|
||||
}
|
||||
|
||||
powResult := uint256.NewInt(0).Div(base, pow).Bytes32()
|
||||
return types.DifficultyFromBytes(powResult[16:])
|
||||
}
|
||||
|
||||
func (b *PoolBlock) GetAddress() *address.Address {
|
||||
//TODO: support other than Main network
|
||||
return address.FromRawAddress(moneroutil.MainNetwork, b.Side.PublicSpendKey, b.Side.PublicViewKey)
|
||||
}
|
||||
|
||||
func (b *PoolBlock) GetPackedAddress() address.PackedAddress {
|
||||
return address.PackedAddress{SpendPub: b.Side.PublicSpendKey, ViewPub: b.Side.PublicViewKey}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -71,24 +71,24 @@ func TestPoolBlockDecode(t *testing.T) {
|
|||
|
||||
proofResult, _ := types.DifficultyFromString("00000000000000000000006ef6334490")
|
||||
|
||||
if block.GetProofDifficulty().Cmp(proofResult) != 0 {
|
||||
t.Fatalf("expected PoW difficulty %s, got %s", proofResult.String(), block.GetProofDifficulty().String())
|
||||
if types.DifficultyFromPoW(block.PowHash()).Cmp(proofResult) != 0 {
|
||||
t.Fatalf("expected PoW difficulty %s, got %s", proofResult.String(), types.DifficultyFromPoW(block.PowHash()).String())
|
||||
}
|
||||
|
||||
t.Log(block.Main.Id().String())
|
||||
//t.Log(block.Main.PowHash().String())
|
||||
//t.Log(block.Main.PowHash().String())
|
||||
|
||||
if !block.IsProofHigherThanDifficulty() {
|
||||
if !block.IsProofHigherThanMainDifficulty() {
|
||||
t.Fatal("expected proof higher than difficulty")
|
||||
}
|
||||
|
||||
block.c.powHash[31] = 1
|
||||
block.cache.powHash[31] = 1
|
||||
|
||||
if block.IsProofHigherThanDifficulty() {
|
||||
if block.IsProofHigherThanMainDifficulty() {
|
||||
t.Fatal("expected proof lower than difficulty")
|
||||
}
|
||||
|
||||
log.Print(block.GetProofDifficulty().String())
|
||||
log.Print(types.DifficultyFromPoW(block.PowHash()).String())
|
||||
log.Print(block.MainDifficulty().String())
|
||||
}
|
||||
|
|
10
p2pool/sidechain/share.go
Normal file
10
p2pool/sidechain/share.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package sidechain
|
||||
|
||||
import "git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
|
||||
type Shares []Share
|
||||
|
||||
type Share struct {
|
||||
Weight uint64
|
||||
Address address.PackedAddress
|
||||
}
|
962
p2pool/sidechain/sidechain.go
Normal file
962
p2pool/sidechain/sidechain.go
Normal file
|
@ -0,0 +1,962 @@
|
|||
package sidechain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
|
||||
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"golang.org/x/exp/slices"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type ConsensusBroadcaster interface {
|
||||
ConsensusProvider
|
||||
Broadcast(block *PoolBlock)
|
||||
}
|
||||
|
||||
type SideChain struct {
|
||||
cache *DerivationCache
|
||||
server ConsensusBroadcaster
|
||||
|
||||
blocksByLock sync.RWMutex
|
||||
blocksByTemplateId map[types.Hash]*PoolBlock
|
||||
blocksByHeight map[uint64][]*PoolBlock
|
||||
|
||||
chainTip atomic.Pointer[PoolBlock]
|
||||
currentDifficulty atomic.Pointer[types.Difficulty]
|
||||
}
|
||||
|
||||
func NewSideChain(server ConsensusBroadcaster) *SideChain {
|
||||
return &SideChain{
|
||||
cache: NewDerivationCache(),
|
||||
server: server,
|
||||
blocksByTemplateId: make(map[types.Hash]*PoolBlock),
|
||||
blocksByHeight: make(map[uint64][]*PoolBlock),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SideChain) Consensus() *Consensus {
|
||||
return c.server.Consensus()
|
||||
}
|
||||
|
||||
func (c *SideChain) PreprocessBlock(block *PoolBlock) (err error) {
|
||||
c.blocksByLock.RLock()
|
||||
defer c.blocksByLock.RUnlock()
|
||||
|
||||
if len(block.Main.Coinbase.Outputs) == 0 {
|
||||
if outputs := c.getOutputs(block); outputs == nil {
|
||||
return errors.New("nil transaction outputs")
|
||||
} else {
|
||||
block.Main.Coinbase.Outputs = outputs
|
||||
}
|
||||
|
||||
if outputBlob, err := block.Main.Coinbase.OutputsBlob(); err != nil {
|
||||
return err
|
||||
} else if uint64(len(outputBlob)) != block.Main.Coinbase.OutputsBlobSize {
|
||||
return errors.New("invalid output blob size")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SideChain) AddPoolBlockExternal(block *PoolBlock) (err error) {
|
||||
// Technically some p2pool node could keep stuffing block with transactions until reward is less than 0.6 XMR
|
||||
// But default transaction picking algorithm never does that. It's better to just ban such nodes
|
||||
if block.Main.Coinbase.TotalReward < 600000000000 {
|
||||
return errors.New("block reward too low")
|
||||
}
|
||||
|
||||
// Enforce deterministic tx keys starting from v15
|
||||
if block.Main.MajorVersion >= monero.HardForkViewTagsVersion {
|
||||
kP := c.cache.GetDeterministicTransactionKey(block.GetAddress(), block.Main.PreviousId)
|
||||
if bytes.Compare(block.CoinbaseExtra(SideCoinbasePublicKey), kP.PublicKey.Bytes()) != 0 || bytes.Compare(block.Side.CoinbasePrivateKey[:], kP.PrivateKey.Bytes()) != 0 {
|
||||
return errors.New("invalid deterministic transaction keys")
|
||||
}
|
||||
}
|
||||
// Both tx types are allowed by Monero consensus during v15 because it needs to process pre-fork mempool transactions,
|
||||
// but P2Pool can switch to using only TXOUT_TO_TAGGED_KEY for miner payouts starting from v15
|
||||
expectedTxType := c.GetTransactionOutputType(block.Main.MajorVersion)
|
||||
|
||||
if err = c.PreprocessBlock(block); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, o := range block.Main.Coinbase.Outputs {
|
||||
if o.Type != expectedTxType {
|
||||
return errors.New("unexpected transaction type")
|
||||
}
|
||||
}
|
||||
|
||||
templateId := c.Consensus().CalculateSideTemplateId(&block.Main, &block.Side)
|
||||
if templateId != block.SideTemplateId(c.Consensus()) {
|
||||
return fmt.Errorf("invalid template id %s, expected %s", templateId.String(), block.SideTemplateId(c.Consensus()).String())
|
||||
}
|
||||
|
||||
if block.Side.Difficulty.Cmp64(c.Consensus().MinimumDifficulty) < 0 {
|
||||
return fmt.Errorf("block has invalid difficulty %d, expected >= %d", block.Side.Difficulty.Lo, c.Consensus().MinimumDifficulty)
|
||||
}
|
||||
|
||||
//TODO: cache?
|
||||
expectedDifficulty := c.GetDifficulty(block)
|
||||
tooLowDiff := block.Side.Difficulty.Cmp(expectedDifficulty) < 0
|
||||
|
||||
if otherBlock := c.GetPoolBlockByTemplateId(templateId); otherBlock != nil {
|
||||
//already added
|
||||
//TODO: specifically check Main id for nonce changes! p2pool does not do this
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is mainly an anti-spam measure, not an actual verification step
|
||||
if tooLowDiff {
|
||||
// Reduce required diff by 50% (by doubling this block's diff) to account for alternative chains
|
||||
diff2 := block.Side.Difficulty.Mul64(2)
|
||||
tip := c.GetChainTip()
|
||||
for tmp := tip; tmp != nil && (tmp.Side.Height+c.Consensus().ChainWindowSize > tip.Side.Height); tmp = c.GetParent(tmp) {
|
||||
if diff2.Cmp(tmp.Side.Difficulty) >= 0 {
|
||||
tooLowDiff = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO log
|
||||
|
||||
if tooLowDiff {
|
||||
//TODO warn
|
||||
return nil
|
||||
}
|
||||
|
||||
// This check is not always possible to perform because of mainchain reorgs
|
||||
//TODO: cache current miner data?
|
||||
if data := mainblock.GetBlockHeaderByHash(block.Main.PreviousId); data != nil {
|
||||
if data.Height+1 != block.Main.Coinbase.GenHeight {
|
||||
return fmt.Errorf("wrong mainchain height %d, expected %d", block.Main.Coinbase.GenHeight, data.Height+1)
|
||||
}
|
||||
} else {
|
||||
//TODO warn unknown block, reorg
|
||||
}
|
||||
|
||||
if _, err := block.PowHashWithError(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
//TODO: fast monero submission
|
||||
if isHigher, err := block.IsProofHigherThanDifficultyWithError(); err != nil {
|
||||
return err
|
||||
} else if !isHigher {
|
||||
return fmt.Errorf("not enough PoW for height = %d, mainchain height %d", block.Side.Height, block.Main.Coinbase.GenHeight)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: block found section
|
||||
|
||||
return c.AddPoolBlock(block)
|
||||
}
|
||||
|
||||
func (c *SideChain) AddPoolBlock(block *PoolBlock) (err error) {
|
||||
c.blocksByLock.Lock()
|
||||
defer c.blocksByLock.Unlock()
|
||||
if _, ok := c.blocksByTemplateId[block.SideTemplateId(c.Consensus())]; ok {
|
||||
//already inserted
|
||||
//TODO WARN
|
||||
return nil
|
||||
}
|
||||
c.blocksByTemplateId[block.SideTemplateId(c.Consensus())] = block
|
||||
|
||||
if l, ok := c.blocksByHeight[block.Side.Height]; ok {
|
||||
c.blocksByHeight[block.Side.Height] = append(l, block)
|
||||
} else {
|
||||
c.blocksByHeight[block.Side.Height] = []*PoolBlock{block}
|
||||
}
|
||||
|
||||
c.updateDepths(block)
|
||||
|
||||
if block.Verified.Load() {
|
||||
if !block.Invalid.Load() {
|
||||
c.updateChainTip(block)
|
||||
}
|
||||
} else {
|
||||
c.verifyLoop(block)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SideChain) verifyLoop(block *PoolBlock) {
|
||||
// PoW is already checked at this point
|
||||
|
||||
blocksToVerify := make([]*PoolBlock, 1, 8)
|
||||
blocksToVerify[0] = block
|
||||
var highestBlock *PoolBlock
|
||||
for len(blocksToVerify) != 0 {
|
||||
block = blocksToVerify[len(blocksToVerify)-1]
|
||||
blocksToVerify = blocksToVerify[:len(blocksToVerify)-1]
|
||||
|
||||
if block.Verified.Load() {
|
||||
continue
|
||||
}
|
||||
|
||||
c.verifyBlock(block)
|
||||
|
||||
if !block.Verified.Load() {
|
||||
//TODO log
|
||||
continue
|
||||
}
|
||||
if block.Invalid.Load() {
|
||||
//TODO log
|
||||
} else {
|
||||
//TODO log
|
||||
|
||||
// This block is now verified
|
||||
|
||||
if isLongerChain, _ := c.IsLongerChain(highestBlock, block); isLongerChain {
|
||||
highestBlock = block
|
||||
} else if highestBlock != nil && highestBlock.Side.Height > block.Side.Height {
|
||||
//TODO log
|
||||
}
|
||||
|
||||
if block.WantBroadcast.Load() && !block.Broadcasted.Swap(true) {
|
||||
if block.Depth.Load() < UncleBlockDepth {
|
||||
c.server.Broadcast(block)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO save
|
||||
|
||||
// Try to verify blocks on top of this one
|
||||
for i := uint64(1); i <= UncleBlockDepth; i++ {
|
||||
blocksToVerify = append(blocksToVerify, c.blocksByHeight[block.Side.Height+i]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if highestBlock != nil {
|
||||
c.updateChainTip(highestBlock)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SideChain) verifyBlock(block *PoolBlock) {
|
||||
// Genesis
|
||||
if block.Side.Height == 0 {
|
||||
if block.Side.Parent != types.ZeroHash ||
|
||||
len(block.Side.Uncles) != 0 ||
|
||||
block.Side.Difficulty.Cmp64(c.Consensus().MinimumDifficulty) != 0 ||
|
||||
block.Side.CumulativeDifficulty.Cmp64(c.Consensus().MinimumDifficulty) != 0 {
|
||||
block.Invalid.Store(true)
|
||||
}
|
||||
block.Verified.Store(true)
|
||||
//this does not verify coinbase outputs, but that's fine
|
||||
return
|
||||
}
|
||||
|
||||
// Deep block
|
||||
//
|
||||
// Blocks in PPLNS window (m_chainWindowSize) require up to m_chainWindowSize earlier blocks to verify
|
||||
// If a block is deeper than m_chainWindowSize * 2 - 1 it can't influence blocks in PPLNS window
|
||||
// Also, having so many blocks on top of this one means it was verified by the network at some point
|
||||
// We skip checks in this case to make pruning possible
|
||||
if block.Depth.Load() >= c.Consensus().ChainWindowSize*2 {
|
||||
//TODO log
|
||||
block.Verified.Store(true)
|
||||
block.Invalid.Store(false)
|
||||
return
|
||||
}
|
||||
|
||||
//Regular block
|
||||
//Must have parent
|
||||
if block.Side.Parent == types.ZeroHash {
|
||||
block.Verified.Store(true)
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
|
||||
if parent := c.getParent(block); parent != nil {
|
||||
// If it's invalid then this block is also invalid
|
||||
if parent.Invalid.Load() {
|
||||
block.Verified.Store(false)
|
||||
block.Invalid.Store(false)
|
||||
return
|
||||
}
|
||||
|
||||
expectedHeight := parent.Side.Height + 1
|
||||
if expectedHeight != block.Side.Height {
|
||||
//TODO WARN
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Uncle hashes must be sorted in the ascending order to prevent cheating when the same hash is repeated multiple times
|
||||
for i, uncleId := range block.Side.Uncles {
|
||||
if i == 1 {
|
||||
continue
|
||||
}
|
||||
if bytes.Compare(block.Side.Uncles[i-1][:], uncleId[:]) < 0 {
|
||||
//TODO warn
|
||||
block.Verified.Store(true)
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
expectedCummulativeDifficulty := parent.Side.CumulativeDifficulty.Add(block.Side.Difficulty)
|
||||
|
||||
//check uncles
|
||||
|
||||
minedBlocks := make([]types.Hash, 0, len(block.Side.Uncles)*UncleBlockDepth*2+1)
|
||||
{
|
||||
tmp := parent
|
||||
n := utils.Min(UncleBlockDepth, block.Side.Height+1)
|
||||
for i := uint64(0); tmp != nil && i < n; i++ {
|
||||
minedBlocks = append(minedBlocks, tmp.SideTemplateId(c.Consensus()))
|
||||
for _, uncleId := range tmp.Side.Uncles {
|
||||
minedBlocks = append(minedBlocks, uncleId)
|
||||
}
|
||||
tmp = c.getParent(tmp)
|
||||
}
|
||||
}
|
||||
|
||||
for _, uncleId := range block.Side.Uncles {
|
||||
// Empty hash is only used in the genesis block and only for its parent
|
||||
// Uncles can't be empty
|
||||
if uncleId == types.ZeroHash {
|
||||
//TODO warn
|
||||
block.Verified.Store(true)
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Can't mine the same uncle block twice
|
||||
if slices.Index(minedBlocks, uncleId) != -1 {
|
||||
//TODO warn
|
||||
block.Verified.Store(true)
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
|
||||
if uncle := c.getPoolBlockByTemplateId(uncleId); uncle == nil {
|
||||
block.Verified.Store(false)
|
||||
return
|
||||
} else if uncle.Invalid.Load() {
|
||||
// If it's invalid then this block is also invalid
|
||||
block.Verified.Store(true)
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
} else if uncle.Side.Height >= block.Side.Height || (uncle.Side.Height+UncleBlockDepth < block.Side.Height) {
|
||||
//TODO warn
|
||||
block.Verified.Store(true)
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
} else {
|
||||
// Check that uncle and parent have the same ancestor (they must be on the same chain)
|
||||
tmp := parent
|
||||
for tmp.Side.Height > uncle.Side.Height {
|
||||
tmp = c.getParent(tmp)
|
||||
if tmp == nil {
|
||||
//TODO warn
|
||||
block.Verified.Store(true)
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if tmp.Side.Height < uncle.Side.Height {
|
||||
//TODO warn
|
||||
block.Verified.Store(true)
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
|
||||
if sameChain := func() bool {
|
||||
tmp2 := uncle
|
||||
for j := uint64(0); j < UncleBlockDepth && tmp != nil && tmp2 != nil && (tmp.Side.Height+UncleBlockDepth >= block.Side.Height); j++ {
|
||||
if tmp.Side.Parent == tmp2.Side.Parent {
|
||||
return true
|
||||
}
|
||||
tmp = c.getParent(tmp)
|
||||
tmp2 = c.getParent(tmp2)
|
||||
}
|
||||
return false
|
||||
}(); !sameChain {
|
||||
//TODO warn
|
||||
block.Verified.Store(true)
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
|
||||
expectedCummulativeDifficulty = expectedCummulativeDifficulty.Add(uncle.Side.Difficulty)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// We can verify this block now (all previous blocks in the window are verified and valid)
|
||||
// It can still turn out to be invalid
|
||||
block.Verified.Store(true)
|
||||
|
||||
if !block.Side.CumulativeDifficulty.Equals(expectedCummulativeDifficulty) {
|
||||
//TODO warn
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify difficulty and miner rewards only for blocks in PPLNS window
|
||||
if block.Depth.Load() >= c.Consensus().ChainWindowSize {
|
||||
//TODO warn
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
|
||||
if diff := c.getDifficulty(parent); diff == types.ZeroDifficulty {
|
||||
//TODO warn
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
} else if diff != block.Side.Difficulty {
|
||||
//TODO warn
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
|
||||
if shares := c.getShares(block); len(shares) == 0 {
|
||||
//TODO warn
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
} else if len(shares) != len(block.Main.Coinbase.Outputs) {
|
||||
//TODO warn
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
} else if totalReward := func() (result uint64) {
|
||||
for _, o := range block.Main.Coinbase.Outputs {
|
||||
result += o.Reward
|
||||
}
|
||||
return
|
||||
}(); totalReward != block.Main.Coinbase.TotalReward {
|
||||
//TODO warn
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
} else if rewards := c.SplitReward(totalReward, shares); len(rewards) != len(block.Main.Coinbase.Outputs) {
|
||||
//TODO warn
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
} else {
|
||||
for i, reward := range rewards {
|
||||
out := block.Main.Coinbase.Outputs[i]
|
||||
if reward != out.Reward {
|
||||
//TODO warn
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
|
||||
if ephPublicKey, viewTag := c.cache.GetEphemeralPublicKey(shares[i].Address.ToAddress(), block.Side.CoinbasePrivateKey, uint64(i)); ephPublicKey != out.EphemeralPublicKey {
|
||||
//TODO warn
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
} else if out.Type == transaction.TxOutToTaggedKey && viewTag != out.ViewTag {
|
||||
//TODO warn
|
||||
block.Invalid.Store(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All checks passed
|
||||
block.Invalid.Store(false)
|
||||
return
|
||||
} else {
|
||||
block.Verified.Store(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SideChain) updateDepths(block *PoolBlock) {
|
||||
for i := uint64(1); i <= UncleBlockDepth; i++ {
|
||||
for _, child := range c.blocksByHeight[block.Side.Height+i] {
|
||||
if child.Side.Parent.Equals(block.SideTemplateId(c.Consensus())) {
|
||||
if i != 1 {
|
||||
//TODO: error
|
||||
} else {
|
||||
block.Depth.Store(utils.Max(block.Depth.Load(), child.Depth.Load()+1))
|
||||
}
|
||||
}
|
||||
|
||||
if ix := slices.Index(child.Side.Uncles, block.SideTemplateId(c.Consensus())); ix != 1 {
|
||||
block.Depth.Store(utils.Max(block.Depth.Load(), child.Depth.Load()+1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blocksToUpdate := make([]*PoolBlock, 1, 8)
|
||||
blocksToUpdate[0] = block
|
||||
|
||||
for len(blocksToUpdate) != 0 {
|
||||
block = blocksToUpdate[len(blocksToUpdate)-1]
|
||||
blocksToUpdate = blocksToUpdate[:len(blocksToUpdate)-1]
|
||||
|
||||
blockDepth := block.Depth.Load()
|
||||
// Verify this block and possibly other blocks on top of it when we're sure it will get verified
|
||||
if !block.Verified.Load() && (blockDepth >= c.Consensus().ChainWindowSize*2 || block.Side.Height == 0) {
|
||||
c.verifyLoop(block)
|
||||
}
|
||||
|
||||
if parent := c.getParent(block); parent != nil {
|
||||
if parent.Side.Height+1 != block.Side.Height {
|
||||
//TODO error
|
||||
}
|
||||
|
||||
if parent.Depth.Load() < blockDepth+1 {
|
||||
parent.Depth.Store(blockDepth + 1)
|
||||
blocksToUpdate = append(blocksToUpdate, parent)
|
||||
}
|
||||
}
|
||||
|
||||
for _, uncleId := range block.Side.Uncles {
|
||||
if uncle := c.getPoolBlockByTemplateId(uncleId); uncle != nil {
|
||||
if uncle.Side.Height >= block.Side.Height || (uncle.Side.Height+UncleBlockDepth < block.Side.Height) {
|
||||
//TODO: error
|
||||
}
|
||||
|
||||
d := block.Side.Height - uncle.Side.Height
|
||||
if uncle.Depth.Load() < blockDepth+d {
|
||||
uncle.Depth.Store(blockDepth + d)
|
||||
blocksToUpdate = append(blocksToUpdate, uncle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SideChain) updateChainTip(block *PoolBlock) {
|
||||
if !block.Verified.Load() || block.Invalid.Load() {
|
||||
//todo err
|
||||
return
|
||||
}
|
||||
|
||||
if block.Depth.Load() >= c.Consensus().ChainWindowSize {
|
||||
//TODO err
|
||||
return
|
||||
}
|
||||
|
||||
tip := c.GetChainTip()
|
||||
|
||||
if isLongerChain, isAlternative := c.IsLongerChain(tip, block); isLongerChain {
|
||||
if diff := c.getDifficulty(block); diff != types.ZeroDifficulty {
|
||||
c.chainTip.Store(block)
|
||||
c.currentDifficulty.Store(&diff)
|
||||
//TODO log
|
||||
|
||||
block.WantBroadcast.Store(true)
|
||||
|
||||
if isAlternative {
|
||||
c.cache.Clear()
|
||||
}
|
||||
|
||||
c.pruneOldBlocks()
|
||||
}
|
||||
} else if block.Side.Height > tip.Side.Height {
|
||||
//TODO log
|
||||
} else if block.Side.Height+UncleBlockDepth > tip.Side.Height {
|
||||
//TODO: log
|
||||
}
|
||||
|
||||
if block.WantBroadcast.Load() && !block.Broadcasted.Swap(true) {
|
||||
c.server.Broadcast(block)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *SideChain) pruneOldBlocks() {
|
||||
//TODO
|
||||
}
|
||||
|
||||
func (c *SideChain) GetTransactionOutputType(majorVersion uint8) uint8 {
|
||||
// Both tx types are allowed by Monero consensus during v15 because it needs to process pre-fork mempool transactions,
|
||||
// but P2Pool can switch to using only TXOUT_TO_TAGGED_KEY for miner payouts starting from v15
|
||||
expectedTxType := uint8(transaction.TxOutToKey)
|
||||
if majorVersion >= monero.HardForkViewTagsVersion {
|
||||
expectedTxType = transaction.TxOutToTaggedKey
|
||||
}
|
||||
|
||||
return expectedTxType
|
||||
}
|
||||
|
||||
func (c *SideChain) getOutputs(block *PoolBlock) []*transaction.CoinbaseTransactionOutput {
|
||||
if b := c.GetPoolBlockByTemplateId(block.SideTemplateId(c.Consensus())); b != nil {
|
||||
return b.Main.Coinbase.Outputs
|
||||
}
|
||||
|
||||
tmpShares := c.getShares(block)
|
||||
tmpRewards := c.SplitReward(block.Main.Coinbase.TotalReward, tmpShares)
|
||||
|
||||
if tmpShares == nil || tmpRewards == nil || len(tmpRewards) != len(tmpShares) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
var counter atomic.Uint32
|
||||
n := uint32(len(tmpShares))
|
||||
|
||||
outputs := make([]*transaction.CoinbaseTransactionOutput, n)
|
||||
|
||||
txType := c.GetTransactionOutputType(block.Main.MajorVersion)
|
||||
|
||||
const helperRoutines = 4
|
||||
|
||||
//TODO: check
|
||||
|
||||
for i := 0; i < helperRoutines; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
workIndex := counter.Add(1)
|
||||
if workIndex > n {
|
||||
return
|
||||
}
|
||||
|
||||
output := &transaction.CoinbaseTransactionOutput{
|
||||
Index: uint64(workIndex - 1),
|
||||
Type: txType,
|
||||
}
|
||||
output.Reward = tmpRewards[output.Index]
|
||||
output.EphemeralPublicKey, output.ViewTag = c.cache.GetEphemeralPublicKey(tmpShares[output.Index].Address.ToAddress(), block.Side.CoinbasePrivateKey, output.Index)
|
||||
|
||||
outputs[output.Index] = output
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return outputs
|
||||
}
|
||||
|
||||
func (c *SideChain) SplitReward(reward uint64, shares Shares) (rewards []uint64) {
|
||||
var totalWeight uint64
|
||||
for i := range shares {
|
||||
totalWeight += shares[i].Weight
|
||||
}
|
||||
|
||||
if totalWeight == 0 {
|
||||
//TODO: err
|
||||
return nil
|
||||
}
|
||||
|
||||
rewards = make([]uint64, len(shares))
|
||||
|
||||
var w uint64
|
||||
var rewardGiven uint64
|
||||
|
||||
for i := range shares {
|
||||
w += shares[i].Weight
|
||||
nextValue := types.DifficultyFrom64(w).Mul64(reward).Div64(totalWeight).Lo
|
||||
rewards[i] = nextValue - rewardGiven
|
||||
rewardGiven = nextValue
|
||||
}
|
||||
|
||||
// Double check that we gave out the exact amount
|
||||
rewardGiven = 0
|
||||
for _, r := range rewards {
|
||||
rewardGiven += r
|
||||
}
|
||||
if rewardGiven != reward {
|
||||
return nil
|
||||
}
|
||||
|
||||
return rewards
|
||||
}
|
||||
|
||||
func (c *SideChain) getShares(tip *PoolBlock) (shares Shares) {
|
||||
shares = make([]Share, 0, c.Consensus().ChainWindowSize*2)
|
||||
|
||||
var blockDepth uint64
|
||||
|
||||
cur := tip
|
||||
var curShare Share
|
||||
for {
|
||||
curShare.Weight = cur.Side.Difficulty.Lo
|
||||
curShare.Address = cur.GetPackedAddress()
|
||||
|
||||
for _, uncleId := range cur.Side.Uncles {
|
||||
if uncle := c.GetPoolBlockByTemplateId(uncleId); uncle == nil {
|
||||
//cannot find uncles
|
||||
return nil
|
||||
} else {
|
||||
// Skip uncles which are already out of PPLNS window
|
||||
if (tip.Side.Height - uncle.Side.Height) >= c.Consensus().ChainWindowSize {
|
||||
continue
|
||||
}
|
||||
|
||||
// Take some % of uncle's weight into this share
|
||||
product := uncle.Side.Difficulty.Mul64(c.Consensus().UnclePenalty)
|
||||
unclePenalty := product.Div64(100)
|
||||
curShare.Weight += unclePenalty.Lo
|
||||
|
||||
shares = append(shares, Share{Weight: uncle.Side.Difficulty.Sub(unclePenalty).Lo, Address: uncle.GetPackedAddress()})
|
||||
}
|
||||
}
|
||||
|
||||
shares = append(shares, curShare)
|
||||
|
||||
blockDepth++
|
||||
|
||||
if blockDepth >= c.Consensus().ChainWindowSize {
|
||||
break
|
||||
}
|
||||
|
||||
// Reached the genesis block so we're done
|
||||
if cur.Side.Height == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
cur = c.getPoolBlockByTemplateId(cur.SideTemplateId(c.Consensus()))
|
||||
|
||||
if cur == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Combine shares with the same wallet addresses
|
||||
slices.SortFunc(shares, func(a Share, b Share) bool {
|
||||
return a.Address.Compare(b.Address) < 0
|
||||
})
|
||||
|
||||
k := 0
|
||||
for i := 0; i < len(shares); i++ {
|
||||
if shares[i].Address.Compare(shares[k].Address) == 0 {
|
||||
shares[k].Weight += shares[i].Weight
|
||||
} else {
|
||||
k++
|
||||
shares[k].Address = shares[i].Address
|
||||
shares[k].Weight = shares[i].Weight
|
||||
}
|
||||
}
|
||||
|
||||
return slices.Clone(shares[:k+1])
|
||||
}
|
||||
|
||||
type DifficultyData struct {
|
||||
CumulativeDifficulty types.Difficulty
|
||||
Timestamp uint64
|
||||
}
|
||||
|
||||
func (c *SideChain) GetDifficulty(tip *PoolBlock) types.Difficulty {
|
||||
c.blocksByLock.RLock()
|
||||
defer c.blocksByLock.RUnlock()
|
||||
return c.getDifficulty(tip)
|
||||
}
|
||||
|
||||
func (c *SideChain) getDifficulty(tip *PoolBlock) types.Difficulty {
|
||||
difficultyData := make([]DifficultyData, 0, c.Consensus().ChainWindowSize*2)
|
||||
cur := tip
|
||||
var blockDepth uint64
|
||||
var oldestTimestamp uint64 = math.MaxUint64
|
||||
for {
|
||||
oldestTimestamp = utils.Min(oldestTimestamp, cur.Main.Timestamp)
|
||||
difficultyData = append(difficultyData, DifficultyData{CumulativeDifficulty: cur.Side.CumulativeDifficulty, Timestamp: cur.Main.Timestamp})
|
||||
|
||||
for _, uncleId := range cur.Side.Uncles {
|
||||
if uncle := c.getPoolBlockByTemplateId(uncleId); uncle == nil {
|
||||
//cannot find uncles
|
||||
return types.ZeroDifficulty
|
||||
} else {
|
||||
// Skip uncles which are already out of PPLNS window
|
||||
if (tip.Side.Height - uncle.Side.Height) >= c.Consensus().ChainWindowSize {
|
||||
continue
|
||||
}
|
||||
|
||||
oldestTimestamp = utils.Min(oldestTimestamp, uncle.Main.Timestamp)
|
||||
difficultyData = append(difficultyData, DifficultyData{CumulativeDifficulty: uncle.Side.CumulativeDifficulty, Timestamp: uncle.Main.Timestamp})
|
||||
}
|
||||
}
|
||||
|
||||
blockDepth++
|
||||
|
||||
if blockDepth >= c.Consensus().ChainWindowSize {
|
||||
break
|
||||
}
|
||||
|
||||
// Reached the genesis block so we're done
|
||||
if cur.Side.Height == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
cur = c.getPoolBlockByTemplateId(cur.SideTemplateId(c.Consensus()))
|
||||
|
||||
if cur == nil {
|
||||
return types.ZeroDifficulty
|
||||
}
|
||||
}
|
||||
|
||||
// Discard 10% oldest and 10% newest (by timestamp) blocks
|
||||
tmpTimestamps := make([]uint32, 0, len(difficultyData))
|
||||
for i := range difficultyData {
|
||||
tmpTimestamps = append(tmpTimestamps, uint32(difficultyData[i].Timestamp-oldestTimestamp))
|
||||
}
|
||||
|
||||
cutSize := (len(difficultyData) + 9) / 10
|
||||
index1 := cutSize - 1
|
||||
index2 := len(difficultyData) - cutSize
|
||||
|
||||
//TODO: replace this with introspective selection, use order for now
|
||||
slices.Sort(tmpTimestamps)
|
||||
|
||||
timestamp1 := oldestTimestamp + uint64(tmpTimestamps[index1])
|
||||
timestamp2 := oldestTimestamp + uint64(tmpTimestamps[index2])
|
||||
|
||||
deltaT := uint64(1)
|
||||
if timestamp2 > timestamp1 {
|
||||
deltaT = timestamp2 - timestamp1
|
||||
}
|
||||
|
||||
var diff1 = types.Difficulty{Hi: math.MaxUint64, Lo: math.MaxUint64}
|
||||
var diff2 types.Difficulty
|
||||
|
||||
for i := range difficultyData {
|
||||
d := &difficultyData[i]
|
||||
if timestamp1 <= d.Timestamp && d.Timestamp <= timestamp2 {
|
||||
if d.CumulativeDifficulty.Cmp(diff1) < 0 {
|
||||
diff1 = d.CumulativeDifficulty
|
||||
}
|
||||
if diff2.Cmp(d.CumulativeDifficulty) < 0 {
|
||||
diff2 = d.CumulativeDifficulty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: p2pool uses uint64 instead of full range here: This is correct as long as the difference between two 128-bit difficulties is less than 2^64, even if it wraps
|
||||
deltaDiff := diff2.Sub(diff1)
|
||||
|
||||
product := deltaDiff.Mul64(c.Consensus().TargetBlockTime)
|
||||
|
||||
if product.Cmp64(deltaT) >= 0 {
|
||||
//TODO: error, calculated difficulty too high
|
||||
return types.ZeroDifficulty
|
||||
}
|
||||
|
||||
curDifficulty := product.Div64(deltaT)
|
||||
|
||||
if curDifficulty.Cmp64(c.Consensus().MinimumDifficulty) < 0 {
|
||||
curDifficulty = types.DifficultyFrom64(c.Consensus().MinimumDifficulty)
|
||||
}
|
||||
return curDifficulty
|
||||
}
|
||||
|
||||
func (c *SideChain) GetParent(block *PoolBlock) *PoolBlock {
|
||||
c.blocksByLock.RLock()
|
||||
defer c.blocksByLock.RUnlock()
|
||||
return c.getParent(block)
|
||||
}
|
||||
|
||||
func (c *SideChain) getParent(block *PoolBlock) *PoolBlock {
|
||||
return c.getPoolBlockByTemplateId(block.Side.Parent)
|
||||
}
|
||||
|
||||
func (c *SideChain) GetPoolBlockByTemplateId(id types.Hash) *PoolBlock {
|
||||
c.blocksByLock.RLock()
|
||||
defer c.blocksByLock.RUnlock()
|
||||
return c.getPoolBlockByTemplateId(id)
|
||||
}
|
||||
|
||||
func (c *SideChain) getPoolBlockByTemplateId(id types.Hash) *PoolBlock {
|
||||
return c.blocksByTemplateId[id]
|
||||
}
|
||||
|
||||
func (c *SideChain) GetChainTip() *PoolBlock {
|
||||
return c.chainTip.Load()
|
||||
}
|
||||
|
||||
func (c *SideChain) IsLongerChain(block, candidate *PoolBlock) (isLonger, isAlternative bool) {
|
||||
if candidate == nil || !candidate.Verified.Load() || !candidate.Invalid.Load() {
|
||||
return false, false
|
||||
}
|
||||
|
||||
// Switching from an empty to a non-empty chain
|
||||
if block == nil {
|
||||
return true, true
|
||||
}
|
||||
|
||||
// If these two blocks are on the same chain, they must have a common ancestor
|
||||
|
||||
blockAncestor := block
|
||||
for blockAncestor != nil && blockAncestor.Side.Height > candidate.Side.Height {
|
||||
blockAncestor = c.GetParent(blockAncestor)
|
||||
//TODO: err on blockAncestor nil
|
||||
}
|
||||
|
||||
if blockAncestor != nil {
|
||||
candidateAncestor := candidate
|
||||
for candidateAncestor != nil && candidateAncestor.Side.Height > blockAncestor.Side.Height {
|
||||
candidateAncestor = c.GetParent(candidateAncestor)
|
||||
//TODO: err on candidateAncestor nil
|
||||
}
|
||||
|
||||
for blockAncestor != nil && candidateAncestor != nil {
|
||||
if blockAncestor.Side.Parent.Equals(candidateAncestor.Side.Parent) {
|
||||
return block.Side.CumulativeDifficulty.Cmp(candidate.Side.CumulativeDifficulty) < 0, false
|
||||
}
|
||||
blockAncestor = c.GetParent(blockAncestor)
|
||||
candidateAncestor = c.GetParent(candidateAncestor)
|
||||
}
|
||||
}
|
||||
|
||||
// They're on totally different chains. Compare total difficulties over the last m_chainWindowSize blocks
|
||||
|
||||
var blockTotalDiff, candidateTotalDiff types.Difficulty
|
||||
|
||||
oldChain := block
|
||||
newChain := candidate
|
||||
|
||||
var candidateMainchainHeight, candidateMainchainMinHeight uint64
|
||||
var mainchainPrevId types.Hash
|
||||
|
||||
for i := uint64(0); i < c.Consensus().ChainWindowSize && (oldChain != nil || newChain != nil); i++ {
|
||||
if oldChain != nil {
|
||||
blockTotalDiff = blockTotalDiff.Add(oldChain.Side.Difficulty)
|
||||
oldChain = c.GetParent(oldChain)
|
||||
}
|
||||
|
||||
if newChain != nil {
|
||||
if candidateMainchainMinHeight != 0 {
|
||||
candidateMainchainMinHeight = utils.Min(candidateMainchainMinHeight, newChain.Main.Coinbase.GenHeight)
|
||||
} else {
|
||||
candidateMainchainMinHeight = newChain.Main.Coinbase.GenHeight
|
||||
}
|
||||
candidateTotalDiff = candidateTotalDiff.Add(newChain.Side.Difficulty)
|
||||
|
||||
if !newChain.Main.PreviousId.Equals(mainchainPrevId) {
|
||||
if data := mainblock.GetBlockHeaderByHash(newChain.Main.PreviousId); data != nil {
|
||||
mainchainPrevId = data.Id
|
||||
candidateMainchainHeight = utils.Max(candidateMainchainHeight, data.Height)
|
||||
}
|
||||
}
|
||||
|
||||
newChain = c.GetParent(newChain)
|
||||
}
|
||||
}
|
||||
|
||||
if blockTotalDiff.Cmp(candidateTotalDiff) >= 0 {
|
||||
return false, true
|
||||
}
|
||||
|
||||
// Final check: candidate chain must be built on top of recent mainchain blocks
|
||||
if data := mainblock.GetLastBlockHeader(); data != nil {
|
||||
if candidateMainchainHeight+10 < data.Height {
|
||||
//TODO: warn received a longer alternative chain but it's stale: height
|
||||
return false, true
|
||||
}
|
||||
|
||||
limit := c.Consensus().ChainWindowSize * 4 * c.Consensus().TargetBlockTime / monero.BlockTime
|
||||
if candidateMainchainMinHeight+limit < data.Height {
|
||||
//TODO: warn received a longer alternative chain but it's stale: min height
|
||||
return false, true
|
||||
}
|
||||
|
||||
return true, true
|
||||
} else {
|
||||
return false, true
|
||||
}
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/holiman/uint256"
|
||||
"lukechampine.com/uint128"
|
||||
"math/big"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
const DifficultySize = 16
|
||||
|
@ -224,8 +227,30 @@ func (d *Difficulty) UnmarshalJSON(b []byte) error {
|
|||
}
|
||||
}
|
||||
|
||||
func (d Difficulty) String() string {
|
||||
var buf [DifficultySize]byte
|
||||
d.ReverseBytes().PutBytes(buf[:])
|
||||
return hex.EncodeToString(buf[:])
|
||||
func (d Difficulty) Bytes() []byte {
|
||||
buf := make([]byte, DifficultySize)
|
||||
d.ReverseBytes().PutBytes(buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (d Difficulty) String() string {
|
||||
return hex.EncodeToString(d.Bytes())
|
||||
}
|
||||
|
||||
var powBase = uint256.NewInt(0).SetBytes32(bytes.Repeat([]byte{0xff}, 32))
|
||||
|
||||
func DifficultyFromPoW(powHash Hash) Difficulty {
|
||||
pow := uint256.NewInt(0).SetBytes32(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 ZeroDifficulty
|
||||
}
|
||||
|
||||
powResult := uint256.NewInt(0).Div(powBase, pow).Bytes32()
|
||||
return DifficultyFromBytes(powResult[16:])
|
||||
}
|
||||
|
||||
func (d Difficulty) CheckPoW(pow Hash) bool {
|
||||
return DifficultyFromPoW(pow).Cmp(d) >= 0
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue