Support p2pool hardfork in March

This commit is contained in:
DataHoarder 2023-03-07 18:57:06 +01:00
parent 92b74f667f
commit 968b07ae0b
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
17 changed files with 283 additions and 75 deletions

View file

@ -736,7 +736,7 @@ func main() {
if buf, err := hex.DecodeString(string(s)); err == nil { if buf, err := hex.DecodeString(string(s)); err == nil {
raw, _ = sidechain.NewShareFromExportedBytes(buf) raw, _ = sidechain.NewShareFromExportedBytes(buf, sidechain.NetworkMainnet)
} }
} }

View file

@ -41,7 +41,7 @@ var previousId, _ = types.HashFromString("d59abce89ce8131eba025988d1ea372937f2ac
var detTxPriv, _ = types.HashFromString("10f3941fd50ca266d3350984004a804c887c36ec11620080fe0b7c4a2a208605") var detTxPriv, _ = types.HashFromString("10f3941fd50ca266d3350984004a804c887c36ec11620080fe0b7c4a2a208605")
func TestDeterministic(t *testing.T) { func TestDeterministic(t *testing.T) {
detTx := types.HashFromBytes(GetDeterministicTransactionPrivateKey(testAddress2, previousId).AsSlice()) detTx := types.HashFromBytes(GetDeterministicTransactionPrivateKey(types.Hash(testAddress2.SpendPublicKey().AsBytes()), previousId).AsSlice())
if detTx != detTxPriv { if detTx != detTxPriv {
t.Fatal() t.Fatal()
} }

View file

@ -10,8 +10,8 @@ import (
"strings" "strings"
) )
func GetDeterministicTransactionPrivateKey(a Interface, prevId types.Hash) crypto.PrivateKey { func GetDeterministicTransactionPrivateKey(seed types.Hash, prevId types.Hash) crypto.PrivateKey {
return p2poolcrypto.GetDeterministicTransactionPrivateKey(a.SpendPublicKey(), prevId) return p2poolcrypto.GetDeterministicTransactionPrivateKey(seed, prevId)
} }
func GetPublicKeyForSharedData(a Interface, sharedData crypto.PrivateKey) crypto.PublicKey { func GetPublicKeyForSharedData(a Interface, sharedData crypto.PrivateKey) crypto.PublicKey {

View file

@ -197,7 +197,9 @@ func (b *Block) HeaderBlob() []byte {
return buf return buf
} }
// SideChainHashingBlob Same as MarshalBinary but with nonce set to 0
func (b *Block) SideChainHashingBlob() (buf []byte, err error) { func (b *Block) SideChainHashingBlob() (buf []byte, err error) {
var txBuf []byte var txBuf []byte
if txBuf, err = b.Coinbase.SideChainHashingBlob(); err != nil { if txBuf, err = b.Coinbase.SideChainHashingBlob(); err != nil {
return nil, err return nil, err

View file

@ -92,7 +92,7 @@ func (a *Api) GetFailedRawBlock(id types.Hash) (b *sidechain.PoolBlock, err erro
} else { } else {
data := make([]byte, len(buf)/2) data := make([]byte, len(buf)/2)
_, _ = hex.Decode(data, buf) _, _ = hex.Decode(data, buf)
return sidechain.NewShareFromExportedBytes(data) return sidechain.NewShareFromExportedBytes(data, sidechain.NetworkMainnet)
} }
} }
@ -111,7 +111,7 @@ func (a *Api) GetRawBlock(id types.Hash) (b *sidechain.PoolBlock, err error) {
} else { } else {
data := make([]byte, len(buf)/2) data := make([]byte, len(buf)/2)
_, _ = hex.Decode(data, buf) _, _ = hex.Decode(data, buf)
return sidechain.NewShareFromExportedBytes(data) return sidechain.NewShareFromExportedBytes(data, sidechain.NetworkMainnet)
} }
} }

View file

@ -6,7 +6,16 @@ import (
"unsafe" "unsafe"
) )
func GetDeterministicTransactionPrivateKey(spendPublicKey crypto.PublicKey, previousMoneroId types.Hash) crypto.PrivateKey { func CalculateTransactionPrivateKeySeed(main, side []byte) types.Hash {
return crypto.PooledKeccak256(
// domain
[]byte("tx_key_seed"), //TODO: check for null termination
main,
side,
)
}
func GetDeterministicTransactionPrivateKey(seed types.Hash, previousMoneroId types.Hash) crypto.PrivateKey {
/* /*
Current deterministic key issues Current deterministic key issues
* This Deterministic private key changes too ofter, and does not fit full purpose (prevent knowledge of private keys on Coinbase without observing of sidechains). * This Deterministic private key changes too ofter, and does not fit full purpose (prevent knowledge of private keys on Coinbase without observing of sidechains).
@ -20,11 +29,11 @@ func GetDeterministicTransactionPrivateKey(spendPublicKey crypto.PublicKey, prev
*/ */
var entropy [13 + crypto.PublicKeySize + types.HashSize + (int(unsafe.Sizeof(uint32(0))) /*pre-allocate uint32 for counter*/)]byte var entropy [13 + types.HashSize + types.HashSize + (int(unsafe.Sizeof(uint32(0))) /*pre-allocate uint32 for counter*/)]byte
copy(entropy[:], []byte("tx_secret_key")) //domain copy(entropy[:], []byte("tx_secret_key")) //domain
copy(entropy[13:], spendPublicKey.AsSlice()) copy(entropy[13:], seed[:])
copy(entropy[13+crypto.PublicKeySize:], previousMoneroId[:]) copy(entropy[13+types.HashSize:], previousMoneroId[:])
return crypto.PrivateKeyFromScalar(crypto.DeterministicScalar(entropy[:13+crypto.PublicKeySize+types.HashSize])) return crypto.PrivateKeyFromScalar(crypto.DeterministicScalar(entropy[:13+types.HashSize+types.HashSize]))
/* /*

View file

@ -182,7 +182,7 @@ func (c *MainChain) HandleMainBlock(b *mainblock.Block) {
} }
// HandleChainMain // HandleChainMain
// deprecated // Deprecated
func (c *MainChain) HandleChainMain(mainData *sidechain.ChainMain, extra []byte) { func (c *MainChain) HandleChainMain(mainData *sidechain.ChainMain, extra []byte) {
func() { func() {
c.lock.Lock() c.lock.Lock()

View file

@ -367,6 +367,7 @@ func (c *Client) OnConnection() {
case MessageBlockResponse: case MessageBlockResponse:
block := &sidechain.PoolBlock{ block := &sidechain.PoolBlock{
LocalTimestamp: uint64(time.Now().Unix()), LocalTimestamp: uint64(time.Now().Unix()),
NetworkType: c.Owner.Consensus().NetworkType,
} }
expectedBlockId, ok := c.getNextBlockRequest() expectedBlockId, ok := c.getNextBlockRequest()
@ -424,6 +425,7 @@ func (c *Client) OnConnection() {
case MessageBlockBroadcast, MessageBlockBroadcastCompact: case MessageBlockBroadcast, MessageBlockBroadcastCompact:
block := &sidechain.PoolBlock{ block := &sidechain.PoolBlock{
LocalTimestamp: uint64(time.Now().Unix()), LocalTimestamp: uint64(time.Now().Unix()),
NetworkType: c.Owner.Consensus().NetworkType,
} }
var blockSize uint32 var blockSize uint32
if err := binary.Read(c, binary.LittleEndian, &blockSize); err != nil { if err := binary.Read(c, binary.LittleEndian, &blockSize); err != nil {

View file

@ -13,10 +13,10 @@ import (
const BlockSaveEpochSize = 32 const BlockSaveEpochSize = 32
const ( const (
BlockSaveOptionTemplate = 1 << 0 BlockSaveOptionTemplate = 1 << 0
BlockSaveOptionDeterministicPrivateKey = 1 << 1 //BlockSaveOptionDeterministicPrivateKey = 1 << 1
BlockSaveOptionDeterministicBlobs = 1 << 2 BlockSaveOptionDeterministicBlobs = 1 << 2
BlockSaveOptionUncles = 1 << 3 BlockSaveOptionUncles = 1 << 3
BlockSaveFieldSizeInBits = 8 BlockSaveFieldSizeInBits = 8
@ -61,7 +61,7 @@ func (c *SideChain) saveBlock(block *PoolBlock) {
defer c.sidechainLock.RUnlock() defer c.sidechainLock.RUnlock()
//only store keys when not deterministic //only store keys when not deterministic
isDeterministicPrivateKey := c.isPoolBlockTransactionKeyIsDeterministic(block) //isDeterministicPrivateKey := c.isPoolBlockTransactionKeyIsDeterministic(block)
calculatedOutputs := c.calculateOutputs(block) calculatedOutputs := c.calculateOutputs(block)
calcBlob, _ := calculatedOutputs.MarshalBinary() calcBlob, _ := calculatedOutputs.MarshalBinary()
@ -80,9 +80,9 @@ func (c *SideChain) saveBlock(block *PoolBlock) {
var blockFlags uint64 var blockFlags uint64
if isDeterministicPrivateKey { /*if isDeterministicPrivateKey {
blockFlags |= BlockSaveOptionDeterministicPrivateKey blockFlags |= BlockSaveOptionDeterministicPrivateKey
} }*/
if !storeBlob { if !storeBlob {
blockFlags |= BlockSaveOptionDeterministicBlobs blockFlags |= BlockSaveOptionDeterministicBlobs
@ -164,8 +164,8 @@ func (c *SideChain) saveBlock(block *PoolBlock) {
} }
// private key, if needed // private key, if needed
if (blockFlags & BlockSaveOptionDeterministicPrivateKey) == 0 { if true /*(blockFlags & BlockSaveOptionDeterministicPrivateKey) == 0*/ {
blob = append(blob, block.Side.CoinbasePrivateKey[:]...) blob = append(blob, block.Side.CoinbasePrivateKeySeed[:]...)
//public may be needed on invalid - TODO check //public may be needed on invalid - TODO check
//blob = append(blob, block.CoinbaseExtra(SideCoinbasePublicKey)...) //blob = append(blob, block.CoinbaseExtra(SideCoinbasePublicKey)...)
} }

View file

@ -61,14 +61,14 @@ func (d *DerivationCache) GetEphemeralPublicKey(a address.Interface, txKeySlice
} }
} }
func (d *DerivationCache) GetDeterministicTransactionKey(a address.Interface, prevId types.Hash) *crypto.KeyPair { func (d *DerivationCache) GetDeterministicTransactionKey(seed types.Hash, prevId types.Hash) *crypto.KeyPair {
var key deterministicTransactionCacheKey var key deterministicTransactionCacheKey
copy(key[:], a.SpendPublicKey().AsSlice()) copy(key[:], seed[:])
copy(key[types.HashSize:], prevId[:]) copy(key[types.HashSize:], prevId[:])
deterministicKeyCache := d.deterministicKeyCache.Load() deterministicKeyCache := d.deterministicKeyCache.Load()
if kp := deterministicKeyCache.Get(key); kp == nil { if kp := deterministicKeyCache.Get(key); kp == nil {
data := crypto.NewKeyPairFromPrivate(address.GetDeterministicTransactionPrivateKey(a, prevId)) data := crypto.NewKeyPairFromPrivate(address.GetDeterministicTransactionPrivateKey(seed, prevId))
deterministicKeyCache.Set(key, data) deterministicKeyCache.Set(key, data)
return data return data
} else { } else {

View file

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero" "git.gammaspectra.live/P2Pool/p2pool-observer/monero"
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto" "git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx" "git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
"git.gammaspectra.live/P2Pool/p2pool-observer/types" "git.gammaspectra.live/P2Pool/p2pool-observer/types"
@ -131,7 +130,7 @@ func (c *Consensus) verify() bool {
return false return false
} }
if c.MinimumDifficulty < SmallestMinimumDifficulty || c.MinimumDifficulty > LargestMinimumDifficulty { if c.NetworkType == NetworkMainnet && c.MinimumDifficulty < SmallestMinimumDifficulty || c.MinimumDifficulty > LargestMinimumDifficulty {
return false return false
} }
@ -152,15 +151,16 @@ func (c *Consensus) verify() bool {
return true return true
} }
func (c *Consensus) CalculateSideTemplateId(main *mainblock.Block, side *SideData) types.Hash { func (c *Consensus) CalculateSideTemplateId(share *PoolBlock) types.Hash {
mainData, _ := main.SideChainHashingBlob() mainData, _ := share.Main.SideChainHashingBlob()
sideData, _ := side.MarshalBinary() sideData, _ := share.Side.MarshalBinary(share.ShareVersion())
return c.CalculateSideChainIdFromBlobs(mainData, sideData) return c.CalculateSideChainIdFromBlobs(mainData, sideData)
} }
func (c *Consensus) CalculateSideChainIdFromBlobs(mainBlob, sideBlob []byte) types.Hash { func (c *Consensus) CalculateSideChainIdFromBlobs(mainBlob, sideBlob []byte) types.Hash {
//TODO: handle extra nonce
return crypto.PooledKeccak256(mainBlob, sideBlob, c.id[:]) return crypto.PooledKeccak256(mainBlob, sideBlob, c.id[:])
} }

View file

@ -9,8 +9,10 @@ import (
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block" mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto" "git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction" "git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
p2poolcrypto "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/crypto"
"git.gammaspectra.live/P2Pool/p2pool-observer/types" "git.gammaspectra.live/P2Pool/p2pool-observer/types"
"io" "io"
"log"
"sync" "sync"
"sync/atomic" "sync/atomic"
"unsafe" "unsafe"
@ -27,6 +29,17 @@ const (
SideTemplateId = transaction.TxExtraTagMergeMining SideTemplateId = transaction.TxExtraTagMergeMining
) )
type ShareVersion int
const (
ShareVersion_None ShareVersion = 0
ShareVersion_V1 ShareVersion = 1
ShareVersion_V2 ShareVersion = 2
)
const ShareVersion_V2MainNetTimestamp uint64 = 1679173200 // 2023-03-18 21:00 UTC
const ShareVersion_V2TestNetTimestamp uint64 = 1674507600 // 2023-01-23 21:00 UTC
type PoolBlock struct { type PoolBlock struct {
Main mainblock.Block Main mainblock.Block
@ -41,12 +54,16 @@ type PoolBlock struct {
WantBroadcast atomic.Bool WantBroadcast atomic.Bool
Broadcasted atomic.Bool Broadcasted atomic.Bool
NetworkType NetworkType
LocalTimestamp uint64 LocalTimestamp uint64
} }
// NewShareFromExportedBytes TODO deprecate this in favor of standard serialized shares // NewShareFromExportedBytes TODO deprecate this in favor of standard serialized shares
func NewShareFromExportedBytes(buf []byte) (*PoolBlock, error) { // Deprecated
b := &PoolBlock{} func NewShareFromExportedBytes(buf []byte, networkType NetworkType) (*PoolBlock, error) {
b := &PoolBlock{
NetworkType: networkType,
}
if len(buf) < 32 { if len(buf) < 32 {
return nil, errors.New("invalid block data") return nil, errors.New("invalid block data")
@ -141,7 +158,7 @@ func NewShareFromExportedBytes(buf []byte) (*PoolBlock, error) {
return nil, err return nil, err
} }
if err = b.Side.UnmarshalBinary(sideData); err != nil { if err = b.Side.UnmarshalBinary(sideData, b.ShareVersion()); err != nil {
return nil, err return nil, err
} }
@ -150,6 +167,37 @@ func NewShareFromExportedBytes(buf []byte) (*PoolBlock, error) {
return b, nil return b, nil
} }
func (b *PoolBlock) ShareVersion() ShareVersion {
// P2Pool forks to v2 at 2023-03-18 21:00 UTC
// Different miners can have different timestamps,
// so a temporary mix of v1 and v2 blocks is allowed
switch b.NetworkType {
case NetworkInvalid:
log.Panicf("invalid network type for determining share version")
case NetworkMainnet:
if b.Main.Timestamp >= ShareVersion_V2MainNetTimestamp {
return ShareVersion_V2
}
case NetworkTestnet:
if b.Main.Timestamp >= ShareVersion_V2TestNetTimestamp {
return ShareVersion_V2
}
case NetworkStagenet:
return ShareVersion_V2
}
if b.Main.Timestamp >= ShareVersion_V2MainNetTimestamp {
return ShareVersion_V2
}
return ShareVersion_V1
}
func (b *PoolBlock) ShareVersionSignaling() ShareVersion {
if b.ShareVersion() == ShareVersion_V1 && ((binary.LittleEndian.Uint32(b.CoinbaseExtra(SideExtraNonce)))&0xFF100000 == 0xFF000000) {
return ShareVersion_V2
}
return ShareVersion_None
}
func (b *PoolBlock) CoinbaseExtra(tag CoinbaseExtraTag) []byte { func (b *PoolBlock) CoinbaseExtra(tag CoinbaseExtraTag) []byte {
switch tag { switch tag {
case SideExtraNonce: case SideExtraNonce:
@ -271,7 +319,7 @@ func (b *PoolBlock) SideTemplateId(consensus *Consensus) types.Hash {
b.cache.lock.Lock() b.cache.lock.Lock()
defer b.cache.lock.Unlock() defer b.cache.lock.Unlock()
if b.cache.templateId == types.ZeroHash { //check again for race if b.cache.templateId == types.ZeroHash { //check again for race
b.cache.templateId = consensus.CalculateSideTemplateId(&b.Main, &b.Side) b.cache.templateId = consensus.CalculateSideTemplateId(b)
} }
return b.cache.templateId return b.cache.templateId
} }
@ -311,7 +359,7 @@ func (b *PoolBlock) UnmarshalBinary(data []byte) error {
func (b *PoolBlock) MarshalBinary() ([]byte, error) { func (b *PoolBlock) MarshalBinary() ([]byte, error) {
if mainData, err := b.Main.MarshalBinary(); err != nil { if mainData, err := b.Main.MarshalBinary(); err != nil {
return nil, err return nil, err
} else if sideData, err := b.Side.MarshalBinary(); err != nil { } else if sideData, err := b.Side.MarshalBinary(b.ShareVersion()); err != nil {
return nil, err return nil, err
} else { } else {
data := make([]byte, 0, len(mainData)+len(sideData)) data := make([]byte, 0, len(mainData)+len(sideData))
@ -324,7 +372,7 @@ func (b *PoolBlock) MarshalBinary() ([]byte, error) {
func (b *PoolBlock) MarshalBinaryFlags(pruned, compact bool) ([]byte, error) { func (b *PoolBlock) MarshalBinaryFlags(pruned, compact bool) ([]byte, error) {
if mainData, err := b.Main.MarshalBinaryFlags(pruned, compact); err != nil { if mainData, err := b.Main.MarshalBinaryFlags(pruned, compact); err != nil {
return nil, err return nil, err
} else if sideData, err := b.Side.MarshalBinary(); err != nil { } else if sideData, err := b.Side.MarshalBinary(b.ShareVersion()); err != nil {
return nil, err return nil, err
} else { } else {
data := make([]byte, 0, len(mainData)+len(sideData)) data := make([]byte, 0, len(mainData)+len(sideData))
@ -339,7 +387,7 @@ func (b *PoolBlock) FromReader(reader readerAndByteReader) (err error) {
return err return err
} }
if err = b.Side.FromReader(reader); err != nil { if err = b.Side.FromReader(reader, b.ShareVersion()); err != nil {
return err return err
} }
@ -352,7 +400,7 @@ func (b *PoolBlock) FromCompactReader(reader readerAndByteReader) (err error) {
return err return err
} }
if err = b.Side.FromReader(reader); err != nil { if err = b.Side.FromReader(reader, b.ShareVersion()); err != nil {
return err return err
} }
@ -387,6 +435,26 @@ func (b *PoolBlock) IsProofHigherThanDifficultyWithError(f mainblock.GetSeedByHe
} }
} }
func (b *PoolBlock) GetPrivateKeySeed() types.Hash {
if b.ShareVersion() > ShareVersion_V1 {
return b.Side.CoinbasePrivateKeySeed
}
return types.Hash(b.Side.PublicSpendKey.AsBytes())
}
func (b *PoolBlock) CalculateTransactionPrivateKeySeed() types.Hash {
if b.ShareVersion() > ShareVersion_V1 {
mainData, _ := b.Main.SideChainHashingBlob()
sideData, _ := b.Side.MarshalBinary(b.ShareVersion())
return p2poolcrypto.CalculateTransactionPrivateKeySeed(
mainData,
sideData,
)
}
return types.Hash(b.Side.PublicSpendKey.AsBytes())
}
func (b *PoolBlock) GetAddress() *address.PackedAddress { func (b *PoolBlock) GetAddress() *address.PackedAddress {
a := address.NewPackedAddressFromBytes(b.Side.PublicSpendKey, b.Side.PublicViewKey) a := address.NewPackedAddressFromBytes(b.Side.PublicSpendKey, b.Side.PublicViewKey)
return &a return &a

View file

@ -32,7 +32,7 @@ func TestPoolBlockDecode(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
block, err := NewShareFromExportedBytes(contents) block, err := NewShareFromExportedBytes(contents, NetworkMainnet)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -57,14 +57,14 @@ func TestPoolBlockDecode(t *testing.T) {
b2, _ := block.Main.MarshalBinary() b2, _ := block.Main.MarshalBinary()
side2 := &SideData{} side2 := &SideData{}
if err = side2.UnmarshalBinary(b2); err != nil { if err = side2.UnmarshalBinary(b2, block.ShareVersion()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Log(block.SideTemplateId(ConsensusDefault).String()) t.Log(block.SideTemplateId(ConsensusDefault).String())
t.Log(block.Side.CoinbasePrivateKey.String()) t.Log(block.Side.CoinbasePrivateKey.String())
t.Log(address.GetDeterministicTransactionPrivateKey(block.GetAddress(), block.Main.PreviousId).String()) t.Log(address.GetDeterministicTransactionPrivateKey(block.GetPrivateKeySeed(), block.Main.PreviousId).String())
txId := block.Main.Coinbase.Id() txId := block.Main.Coinbase.Id()

View file

@ -2,18 +2,21 @@ package sidechain
import ( import (
"bytes" "bytes"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero" "git.gammaspectra.live/P2Pool/p2pool-observer/monero"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address" "git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block" mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client" "git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx" "git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction" "git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
"git.gammaspectra.live/P2Pool/p2pool-observer/types" "git.gammaspectra.live/P2Pool/p2pool-observer/types"
"git.gammaspectra.live/P2Pool/p2pool-observer/utils" "git.gammaspectra.live/P2Pool/p2pool-observer/utils"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"log" "log"
"lukechampine.com/uint128"
"math" "math"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -151,8 +154,11 @@ func (c *SideChain) fillPoolBlockTransactionParentIndices(block *PoolBlock) {
} }
func (c *SideChain) isPoolBlockTransactionKeyIsDeterministic(block *PoolBlock) bool { func (c *SideChain) isPoolBlockTransactionKeyIsDeterministic(block *PoolBlock) bool {
kP := c.derivationCache.GetDeterministicTransactionKey(block.GetAddress(), block.Main.PreviousId) kP := c.derivationCache.GetDeterministicTransactionKey(block.GetPrivateKeySeed(), block.Main.PreviousId)
return bytes.Compare(block.CoinbaseExtra(SideCoinbasePublicKey), kP.PublicKey.AsSlice()) == 0 && bytes.Compare(block.Side.CoinbasePrivateKey[:], kP.PrivateKey.AsSlice()) == 0 if block.ShareVersion() > ShareVersion_V1 {
block.Side.CoinbasePrivateKey = kP.PrivateKey.AsBytes()
}
return bytes.Compare(block.CoinbaseExtra(SideCoinbasePublicKey), kP.PublicKey.AsSlice()) == 0 && bytes.Compare(kP.PrivateKey.AsSlice(), block.Side.CoinbasePrivateKey[:]) == 0
} }
func (c *SideChain) getSeedByHeightFunc() mainblock.GetSeedByHeightFunc { func (c *SideChain) getSeedByHeightFunc() mainblock.GetSeedByHeightFunc {
@ -192,7 +198,7 @@ func (c *SideChain) AddPoolBlockExternal(block *PoolBlock) (missingBlocks []type
} }
} }
templateId := c.Consensus().CalculateSideTemplateId(&block.Main, &block.Side) templateId := c.Consensus().CalculateSideTemplateId(block)
if templateId != block.SideTemplateId(c.Consensus()) { if templateId != block.SideTemplateId(c.Consensus()) {
return nil, fmt.Errorf("invalid template id %s, expected %s", templateId.String(), block.SideTemplateId(c.Consensus()).String()) return nil, fmt.Errorf("invalid template id %s, expected %s", templateId.String(), block.SideTemplateId(c.Consensus()).String())
} }
@ -384,7 +390,8 @@ func (c *SideChain) verifyBlock(block *PoolBlock) (verification error, invalid e
if block.Side.Parent != types.ZeroHash || if block.Side.Parent != types.ZeroHash ||
len(block.Side.Uncles) != 0 || len(block.Side.Uncles) != 0 ||
block.Side.Difficulty.Cmp64(c.Consensus().MinimumDifficulty) != 0 || block.Side.Difficulty.Cmp64(c.Consensus().MinimumDifficulty) != 0 ||
block.Side.CumulativeDifficulty.Cmp64(c.Consensus().MinimumDifficulty) != 0 { block.Side.CumulativeDifficulty.Cmp64(c.Consensus().MinimumDifficulty) != 0 ||
(block.ShareVersion() > ShareVersion_V1 && block.Side.CoinbasePrivateKeySeed == types.ZeroHash) {
return nil, errors.New("genesis block has invalid parameters") return nil, errors.New("genesis block has invalid parameters")
} }
//this does not verify coinbase outputs, but that's fine //this does not verify coinbase outputs, but that's fine
@ -417,6 +424,16 @@ func (c *SideChain) verifyBlock(block *PoolBlock) (verification error, invalid e
return nil, errors.New("parent is invalid") return nil, errors.New("parent is invalid")
} }
if block.ShareVersion() > ShareVersion_V1 {
expectedSeed := parent.Side.CoinbasePrivateKeySeed
if parent.Main.PreviousId != block.Main.PreviousId {
expectedSeed = parent.CalculateTransactionPrivateKeySeed()
}
if block.Side.CoinbasePrivateKeySeed != expectedSeed {
return nil, fmt.Errorf("invalid tx key seed: expected %s, got %s", expectedSeed.String(), block.Side.CoinbasePrivateKeySeed.String())
}
}
expectedHeight := parent.Side.Height + 1 expectedHeight := parent.Side.Height + 1
if expectedHeight != block.Side.Height { if expectedHeight != block.Side.Height {
return nil, fmt.Errorf("wrong height, expected %d", expectedHeight) return nil, fmt.Errorf("wrong height, expected %d", expectedHeight)
@ -524,7 +541,7 @@ func (c *SideChain) verifyBlock(block *PoolBlock) (verification error, invalid e
return nil, fmt.Errorf("wrong difficulty, got %s, expected %s", block.Side.Difficulty.StringNumeric(), diff.StringNumeric()) return nil, fmt.Errorf("wrong difficulty, got %s, expected %s", block.Side.Difficulty.StringNumeric(), diff.StringNumeric())
} }
if c.sharesCache = c.getShares(block, c.sharesCache); len(c.sharesCache) == 0 { if c.sharesCache, _ = c.getShares(block, c.sharesCache); len(c.sharesCache) == 0 {
return nil, errors.New("could not get outputs") return nil, errors.New("could not get outputs")
} else if len(c.sharesCache) != len(block.Main.Coinbase.Outputs) { } else if len(c.sharesCache) != len(block.Main.Coinbase.Outputs) {
return nil, fmt.Errorf("invalid number of outputs, got %d, expected %d", len(block.Main.Coinbase.Outputs), len(c.sharesCache)) return nil, fmt.Errorf("invalid number of outputs, got %d, expected %d", len(block.Main.Coinbase.Outputs), len(c.sharesCache))
@ -641,6 +658,11 @@ func (c *SideChain) updateChainTip(block *PoolBlock) {
tip := c.GetChainTip() tip := c.GetChainTip()
if block == tip {
log.Printf("[SideChain] Trying to update chain tip to the same block again. Ignoring it.")
return
}
if isLongerChain, isAlternative := c.isLongerChain(tip, block); isLongerChain { if isLongerChain, isAlternative := c.isLongerChain(tip, block); isLongerChain {
if diff := c.getDifficulty(block); diff != types.ZeroDifficulty { if diff := c.getDifficulty(block); diff != types.ZeroDifficulty {
c.chainTip.Store(block) c.chainTip.Store(block)
@ -777,7 +799,7 @@ func (c *SideChain) getOutputs(block *PoolBlock) transaction.Outputs {
func (c *SideChain) calculateOutputs(block *PoolBlock) transaction.Outputs { func (c *SideChain) calculateOutputs(block *PoolBlock) transaction.Outputs {
//TODO: buffer //TODO: buffer
tmpShares := c.getShares(block, make(Shares, 0, c.Consensus().ChainWindowSize*2)) tmpShares, _ := c.getShares(block, make(Shares, 0, c.Consensus().ChainWindowSize*2))
tmpRewards := c.SplitReward(block.Main.Coinbase.TotalReward, tmpShares) tmpRewards := c.SplitReward(block.Main.Coinbase.TotalReward, tmpShares)
if tmpShares == nil || tmpRewards == nil || len(tmpRewards) != len(tmpShares) { if tmpShares == nil || tmpRewards == nil || len(tmpRewards) != len(tmpShares) {
@ -844,19 +866,54 @@ func (c *SideChain) SplitReward(reward uint64, shares Shares) (rewards []uint64)
return rewards return rewards
} }
func (c *SideChain) getShares(tip *PoolBlock, preAllocatedShares Shares) Shares { func (c *SideChain) getShares(tip *PoolBlock, preAllocatedShares Shares) (shares Shares, bottomHeight uint64) {
var blockDepth uint64 var blockDepth uint64
cur := tip cur := tip
var mainchainDiff types.Difficulty
if tip.Side.Parent != types.ZeroHash {
mainchainDiff = c.server.GetDifficultyByHeight(tip.Main.Coinbase.GenHeight)
if mainchainDiff == types.ZeroDifficulty {
log.Printf("[SideChain] get_shares: couldn't get mainchain difficulty for height = %d", tip.Main.Coinbase.GenHeight)
return nil, 0
}
}
// Dynamic PPLNS window starting from v2
// Limit PPLNS weight to 2x of the Monero difficulty (max 2 blocks per PPLNS window on average)
sidechainVersion := tip.ShareVersion()
maxPplnsWeight := types.MaxDifficulty
if sidechainVersion > ShareVersion_V1 {
maxPplnsWeight = mainchainDiff.Mul64(2)
}
var pplnsWeight types.Difficulty
sharesSet := make(map[address.PackedAddress]*Share, c.Consensus().ChainWindowSize*2)
insertSet := func(weight types.Difficulty, a *address.PackedAddress) {
if _, ok := sharesSet[*a]; ok {
sharesSet[*a].Weight = sharesSet[*a].Weight.Add(weight)
} else {
sharesSet[*a] = &Share{
Weight: weight,
Address: *a,
}
}
}
index := 0 index := 0
l := len(preAllocatedShares) l := len(preAllocatedShares)
insert := func(weight types.Difficulty, a *address.PackedAddress) { insertPreAllocated := func(share *Share) {
if index < l { if index < l {
preAllocatedShares[index].Weight, preAllocatedShares[index].Address = weight, *a preAllocatedShares[index].Weight, preAllocatedShares[index].Address = share.Weight, share.Address
} else { } else {
preAllocatedShares = append(preAllocatedShares, &Share{Weight: weight, Address: *a}) preAllocatedShares = append(preAllocatedShares, share)
} }
index++ index++
} }
@ -867,7 +924,7 @@ func (c *SideChain) getShares(tip *PoolBlock, preAllocatedShares Shares) Shares
for _, uncleId := range cur.Side.Uncles { for _, uncleId := range cur.Side.Uncles {
if uncle := c.getPoolBlockByTemplateId(uncleId); uncle == nil { if uncle := c.getPoolBlockByTemplateId(uncleId); uncle == nil {
//cannot find uncles //cannot find uncles
return nil return nil, 0
} else { } else {
// Skip uncles which are already out of PPLNS window // Skip uncles which are already out of PPLNS window
if (tip.Side.Height - uncle.Side.Height) >= c.Consensus().ChainWindowSize { if (tip.Side.Height - uncle.Side.Height) >= c.Consensus().ChainWindowSize {
@ -875,15 +932,31 @@ func (c *SideChain) getShares(tip *PoolBlock, preAllocatedShares Shares) Shares
} }
// Take some % of uncle's weight into this share // Take some % of uncle's weight into this share
product := uncle.Side.Difficulty.Mul64(c.Consensus().UnclePenalty) unclePenalty := uncle.Side.Difficulty.Mul64(c.Consensus().UnclePenalty).Div64(100)
unclePenalty := product.Div64(100) uncleWeight := uncle.Side.Difficulty.Sub(unclePenalty)
newPplnsWeight := pplnsWeight.Add(uncleWeight)
// Skip uncles that push PPLNS weight above the limit
if newPplnsWeight.Cmp(maxPplnsWeight) > 0 {
continue
}
curWeight = curWeight.Add(unclePenalty) curWeight = curWeight.Add(unclePenalty)
insert(uncle.Side.Difficulty.Sub(unclePenalty), uncle.GetAddress()) insertSet(uncleWeight, uncle.GetAddress())
pplnsWeight = newPplnsWeight
} }
} }
insert(curWeight, cur.GetAddress()) // Always add non-uncle shares even if PPLNS weight goes above the limit
insertSet(curWeight, cur.GetAddress())
pplnsWeight = pplnsWeight.Add(curWeight)
// One non-uncle share can go above the limit, but it will also guarantee that "shares" is never empty
if pplnsWeight.Cmp(maxPplnsWeight) > 0 {
break
}
blockDepth++ blockDepth++
@ -899,29 +972,39 @@ func (c *SideChain) getShares(tip *PoolBlock, preAllocatedShares Shares) Shares
cur = c.getParent(cur) cur = c.getParent(cur)
if cur == nil { if cur == nil {
return nil return nil, 0
} }
} }
shares := preAllocatedShares[:index] bottomHeight = cur.Side.Height
for _, share := range sharesSet {
insertPreAllocated(share)
}
shares = preAllocatedShares[:index]
// Combine shares with the same wallet addresses // Combine shares with the same wallet addresses
slices.SortFunc(shares, func(a *Share, b *Share) bool { slices.SortFunc(shares, func(a *Share, b *Share) bool {
return a.Address.Compare(&b.Address) < 0 return a.Address.Compare(&b.Address) < 0
}) })
k := 0 n := len(shares)
for i := 1; i < len(shares); i++ {
if shares[i].Address.Compare(&shares[k].Address) == 0 { //Shuffle shares
shares[k].Weight = shares[k].Weight.Add(shares[i].Weight) if sidechainVersion > ShareVersion_V1 && n > 1 {
} else { h := crypto.PooledKeccak256(tip.Side.CoinbasePrivateKeySeed[:])
k++ seed := binary.LittleEndian.Uint64(h[:])
shares[k].Address = shares[i].Address
shares[k].Weight = shares[i].Weight for i := 0; i < (n - 1); i++ {
seed = utils.XorShift64Star(seed)
k := int(uint128.From64(seed).Mul64(uint64(n - 1)).Hi)
//swap
shares[i], shares[i+k] = shares[i+k], shares[i]
} }
} }
return shares[:k+1] return shares, bottomHeight
} }
type DifficultyData struct { type DifficultyData struct {

View file

@ -9,14 +9,24 @@ import (
) )
type SideData struct { type SideData struct {
PublicSpendKey crypto.PublicKeyBytes PublicSpendKey crypto.PublicKeyBytes
PublicViewKey crypto.PublicKeyBytes PublicViewKey crypto.PublicKeyBytes
CoinbasePrivateKeySeed types.Hash
// CoinbasePrivateKey filled either on decoding, or side chain filling
CoinbasePrivateKey crypto.PrivateKeyBytes CoinbasePrivateKey crypto.PrivateKeyBytes
Parent types.Hash Parent types.Hash
Uncles []types.Hash Uncles []types.Hash
Height uint64 Height uint64
Difficulty types.Difficulty Difficulty types.Difficulty
CumulativeDifficulty types.Difficulty CumulativeDifficulty types.Difficulty
// ExtraBuffer available in ShareVersion_2 and above
ExtraBuffer struct {
SoftwareId uint32
Version uint32
RandomNumber uint32
SideChainExtraNonce uint32
}
} }
type readerAndByteReader interface { type readerAndByteReader interface {
@ -24,11 +34,11 @@ type readerAndByteReader interface {
io.ByteReader io.ByteReader
} }
func (b *SideData) MarshalBinary() (buf []byte, err error) { func (b *SideData) MarshalBinary(version ShareVersion) (buf []byte, err error) {
buf = make([]byte, 0, types.HashSize+types.HashSize+types.HashSize+types.HashSize+binary.MaxVarintLen64+binary.MaxVarintLen64+binary.MaxVarintLen64+binary.MaxVarintLen64+binary.MaxVarintLen64+binary.MaxVarintLen64) buf = make([]byte, 0, types.HashSize+types.HashSize+types.HashSize+types.HashSize+binary.MaxVarintLen64+binary.MaxVarintLen64+binary.MaxVarintLen64+binary.MaxVarintLen64+binary.MaxVarintLen64+binary.MaxVarintLen64+4*4)
buf = append(buf, b.PublicSpendKey[:]...) buf = append(buf, b.PublicSpendKey[:]...)
buf = append(buf, b.PublicViewKey[:]...) buf = append(buf, b.PublicViewKey[:]...)
buf = append(buf, b.CoinbasePrivateKey[:]...) buf = append(buf, b.CoinbasePrivateKeySeed[:]...)
buf = append(buf, b.Parent[:]...) buf = append(buf, b.Parent[:]...)
buf = binary.AppendUvarint(buf, uint64(len(b.Uncles))) buf = binary.AppendUvarint(buf, uint64(len(b.Uncles)))
for _, uId := range b.Uncles { for _, uId := range b.Uncles {
@ -39,11 +49,17 @@ func (b *SideData) MarshalBinary() (buf []byte, err error) {
buf = binary.AppendUvarint(buf, b.Difficulty.Hi) buf = binary.AppendUvarint(buf, b.Difficulty.Hi)
buf = binary.AppendUvarint(buf, b.CumulativeDifficulty.Lo) buf = binary.AppendUvarint(buf, b.CumulativeDifficulty.Lo)
buf = binary.AppendUvarint(buf, b.CumulativeDifficulty.Hi) buf = binary.AppendUvarint(buf, b.CumulativeDifficulty.Hi)
if version > ShareVersion_V1 {
buf = binary.LittleEndian.AppendUint32(buf, b.ExtraBuffer.SoftwareId)
buf = binary.LittleEndian.AppendUint32(buf, b.ExtraBuffer.Version)
buf = binary.LittleEndian.AppendUint32(buf, b.ExtraBuffer.RandomNumber)
buf = binary.LittleEndian.AppendUint32(buf, b.ExtraBuffer.SideChainExtraNonce)
}
return buf, nil return buf, nil
} }
func (b *SideData) FromReader(reader readerAndByteReader) (err error) { func (b *SideData) FromReader(reader readerAndByteReader, version ShareVersion) (err error) {
var ( var (
uncleCount uint64 uncleCount uint64
uncleHash types.Hash uncleHash types.Hash
@ -54,9 +70,14 @@ func (b *SideData) FromReader(reader readerAndByteReader) (err error) {
if _, err = io.ReadFull(reader, b.PublicViewKey[:]); err != nil { if _, err = io.ReadFull(reader, b.PublicViewKey[:]); err != nil {
return err return err
} }
if _, err = io.ReadFull(reader, b.CoinbasePrivateKey[:]); err != nil { if _, err = io.ReadFull(reader, b.CoinbasePrivateKeySeed[:]); err != nil {
return err return err
} }
if version > ShareVersion_V1 {
//needs preprocessing
} else {
b.CoinbasePrivateKey = crypto.PrivateKeyBytes(b.CoinbasePrivateKeySeed)
}
if _, err = io.ReadFull(reader, b.Parent[:]); err != nil { if _, err = io.ReadFull(reader, b.Parent[:]); err != nil {
return err return err
} }
@ -95,11 +116,25 @@ func (b *SideData) FromReader(reader readerAndByteReader) (err error) {
return err return err
} }
} }
if version > ShareVersion_V1 {
if err = binary.Read(reader, binary.LittleEndian, &b.ExtraBuffer.SoftwareId); err != nil {
return err
}
if err = binary.Read(reader, binary.LittleEndian, &b.ExtraBuffer.Version); err != nil {
return err
}
if err = binary.Read(reader, binary.LittleEndian, &b.ExtraBuffer.RandomNumber); err != nil {
return err
}
if err = binary.Read(reader, binary.LittleEndian, &b.ExtraBuffer.SideChainExtraNonce); err != nil {
return err
}
}
return nil return nil
} }
func (b *SideData) UnmarshalBinary(data []byte) error { func (b *SideData) UnmarshalBinary(data []byte, version ShareVersion) error {
reader := bytes.NewReader(data) reader := bytes.NewReader(data)
return b.FromReader(reader) return b.FromReader(reader, version)
} }

View file

@ -15,6 +15,7 @@ import (
const DifficultySize = 16 const DifficultySize = 16
var ZeroDifficulty = Difficulty(uint128.Zero) var ZeroDifficulty = Difficulty(uint128.Zero)
var MaxDifficulty = Difficulty(uint128.Max)
type Difficulty uint128.Uint128 type Difficulty uint128.Uint128

8
utils/xorshift64star.go Normal file
View file

@ -0,0 +1,8 @@
package utils
func XorShift64Star(x uint64) uint64 {
x ^= x >> 12
x ^= x << 25
x ^= x >> 27
return x * 0x2545F4914F6CDD1D
}