Support p2pool hardfork in March
This commit is contained in:
parent
92b74f667f
commit
968b07ae0b
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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]))
|
||||
|
||||
/*
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)...)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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[:])
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
8
utils/xorshift64star.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package utils
|
||||
|
||||
func XorShift64Star(x uint64) uint64 {
|
||||
x ^= x >> 12
|
||||
x ^= x << 25
|
||||
x ^= x >> 27
|
||||
return x * 0x2545F4914F6CDD1D
|
||||
}
|
Loading…
Reference in a new issue