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 {
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")
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 {
t.Fatal()
}

View file

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

View file

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

View file

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

View file

@ -6,7 +6,16 @@ import (
"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
* 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[13:], spendPublicKey.AsSlice())
copy(entropy[13+crypto.PublicKeySize:], previousMoneroId[:])
return crypto.PrivateKeyFromScalar(crypto.DeterministicScalar(entropy[:13+crypto.PublicKeySize+types.HashSize]))
copy(entropy[13:], seed[:])
copy(entropy[13+types.HashSize:], previousMoneroId[:])
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
// deprecated
// Deprecated
func (c *MainChain) HandleChainMain(mainData *sidechain.ChainMain, extra []byte) {
func() {
c.lock.Lock()

View file

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

View file

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

View file

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

View file

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

View file

@ -32,7 +32,7 @@ func TestPoolBlockDecode(t *testing.T) {
t.Fatal(err)
}
block, err := NewShareFromExportedBytes(contents)
block, err := NewShareFromExportedBytes(contents, NetworkMainnet)
if err != nil {
t.Fatal(err)
}
@ -57,14 +57,14 @@ func TestPoolBlockDecode(t *testing.T) {
b2, _ := block.Main.MarshalBinary()
side2 := &SideData{}
if err = side2.UnmarshalBinary(b2); err != nil {
if err = side2.UnmarshalBinary(b2, block.ShareVersion()); err != nil {
t.Fatal(err)
}
t.Log(block.SideTemplateId(ConsensusDefault).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()

View file

@ -2,18 +2,21 @@ package sidechain
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
"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/transaction"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
"golang.org/x/exp/slices"
"log"
"lukechampine.com/uint128"
"math"
"sync"
"sync/atomic"
@ -151,8 +154,11 @@ func (c *SideChain) fillPoolBlockTransactionParentIndices(block *PoolBlock) {
}
func (c *SideChain) isPoolBlockTransactionKeyIsDeterministic(block *PoolBlock) bool {
kP := c.derivationCache.GetDeterministicTransactionKey(block.GetAddress(), block.Main.PreviousId)
return bytes.Compare(block.CoinbaseExtra(SideCoinbasePublicKey), kP.PublicKey.AsSlice()) == 0 && bytes.Compare(block.Side.CoinbasePrivateKey[:], kP.PrivateKey.AsSlice()) == 0
kP := c.derivationCache.GetDeterministicTransactionKey(block.GetPrivateKeySeed(), block.Main.PreviousId)
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 {
@ -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()) {
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 ||
len(block.Side.Uncles) != 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")
}
//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")
}
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
if expectedHeight != block.Side.Height {
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())
}
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")
} 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))
@ -641,6 +658,11 @@ func (c *SideChain) updateChainTip(block *PoolBlock) {
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 diff := c.getDifficulty(block); diff != types.ZeroDifficulty {
c.chainTip.Store(block)
@ -777,7 +799,7 @@ func (c *SideChain) getOutputs(block *PoolBlock) transaction.Outputs {
func (c *SideChain) calculateOutputs(block *PoolBlock) transaction.Outputs {
//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)
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
}
func (c *SideChain) getShares(tip *PoolBlock, preAllocatedShares Shares) Shares {
func (c *SideChain) getShares(tip *PoolBlock, preAllocatedShares Shares) (shares Shares, bottomHeight uint64) {
var blockDepth uint64
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
l := len(preAllocatedShares)
insert := func(weight types.Difficulty, a *address.PackedAddress) {
insertPreAllocated := func(share *Share) {
if index < l {
preAllocatedShares[index].Weight, preAllocatedShares[index].Address = weight, *a
preAllocatedShares[index].Weight, preAllocatedShares[index].Address = share.Weight, share.Address
} else {
preAllocatedShares = append(preAllocatedShares, &Share{Weight: weight, Address: *a})
preAllocatedShares = append(preAllocatedShares, share)
}
index++
}
@ -867,7 +924,7 @@ func (c *SideChain) getShares(tip *PoolBlock, preAllocatedShares Shares) Shares
for _, uncleId := range cur.Side.Uncles {
if uncle := c.getPoolBlockByTemplateId(uncleId); uncle == nil {
//cannot find uncles
return nil
return nil, 0
} else {
// Skip uncles which are already out of PPLNS window
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
product := uncle.Side.Difficulty.Mul64(c.Consensus().UnclePenalty)
unclePenalty := product.Div64(100)
unclePenalty := uncle.Side.Difficulty.Mul64(c.Consensus().UnclePenalty).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)
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++
@ -899,29 +972,39 @@ func (c *SideChain) getShares(tip *PoolBlock, preAllocatedShares Shares) Shares
cur = c.getParent(cur)
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
slices.SortFunc(shares, func(a *Share, b *Share) bool {
return a.Address.Compare(&b.Address) < 0
})
k := 0
for i := 1; i < len(shares); i++ {
if shares[i].Address.Compare(&shares[k].Address) == 0 {
shares[k].Weight = shares[k].Weight.Add(shares[i].Weight)
} else {
k++
shares[k].Address = shares[i].Address
shares[k].Weight = shares[i].Weight
n := len(shares)
//Shuffle shares
if sidechainVersion > ShareVersion_V1 && n > 1 {
h := crypto.PooledKeccak256(tip.Side.CoinbasePrivateKeySeed[:])
seed := binary.LittleEndian.Uint64(h[:])
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 {

View file

@ -9,14 +9,24 @@ import (
)
type SideData struct {
PublicSpendKey crypto.PublicKeyBytes
PublicViewKey crypto.PublicKeyBytes
PublicSpendKey crypto.PublicKeyBytes
PublicViewKey crypto.PublicKeyBytes
CoinbasePrivateKeySeed types.Hash
// CoinbasePrivateKey filled either on decoding, or side chain filling
CoinbasePrivateKey crypto.PrivateKeyBytes
Parent types.Hash
Uncles []types.Hash
Height uint64
Difficulty 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 {
@ -24,11 +34,11 @@ type readerAndByteReader interface {
io.ByteReader
}
func (b *SideData) MarshalBinary() (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)
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+4*4)
buf = append(buf, b.PublicSpendKey[:]...)
buf = append(buf, b.PublicViewKey[:]...)
buf = append(buf, b.CoinbasePrivateKey[:]...)
buf = append(buf, b.CoinbasePrivateKeySeed[:]...)
buf = append(buf, b.Parent[:]...)
buf = binary.AppendUvarint(buf, uint64(len(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.CumulativeDifficulty.Lo)
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
}
func (b *SideData) FromReader(reader readerAndByteReader) (err error) {
func (b *SideData) FromReader(reader readerAndByteReader, version ShareVersion) (err error) {
var (
uncleCount uint64
uncleHash types.Hash
@ -54,9 +70,14 @@ func (b *SideData) FromReader(reader readerAndByteReader) (err error) {
if _, err = io.ReadFull(reader, b.PublicViewKey[:]); err != nil {
return err
}
if _, err = io.ReadFull(reader, b.CoinbasePrivateKey[:]); err != nil {
if _, err = io.ReadFull(reader, b.CoinbasePrivateKeySeed[:]); err != nil {
return err
}
if version > ShareVersion_V1 {
//needs preprocessing
} else {
b.CoinbasePrivateKey = crypto.PrivateKeyBytes(b.CoinbasePrivateKeySeed)
}
if _, err = io.ReadFull(reader, b.Parent[:]); err != nil {
return err
}
@ -95,11 +116,25 @@ func (b *SideData) FromReader(reader readerAndByteReader) (err error) {
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
}
func (b *SideData) UnmarshalBinary(data []byte) error {
func (b *SideData) UnmarshalBinary(data []byte, version ShareVersion) error {
reader := bytes.NewReader(data)
return b.FromReader(reader)
return b.FromReader(reader, version)
}

View file

@ -15,6 +15,7 @@ import (
const DifficultySize = 16
var ZeroDifficulty = Difficulty(uint128.Zero)
var MaxDifficulty = Difficulty(uint128.Max)
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
}