Refactor Difficulty, cache derivations and client actions, faster miner transaction matching
This commit is contained in:
parent
135fbd8973
commit
ab0e335b9a
|
@ -15,7 +15,6 @@ import (
|
|||
"github.com/ake-persson/mapslice-json"
|
||||
"github.com/gorilla/mux"
|
||||
"log"
|
||||
"lukechampine.com/uint128"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -52,7 +51,7 @@ func main() {
|
|||
blockCount := 0
|
||||
uncleCount := 0
|
||||
|
||||
var windowDifficulty uint128.Uint128
|
||||
var windowDifficulty types.Difficulty
|
||||
|
||||
miners := make(map[uint64]uint64)
|
||||
|
||||
|
@ -63,7 +62,7 @@ func main() {
|
|||
}
|
||||
miners[b.MinerId]++
|
||||
|
||||
windowDifficulty = windowDifficulty.Add(b.Difficulty.Uint128)
|
||||
windowDifficulty = windowDifficulty.Add(b.Difficulty)
|
||||
for u := range api.GetDatabase().GetUnclesByParentId(b.Id) {
|
||||
//TODO: check this check is correct :)
|
||||
if (tip.Height - u.Block.Height) > p2pool.PPLNSWindow {
|
||||
|
@ -76,7 +75,7 @@ func main() {
|
|||
}
|
||||
miners[u.Block.MinerId]++
|
||||
|
||||
windowDifficulty = windowDifficulty.Add(u.Block.Difficulty.Uint128)
|
||||
windowDifficulty = windowDifficulty.Add(u.Block.Difficulty)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +101,7 @@ func main() {
|
|||
|
||||
globalDiff := tip.Template.Difficulty
|
||||
|
||||
currentEffort := float64(uint128.From64(poolStats.PoolStatistics.TotalHashes-poolBlocks[0].TotalHashes).Mul64(100000).Div(globalDiff.Uint128).Lo) / 1000
|
||||
currentEffort := float64(types.DifficultyFrom64(poolStats.PoolStatistics.TotalHashes-poolBlocks[0].TotalHashes).Mul64(100000).Div(globalDiff).Lo) / 1000
|
||||
|
||||
if currentEffort <= 0 || poolBlocks[0].TotalHashes == 0 {
|
||||
currentEffort = 0
|
||||
|
@ -140,7 +139,7 @@ func main() {
|
|||
Miners: len(miners),
|
||||
Blocks: blockCount,
|
||||
Uncles: uncleCount,
|
||||
Weight: types.Difficulty{Uint128: windowDifficulty},
|
||||
Weight: windowDifficulty,
|
||||
},
|
||||
WindowSize: p2pool.PPLNSWindow,
|
||||
BlockTime: p2pool.BlockTime,
|
||||
|
|
|
@ -77,11 +77,11 @@ func MapJSONBlock(api *api.Api, block database.BlockInterface, extraUncleData, e
|
|||
Height: uncle.ParentHeight,
|
||||
}
|
||||
|
||||
weight.Uint128 = weight.Mul64(100 - p2pool.UnclePenalty).Div64(100)
|
||||
weight = weight.Mul64(100 - p2pool.UnclePenalty).Div64(100)
|
||||
} else {
|
||||
for u := range api.GetDatabase().GetUnclesByParentId(b.Id) {
|
||||
uncleWeight := u.Block.Difficulty.Mul64(p2pool.UnclePenalty).Div64(100)
|
||||
weight.Uint128 = weight.Add(uncleWeight)
|
||||
weight = weight.Add(uncleWeight)
|
||||
|
||||
if !extraUncleData {
|
||||
b.Uncles = append(b.Uncles, &database.JSONUncleBlockSimple{
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/rand"
|
||||
"lukechampine.com/uint128"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -37,10 +36,10 @@ func cacheHeightDifficulty(height uint64) {
|
|||
if _, ok := getHeightDifficulty(height); !ok {
|
||||
if header, err := client.GetClient().GetBlockHeaderByHeight(height); err != nil {
|
||||
if template, err := client.GetClient().GetBlockTemplate(types.DonationAddress); err != nil {
|
||||
setHeightDifficulty(uint64(template.Height), types.Difficulty{Uint128: uint128.From64(uint64(template.Difficulty))})
|
||||
setHeightDifficulty(uint64(template.Height), types.DifficultyFrom64(uint64(template.Difficulty)))
|
||||
}
|
||||
} else {
|
||||
setHeightDifficulty(header.BlockHeader.Height, types.Difficulty{Uint128: uint128.From64(header.BlockHeader.Difficulty)})
|
||||
setHeightDifficulty(header.BlockHeader.Height, types.DifficultyFrom64(header.BlockHeader.Difficulty))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -630,7 +630,7 @@ func main() {
|
|||
|
||||
miners[miner]["shares"].(*PositionChart).Add(int(toInt64(tip["height"])-toInt64(share["height"])), 1)
|
||||
diff := toUint64(share["weight"])
|
||||
miners[miner]["weight"] = types.Difficulty{Uint128: miners[miner]["weight"].(types.Difficulty).Add64(diff)}
|
||||
miners[miner]["weight"] = miners[miner]["weight"].(types.Difficulty).Add64(diff)
|
||||
|
||||
if _, ok := share["uncles"]; ok {
|
||||
for _, u := range share["uncles"].([]any) {
|
||||
|
@ -651,14 +651,14 @@ func main() {
|
|||
|
||||
miners[miner]["uncles"].(*PositionChart).Add(int(toInt64(tip["height"])-toInt64(uncle["height"])), 1)
|
||||
diff := toUint64(uncle["weight"])
|
||||
miners[miner]["weight"] = types.Difficulty{Uint128: miners[miner]["weight"].(types.Difficulty).Add64(diff)}
|
||||
miners[miner]["weight"] = miners[miner]["weight"].(types.Difficulty).Add64(diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
minerKeys := maps.Keys(miners)
|
||||
slices.SortFunc(minerKeys, func(a string, b string) bool {
|
||||
return miners[a]["weight"].(types.Difficulty).Cmp(miners[b]["weight"].(types.Difficulty).Uint128) > 0
|
||||
return miners[a]["weight"].(types.Difficulty).Cmp(miners[b]["weight"].(types.Difficulty)) > 0
|
||||
})
|
||||
|
||||
sortedMiners := make(mapslice.MapSlice, len(minerKeys))
|
||||
|
@ -709,7 +709,7 @@ func main() {
|
|||
|
||||
if buf, err := hex.DecodeString(string(s)); err == nil {
|
||||
|
||||
raw, _ = sidechain.NewShareFromBytes(buf)
|
||||
raw, _ = sidechain.NewShareFromExportedBytes(buf)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -793,16 +793,16 @@ func main() {
|
|||
unclesFound.Add(int(int64(tipHeight)-toInt64(parent["height"])), 1)
|
||||
if toUint64(s["height"]) > wend {
|
||||
unclesInWindow++
|
||||
windowDiff.Uint128 = windowDiff.Add64(toUint64(s["weight"]))
|
||||
windowDiff = windowDiff.Add64(toUint64(s["weight"]))
|
||||
}
|
||||
} else {
|
||||
sharesFound.Add(int(int64(tipHeight)-toInt64(s["height"])), 1)
|
||||
if toUint64(s["height"]) > wend {
|
||||
sharesInWindow++
|
||||
windowDiff.Uint128 = windowDiff.Add64(toUint64(s["weight"]))
|
||||
windowDiff = windowDiff.Add64(toUint64(s["weight"]))
|
||||
}
|
||||
}
|
||||
longDiff.Uint128 = longDiff.Add64(toUint64(s["weight"]))
|
||||
longDiff = longDiff.Add64(toUint64(s["weight"]))
|
||||
}
|
||||
|
||||
if len(payouts) > 10 {
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"github.com/holiman/uint256"
|
||||
"golang.org/x/exp/slices"
|
||||
"lukechampine.com/uint128"
|
||||
"math/bits"
|
||||
"sync"
|
||||
)
|
||||
|
@ -23,7 +22,7 @@ var UndefinedDifficulty types.Difficulty
|
|||
func init() {
|
||||
copy(NilHash[:], bytes.Repeat([]byte{0}, types.HashSize))
|
||||
copy(UndefinedHash[:], bytes.Repeat([]byte{0xff}, types.HashSize))
|
||||
UndefinedDifficulty.Uint128 = uint128.FromBytes(bytes.Repeat([]byte{0xff}, types.DifficultySize))
|
||||
UndefinedDifficulty = types.DifficultyFromBytes(bytes.Repeat([]byte{0xff}, types.DifficultySize))
|
||||
}
|
||||
|
||||
type BlockInterface interface {
|
||||
|
@ -120,7 +119,7 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.Share, knownUncles []*si
|
|||
}
|
||||
|
||||
block = &Block{
|
||||
Id: b.CoinbaseExtra.SideId,
|
||||
Id: types.HashFromBytes(b.CoinbaseExtra(sidechain.SideTemplateId)),
|
||||
Height: b.Side.Height,
|
||||
PreviousId: b.Side.Parent,
|
||||
Coinbase: BlockCoinbase{
|
||||
|
@ -136,9 +135,9 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.Share, knownUncles []*si
|
|||
Difficulty: b.Side.Difficulty,
|
||||
Timestamp: b.Main.Timestamp,
|
||||
MinerId: miner.Id(),
|
||||
PowHash: b.Extra.PowHash,
|
||||
PowHash: b.PowHash(),
|
||||
Main: BlockMainData{
|
||||
Id: b.Extra.MainId,
|
||||
Id: b.MainId(),
|
||||
Height: b.Main.Coinbase.GenHeight,
|
||||
Found: b.IsProofHigherThanDifficulty(),
|
||||
},
|
||||
|
@ -147,13 +146,13 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.Share, knownUncles []*si
|
|||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
}{
|
||||
Id: b.Main.PreviousId,
|
||||
Difficulty: b.Extra.MainDifficulty,
|
||||
Difficulty: b.MainDifficulty(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, u := range b.Side.Uncles {
|
||||
if i := slices.IndexFunc(knownUncles, func(uncle *sidechain.Share) bool {
|
||||
return uncle.CoinbaseExtra.SideId == u
|
||||
return types.HashFromBytes(uncle.CoinbaseExtra(sidechain.SideTemplateId)) == u
|
||||
}); i != -1 {
|
||||
uncle := knownUncles[i]
|
||||
uncleMiner := db.GetOrCreateMinerByAddress(uncle.GetAddress().ToBase58())
|
||||
|
@ -162,7 +161,7 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.Share, knownUncles []*si
|
|||
}
|
||||
uncles = append(uncles, &UncleBlock{
|
||||
Block: Block{
|
||||
Id: uncle.CoinbaseExtra.SideId,
|
||||
Id: types.HashFromBytes(uncle.CoinbaseExtra(sidechain.SideTemplateId)),
|
||||
Height: uncle.Side.Height,
|
||||
PreviousId: uncle.Side.Parent,
|
||||
Coinbase: BlockCoinbase{
|
||||
|
@ -178,9 +177,9 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.Share, knownUncles []*si
|
|||
Difficulty: uncle.Side.Difficulty,
|
||||
Timestamp: uncle.Main.Timestamp,
|
||||
MinerId: uncleMiner.Id(),
|
||||
PowHash: uncle.Extra.PowHash,
|
||||
PowHash: uncle.PowHash(),
|
||||
Main: BlockMainData{
|
||||
Id: uncle.Extra.MainId,
|
||||
Id: uncle.MainId(),
|
||||
Height: uncle.Main.Coinbase.GenHeight,
|
||||
Found: uncle.IsProofHigherThanDifficulty(),
|
||||
},
|
||||
|
@ -189,7 +188,7 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.Share, knownUncles []*si
|
|||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
}{
|
||||
Id: uncle.Main.PreviousId,
|
||||
Difficulty: uncle.Extra.MainDifficulty,
|
||||
Difficulty: uncle.MainDifficulty(),
|
||||
},
|
||||
},
|
||||
ParentId: block.Id,
|
||||
|
@ -390,7 +389,7 @@ func NewBlockFromJSONBlock(db *Database, data []byte) (block *Block, uncles []*U
|
|||
Reward: 0,
|
||||
PrivateKey: b.TxPriv,
|
||||
},
|
||||
Difficulty: types.Difficulty{Uint128: uint128.From64(b.Diff)},
|
||||
Difficulty: types.DifficultyFrom64(b.Diff),
|
||||
Timestamp: b.Ts,
|
||||
MinerId: miner.Id(),
|
||||
PowHash: b.PowHash,
|
||||
|
@ -424,7 +423,7 @@ func NewBlockFromJSONBlock(db *Database, data []byte) (block *Block, uncles []*U
|
|||
Reward: 0,
|
||||
PrivateKey: NilHash,
|
||||
},
|
||||
Difficulty: types.Difficulty{Uint128: uint128.From64(b.Diff)},
|
||||
Difficulty: types.DifficultyFrom64(b.Diff),
|
||||
Timestamp: u.Ts,
|
||||
MinerId: uncleMiner.Id(),
|
||||
PowHash: NilHash,
|
||||
|
@ -459,7 +458,7 @@ func (b *Block) GetBlock() *Block {
|
|||
}
|
||||
|
||||
func (b *Block) IsProofHigherThanDifficulty() bool {
|
||||
return b.GetProofDifficulty().Cmp(b.Template.Difficulty.Uint128) >= 0
|
||||
return b.GetProofDifficulty().Cmp(b.Template.Difficulty) >= 0
|
||||
}
|
||||
|
||||
func (b *Block) GetProofDifficulty() types.Difficulty {
|
||||
|
@ -472,5 +471,5 @@ func (b *Block) GetProofDifficulty() types.Difficulty {
|
|||
}
|
||||
|
||||
powResult := uint256.NewInt(0).Div(base, pow).Bytes32()
|
||||
return types.Difficulty{Uint128: uint128.FromBytes(powResult[16:]).ReverseBytes()}
|
||||
return types.DifficultyFromBytes(powResult[16:])
|
||||
}
|
||||
|
|
|
@ -80,7 +80,12 @@ func MatchOutputs(c *transaction.CoinbaseTransaction, miners []*Miner, privateKe
|
|||
if o == nil {
|
||||
continue
|
||||
}
|
||||
sharedData := address.GetDerivationSharedDataForOutputIndex(derivation, uint64(o.Index))
|
||||
|
||||
if o.Type == transaction.TxOutToTaggedKey && o.ViewTag != address.GetDerivationViewTagForOutputIndex(derivation, o.Index) { //fast check
|
||||
continue
|
||||
}
|
||||
|
||||
sharedData := address.GetDerivationSharedDataForOutputIndex(derivation, o.Index)
|
||||
if bytes.Compare(o.EphemeralPublicKey[:], miner.MoneroAddress().GetPublicKeyForSharedData(sharedData).Bytes()) == 0 {
|
||||
//TODO: maybe clone?
|
||||
result = append(result, outputResult{
|
||||
|
|
3
go.mod
3
go.mod
|
@ -7,7 +7,8 @@ require (
|
|||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20221005074023-b6ca970f3050
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221025112134-5190471ef823
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20221007140323-a2daa2d5fc48
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221026101906-a564ce478425
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221027134633-11f5607e6752
|
||||
github.com/Code-Hex/go-generics-cache v1.2.1
|
||||
github.com/ake-persson/mapslice-json v0.0.0-20210720081907-22c8edf57807
|
||||
github.com/go-faster/xor v0.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
|
|
6
go.sum
6
go.sum
|
@ -6,8 +6,10 @@ git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221025112134-5190471ef823 h1:Hx
|
|||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221025112134-5190471ef823/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20221007140323-a2daa2d5fc48 h1:ExrYG0RSrx/I4McPWgUF4B8R2OkblMrMki2ia8vG6Bw=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20221007140323-a2daa2d5fc48/go.mod h1:XeSC8jK8RXnnzVAmp9e9AQZCDIbML3UoCRkxxGA+lpU=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221026101906-a564ce478425 h1:yfctSdB0kRzKo4Wr1jtJEpCFY4OXD/xcIFDTkUuBncg=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221026101906-a564ce478425/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221027134633-11f5607e6752 h1:4r4KXpFLbixah+OGrBT9ZEflSZoFHD7aVJpXL3ukVIo=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221027134633-11f5607e6752/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
github.com/Code-Hex/go-generics-cache v1.2.1 h1:jKof8Hk8mr28lcEAo9g90oj7H3vb8y2DdccoGpL4lyE=
|
||||
github.com/Code-Hex/go-generics-cache v1.2.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4=
|
||||
github.com/ake-persson/mapslice-json v0.0.0-20210720081907-22c8edf57807 h1:w3nrGk00TWs/4iZ3Q0k9c0vL0e/wRziArKU4e++d/nA=
|
||||
github.com/ake-persson/mapslice-json v0.0.0-20210720081907-22c8edf57807/go.mod h1:fGnnfniJiO/ajHAVHqMSUSL8sE9LmU9rzclCtoeB+y8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
|
|
|
@ -2,15 +2,12 @@ package address
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"filippo.io/edwards25519"
|
||||
"git.gammaspectra.live/P2Pool/moneroutil"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Address struct {
|
||||
|
@ -18,6 +15,8 @@ type Address struct {
|
|||
SpendPub edwards25519.Point
|
||||
ViewPub edwards25519.Point
|
||||
Checksum []byte
|
||||
|
||||
moneroAddress atomic.Pointer[edwards25519.Scalar]
|
||||
}
|
||||
|
||||
var scalar8, _ = edwards25519.NewScalar().SetCanonicalBytes([]byte{8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
|
@ -73,131 +72,6 @@ func (a *Address) ToBase58() string {
|
|||
return moneroutil.EncodeMoneroBase58([]byte{a.Network}, a.SpendPub.Bytes(), a.ViewPub.Bytes(), a.Checksum[:])
|
||||
}
|
||||
|
||||
func (a *Address) GetDerivationForPrivateKey(privateKey *edwards25519.Scalar) *edwards25519.Point {
|
||||
point := (&edwards25519.Point{}).ScalarMult(privateKey, &a.ViewPub)
|
||||
return (&edwards25519.Point{}).ScalarMult(scalar8, point)
|
||||
}
|
||||
|
||||
func GetDerivationSharedDataForOutputIndex(derivation *edwards25519.Point, outputIndex uint64) *edwards25519.Scalar {
|
||||
varIntBuf := make([]byte, binary.MaxVarintLen64)
|
||||
data := append(derivation.Bytes(), varIntBuf[:binary.PutUvarint(varIntBuf, outputIndex)]...)
|
||||
hasher := sha3.NewLegacyKeccak256()
|
||||
if _, err := hasher.Write(data); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return crypto.BytesToScalar(hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func GetDerivationViewTagForOutputIndex(derivation *edwards25519.Point, outputIndex uint64) uint8 {
|
||||
buf := make([]byte, 8+types.HashSize+binary.MaxVarintLen64)
|
||||
copy(buf, "view_tag")
|
||||
copy(buf[8:], derivation.Bytes())
|
||||
binary.PutUvarint(buf[8+types.HashSize:], outputIndex)
|
||||
|
||||
h := moneroutil.Keccak256(buf)
|
||||
return h[0]
|
||||
}
|
||||
|
||||
func (a *Address) GetDeterministicTransactionPrivateKey(prevId types.Hash) *edwards25519.Scalar {
|
||||
//TODO: cache
|
||||
entropy := make([]byte, 0, 13+types.HashSize*2)
|
||||
entropy = append(entropy, "tx_secret_key"...)
|
||||
entropy = append(entropy, a.SpendPub.Bytes()...)
|
||||
entropy = append(entropy, prevId[:]...)
|
||||
return crypto.DeterministicScalar(entropy)
|
||||
}
|
||||
|
||||
func (a *Address) GetPublicKeyForSharedData(sharedData *edwards25519.Scalar) *edwards25519.Point {
|
||||
sG := (&edwards25519.Point{}).ScalarBaseMult(sharedData)
|
||||
|
||||
return (&edwards25519.Point{}).Add(sG, &a.SpendPub)
|
||||
|
||||
}
|
||||
|
||||
func (a *Address) GetEphemeralPublicKey(txKey types.Hash, outputIndex uint64) (result types.Hash) {
|
||||
pK, _ := edwards25519.NewScalar().SetCanonicalBytes(txKey[:])
|
||||
copy(result[:], a.GetPublicKeyForSharedData(GetDerivationSharedDataForOutputIndex(a.GetDerivationForPrivateKey(pK), outputIndex)).Bytes())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *Address) GetTxProof(txId types.Hash, txKey types.Hash, message string) string {
|
||||
|
||||
var prefixData []byte
|
||||
prefixData = append(prefixData, txId[:]...)
|
||||
prefixData = append(prefixData, []byte(message)...)
|
||||
prefixHash := types.Hash(moneroutil.Keccak256(prefixData))
|
||||
|
||||
txKeyS, _ := edwards25519.NewScalar().SetCanonicalBytes(txKey[:])
|
||||
|
||||
sharedSecret := (&edwards25519.Point{}).ScalarMult(txKeyS, &a.ViewPub)
|
||||
txPublicKey := (&edwards25519.Point{}).ScalarBaseMult(txKeyS)
|
||||
|
||||
// pick random k
|
||||
k := crypto.RandomScalar()
|
||||
//k := crypto.HashToScalar(txId)
|
||||
|
||||
buf := make([]byte, 0)
|
||||
buf = append(buf, prefixHash[:]...) //msg
|
||||
buf = append(buf, sharedSecret.Bytes()...) //D
|
||||
|
||||
X := (&edwards25519.Point{}).ScalarBaseMult(k)
|
||||
buf = append(buf, X.Bytes()...) //X
|
||||
|
||||
Y := (&edwards25519.Point{}).ScalarMult(k, &a.ViewPub)
|
||||
buf = append(buf, Y.Bytes()...) //Y
|
||||
|
||||
sep := types.Hash(moneroutil.Keccak256([]byte("TXPROOF_V2"))) // HASH_KEY_TXPROOF_V2
|
||||
buf = append(buf, sep[:]...) //sep
|
||||
|
||||
buf = append(buf, txPublicKey.Bytes()...) //R
|
||||
buf = append(buf, a.ViewPub.Bytes()...) //A
|
||||
buf = append(buf, bytes.Repeat([]byte{0}, 32)...) //B
|
||||
|
||||
sig := &crypto.Signature{}
|
||||
|
||||
sig.C = crypto.HashToScalar(types.Hash(moneroutil.Keccak256(buf)))
|
||||
|
||||
sig.R = edwards25519.NewScalar().Subtract(k, edwards25519.NewScalar().Multiply(sig.C, txKeyS))
|
||||
|
||||
return "OutProofV2" + moneroutil.EncodeMoneroBase58(sharedSecret.Bytes()) + moneroutil.EncodeMoneroBase58(append(sig.C.Bytes(), sig.R.Bytes()...))
|
||||
}
|
||||
|
||||
func (a *Address) GetTxProofV1(txId types.Hash, txKey types.Hash, message string) string {
|
||||
|
||||
var prefixData []byte
|
||||
prefixData = append(prefixData, txId[:]...)
|
||||
prefixData = append(prefixData, []byte(message)...)
|
||||
prefixHash := types.Hash(moneroutil.Keccak256(prefixData))
|
||||
|
||||
txKeyS, _ := edwards25519.NewScalar().SetCanonicalBytes(txKey[:])
|
||||
|
||||
sharedSecret := (&edwards25519.Point{}).ScalarMult(txKeyS, &a.ViewPub)
|
||||
|
||||
// pick random k
|
||||
k := crypto.RandomScalar()
|
||||
//k := crypto.HashToScalar(txId)
|
||||
|
||||
buf := make([]byte, 0)
|
||||
buf = append(buf, prefixHash[:]...) //msg
|
||||
buf = append(buf, sharedSecret.Bytes()...) //D
|
||||
|
||||
X := (&edwards25519.Point{}).ScalarBaseMult(k)
|
||||
buf = append(buf, X.Bytes()...) //X
|
||||
|
||||
Y := (&edwards25519.Point{}).ScalarMult(k, &a.ViewPub)
|
||||
buf = append(buf, Y.Bytes()...) //Y
|
||||
|
||||
sig := &crypto.Signature{}
|
||||
|
||||
sig.C = crypto.HashToScalar(types.Hash(moneroutil.Keccak256(buf)))
|
||||
|
||||
sig.R = edwards25519.NewScalar().Subtract(k, edwards25519.NewScalar().Multiply(sig.C, txKeyS))
|
||||
|
||||
return "OutProofV1" + moneroutil.EncodeMoneroBase58(sharedSecret.Bytes()) + moneroutil.EncodeMoneroBase58(append(sig.C.Bytes(), sig.R.Bytes()...))
|
||||
}
|
||||
|
||||
func (a *Address) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(a.ToBase58())
|
||||
}
|
||||
|
@ -218,56 +92,3 @@ func (a *Address) UnmarshalJSON(b []byte) error {
|
|||
return errors.New("invalid address")
|
||||
}
|
||||
}
|
||||
|
||||
type SignatureVerifyResult int
|
||||
|
||||
const (
|
||||
ResultFail SignatureVerifyResult = iota
|
||||
ResultSuccessSpend
|
||||
ResultSuccessView
|
||||
)
|
||||
|
||||
func (a *Address) getMessageHash(message []byte, mode uint8) types.Hash {
|
||||
buf := make([]byte, 0)
|
||||
buf = append(buf, []byte("MoneroMessageSignature")...)
|
||||
buf = append(buf, []byte{0}...) //null byte for previous string
|
||||
buf = append(buf, a.SpendPub.Bytes()...)
|
||||
buf = append(buf, a.ViewPub.Bytes()...)
|
||||
buf = append(buf, []byte{mode}...) //mode, 0 = sign with spend key, 1 = sign with view key
|
||||
buf = binary.AppendUvarint(buf, uint64(len(message)))
|
||||
buf = append(buf, message...)
|
||||
return types.Hash(moneroutil.Keccak256(buf))
|
||||
}
|
||||
|
||||
func (a *Address) Verify(message []byte, signature string) SignatureVerifyResult {
|
||||
var hash types.Hash
|
||||
|
||||
if strings.HasPrefix(signature, "SigV1") {
|
||||
hash = types.Hash(moneroutil.Keccak256(message))
|
||||
} else if strings.HasPrefix(signature, "SigV2") {
|
||||
hash = a.getMessageHash(message, 0)
|
||||
} else {
|
||||
return ResultFail
|
||||
}
|
||||
raw := moneroutil.DecodeMoneroBase58(signature[5:])
|
||||
|
||||
sig := crypto.NewSignatureFromBytes(raw)
|
||||
|
||||
if sig == nil {
|
||||
return ResultFail
|
||||
}
|
||||
|
||||
if crypto.CheckSignature(hash, &a.SpendPub, sig) {
|
||||
return ResultSuccessSpend
|
||||
}
|
||||
|
||||
if strings.HasPrefix(signature, "SigV2") {
|
||||
hash = a.getMessageHash(message, 1)
|
||||
}
|
||||
|
||||
if crypto.CheckSignature(hash, &a.ViewPub, sig) {
|
||||
return ResultSuccessView
|
||||
}
|
||||
|
||||
return ResultFail
|
||||
}
|
||||
|
|
102
monero/address/cache.go
Normal file
102
monero/address/cache.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package address
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"filippo.io/edwards25519"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"github.com/Code-Hex/go-generics-cache/policy/lfu"
|
||||
)
|
||||
|
||||
type derivationCacheKey [types.HashSize * 2]byte
|
||||
type sharedDataCacheKey [types.HashSize + 8]byte
|
||||
|
||||
type keyPair struct {
|
||||
private *edwards25519.Scalar
|
||||
public *edwards25519.Point
|
||||
}
|
||||
|
||||
type DerivationCache struct {
|
||||
deterministicKeyCache *lfu.Cache[derivationCacheKey, *keyPair]
|
||||
derivationCache *lfu.Cache[derivationCacheKey, *edwards25519.Point]
|
||||
sharedDataCache *lfu.Cache[sharedDataCacheKey, *edwards25519.Scalar]
|
||||
ephemeralPublicKeyCache *lfu.Cache[derivationCacheKey, types.Hash]
|
||||
}
|
||||
|
||||
func NewDerivationCache() *DerivationCache {
|
||||
d := &DerivationCache{}
|
||||
d.Clear()
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DerivationCache) Clear() {
|
||||
//keep a few recent blocks from the past few for uncles, and reused window miners
|
||||
//~10s per share, keys change every monero block (2m). around 2160 max shares per 6h (window), plus uncles. 6 shares per minute.
|
||||
//each share can have up to 2160 outputs, plus uncles. each miner has its own private key per monero block
|
||||
d.deterministicKeyCache = lfu.NewCache[derivationCacheKey, *keyPair](lfu.WithCapacity(4096))
|
||||
d.derivationCache = lfu.NewCache[derivationCacheKey, *edwards25519.Point](lfu.WithCapacity(4096))
|
||||
d.sharedDataCache = lfu.NewCache[sharedDataCacheKey, *edwards25519.Scalar](lfu.WithCapacity(4096 * 2160))
|
||||
d.ephemeralPublicKeyCache = lfu.NewCache[derivationCacheKey, types.Hash](lfu.WithCapacity(4096 * 2160))
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetEphemeralPublicKey(address *Address, txKey types.Hash, outputIndex uint64) types.Hash {
|
||||
sharedData := d.GetSharedData(address, txKey, outputIndex)
|
||||
|
||||
var key derivationCacheKey
|
||||
copy(key[:], address.SpendPub.Bytes())
|
||||
copy(key[types.HashSize:], sharedData.Bytes())
|
||||
if ephemeralPubKey, ok := d.ephemeralPublicKeyCache.Get(key); !ok {
|
||||
copy(ephemeralPubKey[:], address.GetPublicKeyForSharedData(sharedData).Bytes())
|
||||
d.ephemeralPublicKeyCache.Set(key, ephemeralPubKey)
|
||||
return ephemeralPubKey
|
||||
} else {
|
||||
return ephemeralPubKey
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetSharedData(address *Address, txKey types.Hash, outputIndex uint64) *edwards25519.Scalar {
|
||||
derivation := d.GetDerivation(address, txKey)
|
||||
|
||||
var key sharedDataCacheKey
|
||||
copy(key[:], derivation.Bytes())
|
||||
binary.LittleEndian.PutUint64(key[types.HashSize:], outputIndex)
|
||||
|
||||
if sharedData, ok := d.sharedDataCache.Get(key); !ok {
|
||||
sharedData = GetDerivationSharedDataForOutputIndex(derivation, outputIndex)
|
||||
d.sharedDataCache.Set(key, sharedData)
|
||||
return sharedData
|
||||
} else {
|
||||
return sharedData
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetDeterministicTransactionKey(address *Address, prevId types.Hash) (private *edwards25519.Scalar, public *edwards25519.Point) {
|
||||
var key derivationCacheKey
|
||||
copy(key[:], address.SpendPub.Bytes())
|
||||
copy(key[types.HashSize:], prevId[:])
|
||||
|
||||
if kp, ok := d.deterministicKeyCache.Get(key); !ok {
|
||||
kp = &keyPair{
|
||||
private: address.GetDeterministicTransactionPrivateKey(prevId),
|
||||
}
|
||||
kp.public = edwards25519.NewIdentityPoint().ScalarBaseMult(kp.private)
|
||||
d.deterministicKeyCache.Set(key, kp)
|
||||
return kp.private, kp.public
|
||||
} else {
|
||||
return kp.private, kp.public
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DerivationCache) GetDerivation(address *Address, txKey types.Hash) *edwards25519.Point {
|
||||
var key derivationCacheKey
|
||||
copy(key[:], address.ViewPub.Bytes())
|
||||
copy(key[types.HashSize:], txKey[:])
|
||||
|
||||
if derivation, ok := d.derivationCache.Get(key); !ok {
|
||||
pK, _ := edwards25519.NewScalar().SetCanonicalBytes(txKey[:])
|
||||
derivation = address.GetDerivationForPrivateKey(pK)
|
||||
d.derivationCache.Set(key, derivation)
|
||||
return derivation
|
||||
} else {
|
||||
return derivation
|
||||
}
|
||||
}
|
190
monero/address/crypto.go
Normal file
190
monero/address/crypto.go
Normal file
|
@ -0,0 +1,190 @@
|
|||
package address
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"filippo.io/edwards25519"
|
||||
"git.gammaspectra.live/P2Pool/moneroutil"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (a *Address) GetDerivationForPrivateKey(privateKey *edwards25519.Scalar) *edwards25519.Point {
|
||||
point := (&edwards25519.Point{}).ScalarMult(privateKey, &a.ViewPub)
|
||||
return (&edwards25519.Point{}).ScalarMult(scalar8, point)
|
||||
}
|
||||
|
||||
func GetDerivationSharedDataForOutputIndex(derivation *edwards25519.Point, outputIndex uint64) *edwards25519.Scalar {
|
||||
varIntBuf := make([]byte, binary.MaxVarintLen64)
|
||||
data := append(derivation.Bytes(), varIntBuf[:binary.PutUvarint(varIntBuf, outputIndex)]...)
|
||||
hasher := sha3.NewLegacyKeccak256()
|
||||
if _, err := hasher.Write(data); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return crypto.BytesToScalar(hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func GetDerivationViewTagForOutputIndex(derivation *edwards25519.Point, outputIndex uint64) uint8 {
|
||||
buf := make([]byte, 8+types.HashSize+binary.MaxVarintLen64)
|
||||
copy(buf, "view_tag")
|
||||
copy(buf[8:], derivation.Bytes())
|
||||
binary.PutUvarint(buf[8+types.HashSize:], outputIndex)
|
||||
|
||||
h := moneroutil.Keccak256(buf)
|
||||
return h[0]
|
||||
}
|
||||
|
||||
func (a *Address) GetDeterministicTransactionPrivateKey(prevId types.Hash) *edwards25519.Scalar {
|
||||
//TODO: cache
|
||||
entropy := make([]byte, 0, 13+types.HashSize*2)
|
||||
entropy = append(entropy, "tx_secret_key"...)
|
||||
entropy = append(entropy, a.SpendPub.Bytes()...)
|
||||
entropy = append(entropy, prevId[:]...)
|
||||
return crypto.DeterministicScalar(entropy)
|
||||
}
|
||||
|
||||
func (a *Address) GetPublicKeyForSharedData(sharedData *edwards25519.Scalar) *edwards25519.Point {
|
||||
sG := (&edwards25519.Point{}).ScalarBaseMult(sharedData)
|
||||
|
||||
return (&edwards25519.Point{}).Add(sG, &a.SpendPub)
|
||||
|
||||
}
|
||||
|
||||
func (a *Address) GetEphemeralPublicKey(txKey types.Hash, outputIndex uint64) (result types.Hash) {
|
||||
pK, _ := edwards25519.NewScalar().SetCanonicalBytes(txKey[:])
|
||||
copy(result[:], a.GetPublicKeyForSharedData(GetDerivationSharedDataForOutputIndex(a.GetDerivationForPrivateKey(pK), outputIndex)).Bytes())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *Address) GetTxProof(txId types.Hash, txKey types.Hash, message string) string {
|
||||
|
||||
var prefixData []byte
|
||||
prefixData = append(prefixData, txId[:]...)
|
||||
prefixData = append(prefixData, []byte(message)...)
|
||||
prefixHash := types.Hash(moneroutil.Keccak256(prefixData))
|
||||
|
||||
txKeyS, _ := edwards25519.NewScalar().SetCanonicalBytes(txKey[:])
|
||||
|
||||
sharedSecret := (&edwards25519.Point{}).ScalarMult(txKeyS, &a.ViewPub)
|
||||
txPublicKey := (&edwards25519.Point{}).ScalarBaseMult(txKeyS)
|
||||
|
||||
// pick random k
|
||||
k := crypto.RandomScalar()
|
||||
//k := crypto.HashToScalar(txId)
|
||||
|
||||
buf := make([]byte, 0)
|
||||
buf = append(buf, prefixHash[:]...) //msg
|
||||
buf = append(buf, sharedSecret.Bytes()...) //D
|
||||
|
||||
X := (&edwards25519.Point{}).ScalarBaseMult(k)
|
||||
buf = append(buf, X.Bytes()...) //X
|
||||
|
||||
Y := (&edwards25519.Point{}).ScalarMult(k, &a.ViewPub)
|
||||
buf = append(buf, Y.Bytes()...) //Y
|
||||
|
||||
sep := types.Hash(moneroutil.Keccak256([]byte("TXPROOF_V2"))) // HASH_KEY_TXPROOF_V2
|
||||
buf = append(buf, sep[:]...) //sep
|
||||
|
||||
buf = append(buf, txPublicKey.Bytes()...) //R
|
||||
buf = append(buf, a.ViewPub.Bytes()...) //A
|
||||
buf = append(buf, bytes.Repeat([]byte{0}, 32)...) //B
|
||||
|
||||
sig := &crypto.Signature{}
|
||||
|
||||
sig.C = crypto.HashToScalar(types.Hash(moneroutil.Keccak256(buf)))
|
||||
|
||||
sig.R = edwards25519.NewScalar().Subtract(k, edwards25519.NewScalar().Multiply(sig.C, txKeyS))
|
||||
|
||||
return "OutProofV2" + moneroutil.EncodeMoneroBase58(sharedSecret.Bytes()) + moneroutil.EncodeMoneroBase58(append(sig.C.Bytes(), sig.R.Bytes()...))
|
||||
}
|
||||
|
||||
func (a *Address) GetTxProofV1(txId types.Hash, txKey types.Hash, message string) string {
|
||||
|
||||
var prefixData []byte
|
||||
prefixData = append(prefixData, txId[:]...)
|
||||
prefixData = append(prefixData, []byte(message)...)
|
||||
prefixHash := types.Hash(moneroutil.Keccak256(prefixData))
|
||||
|
||||
txKeyS, _ := edwards25519.NewScalar().SetCanonicalBytes(txKey[:])
|
||||
|
||||
sharedSecret := (&edwards25519.Point{}).ScalarMult(txKeyS, &a.ViewPub)
|
||||
|
||||
// pick random k
|
||||
k := crypto.RandomScalar()
|
||||
//k := crypto.HashToScalar(txId)
|
||||
|
||||
buf := make([]byte, 0)
|
||||
buf = append(buf, prefixHash[:]...) //msg
|
||||
buf = append(buf, sharedSecret.Bytes()...) //D
|
||||
|
||||
X := (&edwards25519.Point{}).ScalarBaseMult(k)
|
||||
buf = append(buf, X.Bytes()...) //X
|
||||
|
||||
Y := (&edwards25519.Point{}).ScalarMult(k, &a.ViewPub)
|
||||
buf = append(buf, Y.Bytes()...) //Y
|
||||
|
||||
sig := &crypto.Signature{}
|
||||
|
||||
sig.C = crypto.HashToScalar(types.Hash(moneroutil.Keccak256(buf)))
|
||||
|
||||
sig.R = edwards25519.NewScalar().Subtract(k, edwards25519.NewScalar().Multiply(sig.C, txKeyS))
|
||||
|
||||
return "OutProofV1" + moneroutil.EncodeMoneroBase58(sharedSecret.Bytes()) + moneroutil.EncodeMoneroBase58(append(sig.C.Bytes(), sig.R.Bytes()...))
|
||||
}
|
||||
|
||||
type SignatureVerifyResult int
|
||||
|
||||
const (
|
||||
ResultFail SignatureVerifyResult = iota
|
||||
ResultSuccessSpend
|
||||
ResultSuccessView
|
||||
)
|
||||
|
||||
func (a *Address) getMessageHash(message []byte, mode uint8) types.Hash {
|
||||
buf := make([]byte, 0)
|
||||
buf = append(buf, []byte("MoneroMessageSignature")...)
|
||||
buf = append(buf, []byte{0}...) //null byte for previous string
|
||||
buf = append(buf, a.SpendPub.Bytes()...)
|
||||
buf = append(buf, a.ViewPub.Bytes()...)
|
||||
buf = append(buf, []byte{mode}...) //mode, 0 = sign with spend key, 1 = sign with view key
|
||||
buf = binary.AppendUvarint(buf, uint64(len(message)))
|
||||
buf = append(buf, message...)
|
||||
return types.Hash(moneroutil.Keccak256(buf))
|
||||
}
|
||||
|
||||
func (a *Address) Verify(message []byte, signature string) SignatureVerifyResult {
|
||||
var hash types.Hash
|
||||
|
||||
if strings.HasPrefix(signature, "SigV1") {
|
||||
hash = types.Hash(moneroutil.Keccak256(message))
|
||||
} else if strings.HasPrefix(signature, "SigV2") {
|
||||
hash = a.getMessageHash(message, 0)
|
||||
} else {
|
||||
return ResultFail
|
||||
}
|
||||
raw := moneroutil.DecodeMoneroBase58(signature[5:])
|
||||
|
||||
sig := crypto.NewSignatureFromBytes(raw)
|
||||
|
||||
if sig == nil {
|
||||
return ResultFail
|
||||
}
|
||||
|
||||
if crypto.CheckSignature(hash, &a.SpendPub, sig) {
|
||||
return ResultSuccessSpend
|
||||
}
|
||||
|
||||
if strings.HasPrefix(signature, "SigV2") {
|
||||
hash = a.getMessageHash(message, 1)
|
||||
}
|
||||
|
||||
if crypto.CheckSignature(hash, &a.ViewPub, sig) {
|
||||
return ResultSuccessView
|
||||
}
|
||||
|
||||
return ResultFail
|
||||
}
|
|
@ -9,7 +9,6 @@ import (
|
|||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Block struct {
|
||||
|
@ -215,14 +214,20 @@ func (b *Block) TxTreeHash() (rootHash types.Hash) {
|
|||
return
|
||||
}
|
||||
|
||||
func (b *Block) Difficulty() types.Difficulty {
|
||||
//cached by sidechain.Share
|
||||
d, _ := client.GetClient().GetDifficultyByHeight(b.Coinbase.GenHeight)
|
||||
return d
|
||||
}
|
||||
|
||||
func (b *Block) PowHash() types.Hash {
|
||||
//TODO: cache
|
||||
//cached by sidechain.Share
|
||||
h, _ := HashBlob(b.Coinbase.GenHeight, b.HashingBlob())
|
||||
return h
|
||||
}
|
||||
|
||||
func (b *Block) Id() types.Hash {
|
||||
//TODO: cache
|
||||
//cached by sidechain.Share
|
||||
buf := b.HashingBlob()
|
||||
|
||||
actualDataToHash := make([]byte, 0, len(buf)+binary.MaxVarintLen64)
|
||||
|
@ -232,36 +237,15 @@ func (b *Block) Id() types.Hash {
|
|||
return types.HashFromBytes(keccak(actualDataToHash))
|
||||
}
|
||||
|
||||
var cachedSeedLock sync.Mutex
|
||||
var cachedSeeds = make(map[uint64]types.Hash)
|
||||
var hasher = randomx.NewRandomX()
|
||||
|
||||
func AddSeedToCache(seedHeight uint64, seedHash types.Hash) {
|
||||
cachedSeedLock.Lock()
|
||||
defer cachedSeedLock.Unlock()
|
||||
addSeedToCache(seedHeight, seedHash)
|
||||
}
|
||||
func addSeedToCache(seedHeight uint64, seedHash types.Hash) {
|
||||
cachedSeeds[seedHeight] = seedHash
|
||||
}
|
||||
|
||||
func HashBlob(height uint64, blob []byte) (hash types.Hash, err error) {
|
||||
|
||||
cachedSeedLock.Lock()
|
||||
defer cachedSeedLock.Unlock()
|
||||
|
||||
var seedHash types.Hash
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
if h, ok := cachedSeeds[seedHeight]; ok {
|
||||
seedHash = h
|
||||
if seed, err := client.GetClient().GetSeedByHeight(height); err != nil {
|
||||
return types.ZeroHash, err
|
||||
} else {
|
||||
if seedHash, err = client.GetClient().GetBlockIdByHeight(seedHeight); err != nil {
|
||||
return types.Hash{}, err
|
||||
}
|
||||
addSeedToCache(seedHeight, seedHash)
|
||||
return hasher.Hash(seed[:], blob)
|
||||
}
|
||||
|
||||
return hasher.Hash(seedHash[:], blob)
|
||||
}
|
||||
|
||||
func keccakl(data []byte, len int) []byte {
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc"
|
||||
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc/daemon"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"github.com/Code-Hex/go-generics-cache/policy/lru"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
@ -53,8 +54,15 @@ func GetClient() *Client {
|
|||
|
||||
// Client TODO: ratelimit
|
||||
type Client struct {
|
||||
c *rpc.Client
|
||||
d *daemon.Client
|
||||
c *rpc.Client
|
||||
d *daemon.Client
|
||||
|
||||
difficultyCache *lru.Cache[uint64, types.Difficulty]
|
||||
|
||||
seedCache *lru.Cache[uint64, types.Hash]
|
||||
|
||||
coinbaseTransactionCache *lru.Cache[types.Hash, *transaction.CoinbaseTransaction]
|
||||
|
||||
throttler <-chan time.Time
|
||||
}
|
||||
|
||||
|
@ -64,35 +72,44 @@ func newClient() (*Client, error) {
|
|||
return nil, err
|
||||
}
|
||||
return &Client{
|
||||
c: c,
|
||||
d: daemon.NewClient(c),
|
||||
throttler: time.Tick(time.Second / 2),
|
||||
c: c,
|
||||
d: daemon.NewClient(c),
|
||||
difficultyCache: lru.NewCache[uint64, types.Difficulty](lru.WithCapacity(1024)),
|
||||
seedCache: lru.NewCache[uint64, types.Hash](lru.WithCapacity(1024)),
|
||||
coinbaseTransactionCache: lru.NewCache[types.Hash, *transaction.CoinbaseTransaction](lru.WithCapacity(1024)),
|
||||
throttler: time.Tick(time.Second / 2),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetCoinbaseTransaction(txId types.Hash) (*transaction.CoinbaseTransaction, error) {
|
||||
<-c.throttler
|
||||
if result, err := c.d.GetTransactions(context.Background(), []string{txId.String()}); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if len(result.Txs) != 1 {
|
||||
return nil, errors.New("invalid transaction count")
|
||||
}
|
||||
|
||||
if buf, err := hex.DecodeString(result.Txs[0].PrunedAsHex); err != nil {
|
||||
if tx, ok := c.coinbaseTransactionCache.Get(txId); !ok {
|
||||
<-c.throttler
|
||||
if result, err := c.d.GetTransactions(context.Background(), []string{txId.String()}); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
tx := &transaction.CoinbaseTransaction{}
|
||||
if err = tx.FromReader(bytes.NewReader(buf)); err != nil {
|
||||
if len(result.Txs) != 1 {
|
||||
return nil, errors.New("invalid transaction count")
|
||||
}
|
||||
|
||||
if buf, err := hex.DecodeString(result.Txs[0].PrunedAsHex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
tx = &transaction.CoinbaseTransaction{}
|
||||
if err = tx.UnmarshalBinary(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tx.Id() != txId {
|
||||
return nil, fmt.Errorf("expected %s, got %s", txId.String(), tx.Id().String())
|
||||
}
|
||||
if tx.Id() != txId {
|
||||
return nil, fmt.Errorf("expected %s, got %s", txId.String(), tx.Id().String())
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
c.coinbaseTransactionCache.Set(txId, tx)
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return tx, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,6 +125,48 @@ func (c *Client) GetBlockIdByHeight(height uint64) (types.Hash, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Client) AddSeedByHeightToCache(seedHeight uint64, seed types.Hash) {
|
||||
c.seedCache.Set(seedHeight, seed)
|
||||
}
|
||||
|
||||
func (c *Client) GetSeedByHeight(height uint64) (types.Hash, error) {
|
||||
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
|
||||
if seed, ok := c.seedCache.Get(seedHeight); !ok {
|
||||
if seed, err := c.GetBlockIdByHeight(seedHeight); err != nil {
|
||||
return types.ZeroHash, err
|
||||
} else {
|
||||
c.AddSeedByHeightToCache(seedHeight, seed)
|
||||
return seed, nil
|
||||
}
|
||||
} else {
|
||||
return seed, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetDifficultyByHeight(height uint64) (types.Difficulty, error) {
|
||||
if difficulty, ok := c.difficultyCache.Get(height); !ok {
|
||||
if header, err := c.GetBlockHeaderByHeight(height); err != nil {
|
||||
if template, err := c.GetBlockTemplate(types.DonationAddress); err != nil {
|
||||
return types.Difficulty{}, err
|
||||
} else if uint64(template.Height) == height {
|
||||
difficulty = types.DifficultyFrom64(uint64(template.Difficulty))
|
||||
c.difficultyCache.Set(height, difficulty)
|
||||
return difficulty, nil
|
||||
} else {
|
||||
return types.Difficulty{}, errors.New("height not found and is not next template")
|
||||
}
|
||||
} else {
|
||||
difficulty = types.DifficultyFrom64(uint64(header.BlockHeader.Difficulty))
|
||||
c.difficultyCache.Set(height, difficulty)
|
||||
return difficulty, nil
|
||||
}
|
||||
} else {
|
||||
return difficulty, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetBlockHeaderByHeight(height uint64) (*daemon.GetBlockHeaderByHeightResult, error) {
|
||||
<-c.throttler
|
||||
if result, err := c.d.GetBlockHeaderByHeight(context.Background(), height); err != nil {
|
||||
|
|
|
@ -41,6 +41,7 @@ func DeterministicScalar(entropy []byte) *edwards25519.Scalar {
|
|||
hash := make([]byte, h.Size())
|
||||
|
||||
for {
|
||||
h.Reset()
|
||||
counter++
|
||||
binary.LittleEndian.PutUint32(buf[len(entropy):], counter)
|
||||
h.Write(buf)
|
||||
|
|
|
@ -76,10 +76,23 @@ func (t *ExtraTags) FromReader(reader readerAndByteReader) (err error) {
|
|||
}
|
||||
return err
|
||||
}
|
||||
if t.GetTag(tag.Tag) != nil {
|
||||
return errors.New("tag already exists")
|
||||
}
|
||||
*t = append(*t, tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ExtraTags) GetTag(tag uint8) *ExtraTag {
|
||||
for i := range *t {
|
||||
if (*t)[i].Tag == tag {
|
||||
return &(*t)[i]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ExtraTag) UnmarshalBinary(data []byte) error {
|
||||
reader := bytes.NewReader(data)
|
||||
return t.FromReader(reader)
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"io"
|
||||
"lukechampine.com/uint128"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
@ -93,7 +92,7 @@ func (a *Api) GetFailedRawBlock(id types.Hash) (b *sidechain.Share, err error) {
|
|||
} else {
|
||||
data := make([]byte, len(buf)/2)
|
||||
_, _ = hex.Decode(data, buf)
|
||||
return sidechain.NewShareFromBytes(data)
|
||||
return sidechain.NewShareFromExportedBytes(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +111,7 @@ func (a *Api) GetRawBlock(id types.Hash) (b *sidechain.Share, err error) {
|
|||
} else {
|
||||
data := make([]byte, len(buf)/2)
|
||||
_, _ = hex.Decode(data, buf)
|
||||
return sidechain.NewShareFromBytes(data)
|
||||
return sidechain.NewShareFromExportedBytes(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,8 +137,8 @@ func (a *Api) GetShareFromRawEntry(id types.Hash, errOnUncles bool) (b *database
|
|||
}
|
||||
}
|
||||
|
||||
func (a *Api) GetBlockWindowPayouts(tip *database.Block) (shares map[uint64]uint128.Uint128) {
|
||||
shares = make(map[uint64]uint128.Uint128)
|
||||
func (a *Api) GetBlockWindowPayouts(tip *database.Block) (shares map[uint64]types.Difficulty) {
|
||||
shares = make(map[uint64]types.Difficulty)
|
||||
|
||||
blockCount := 0
|
||||
|
||||
|
@ -152,10 +151,10 @@ func (a *Api) GetBlockWindowPayouts(tip *database.Block) (shares map[uint64]uint
|
|||
|
||||
for {
|
||||
if _, ok := shares[block.MinerId]; !ok {
|
||||
shares[block.MinerId] = uint128.From64(0)
|
||||
shares[block.MinerId] = types.DifficultyFrom64(0)
|
||||
}
|
||||
|
||||
shares[block.MinerId] = shares[block.MinerId].Add(block.Difficulty.Uint128)
|
||||
shares[block.MinerId] = shares[block.MinerId].Add(block.Difficulty)
|
||||
|
||||
for uncle := range a.db.GetUnclesByParentId(block.Id) {
|
||||
if (tip.Height - uncle.Block.Height) >= p2pool.PPLNSWindow {
|
||||
|
@ -163,14 +162,14 @@ func (a *Api) GetBlockWindowPayouts(tip *database.Block) (shares map[uint64]uint
|
|||
}
|
||||
|
||||
if _, ok := shares[uncle.Block.MinerId]; !ok {
|
||||
shares[uncle.Block.MinerId] = uint128.From64(0)
|
||||
shares[uncle.Block.MinerId] = types.DifficultyFrom64(0)
|
||||
}
|
||||
|
||||
product := uncle.Block.Difficulty.Uint128.Mul64(p2pool.UnclePenalty)
|
||||
product := uncle.Block.Difficulty.Mul64(p2pool.UnclePenalty)
|
||||
unclePenalty := product.Div64(100)
|
||||
|
||||
shares[block.MinerId] = shares[block.MinerId].Add(unclePenalty)
|
||||
shares[uncle.Block.MinerId] = shares[uncle.Block.MinerId].Add(uncle.Block.Difficulty.Uint128.Sub(unclePenalty))
|
||||
shares[uncle.Block.MinerId] = shares[uncle.Block.MinerId].Add(uncle.Block.Difficulty.Sub(unclePenalty))
|
||||
}
|
||||
|
||||
blockCount++
|
||||
|
@ -187,13 +186,13 @@ func (a *Api) GetBlockWindowPayouts(tip *database.Block) (shares map[uint64]uint
|
|||
totalReward := tip.Coinbase.Reward
|
||||
|
||||
if totalReward > 0 {
|
||||
totalWeight := uint128.From64(0)
|
||||
totalWeight := types.DifficultyFrom64(0)
|
||||
for _, w := range shares {
|
||||
totalWeight = totalWeight.Add(w)
|
||||
}
|
||||
|
||||
w := uint128.From64(0)
|
||||
rewardGiven := uint128.From64(0)
|
||||
w := types.DifficultyFrom64(0)
|
||||
rewardGiven := types.DifficultyFrom64(0)
|
||||
|
||||
for miner, weight := range shares {
|
||||
w = w.Add(weight)
|
||||
|
@ -210,8 +209,8 @@ func (a *Api) GetBlockWindowPayouts(tip *database.Block) (shares map[uint64]uint
|
|||
return shares
|
||||
}
|
||||
|
||||
func (a *Api) GetWindowPayouts(height, totalReward *uint64) (shares map[uint64]uint128.Uint128) {
|
||||
shares = make(map[uint64]uint128.Uint128)
|
||||
func (a *Api) GetWindowPayouts(height, totalReward *uint64) (shares map[uint64]types.Difficulty) {
|
||||
shares = make(map[uint64]types.Difficulty)
|
||||
|
||||
var tip uint64
|
||||
if height != nil {
|
||||
|
@ -224,10 +223,10 @@ func (a *Api) GetWindowPayouts(height, totalReward *uint64) (shares map[uint64]u
|
|||
|
||||
for block := range a.db.GetBlocksInWindow(&tip, p2pool.PPLNSWindow) {
|
||||
if _, ok := shares[block.MinerId]; !ok {
|
||||
shares[block.MinerId] = uint128.From64(0)
|
||||
shares[block.MinerId] = types.DifficultyFrom64(0)
|
||||
}
|
||||
|
||||
shares[block.MinerId] = shares[block.MinerId].Add(block.Difficulty.Uint128)
|
||||
shares[block.MinerId] = shares[block.MinerId].Add(block.Difficulty)
|
||||
|
||||
for uncle := range a.db.GetUnclesByParentId(block.Id) {
|
||||
if (tip - uncle.Block.Height) >= p2pool.PPLNSWindow {
|
||||
|
@ -235,27 +234,27 @@ func (a *Api) GetWindowPayouts(height, totalReward *uint64) (shares map[uint64]u
|
|||
}
|
||||
|
||||
if _, ok := shares[uncle.Block.MinerId]; !ok {
|
||||
shares[uncle.Block.MinerId] = uint128.From64(0)
|
||||
shares[uncle.Block.MinerId] = types.DifficultyFrom64(0)
|
||||
}
|
||||
|
||||
product := uncle.Block.Difficulty.Uint128.Mul64(p2pool.UnclePenalty)
|
||||
product := uncle.Block.Difficulty.Mul64(p2pool.UnclePenalty)
|
||||
unclePenalty := product.Div64(100)
|
||||
|
||||
shares[block.MinerId] = shares[block.MinerId].Add(unclePenalty)
|
||||
shares[uncle.Block.MinerId] = shares[uncle.Block.MinerId].Add(uncle.Block.Difficulty.Uint128.Sub(unclePenalty))
|
||||
shares[uncle.Block.MinerId] = shares[uncle.Block.MinerId].Add(uncle.Block.Difficulty.Sub(unclePenalty))
|
||||
}
|
||||
|
||||
blockCount++
|
||||
}
|
||||
|
||||
if totalReward != nil && *totalReward > 0 {
|
||||
totalWeight := uint128.From64(0)
|
||||
totalWeight := types.DifficultyFrom64(0)
|
||||
for _, w := range shares {
|
||||
totalWeight = totalWeight.Add(w)
|
||||
}
|
||||
|
||||
w := uint128.From64(0)
|
||||
rewardGiven := uint128.From64(0)
|
||||
w := types.DifficultyFrom64(0)
|
||||
rewardGiven := types.DifficultyFrom64(0)
|
||||
|
||||
for miner, weight := range shares {
|
||||
w = w.Add(weight)
|
||||
|
|
|
@ -119,7 +119,7 @@ func NewConsensus(networkType NetworkType, poolName, poolPassword string, target
|
|||
return c
|
||||
}
|
||||
|
||||
func (i *Consensus) CalculateSideChainId(main *mainblock.Block, side *SideData) types.Hash {
|
||||
func (i *Consensus) CalculateSideTemplateId(main *mainblock.Block, side *SideData) types.Hash {
|
||||
|
||||
mainData, _ := main.SideChainHashingBlob()
|
||||
sideData, _ := side.MarshalBinary()
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package sidechain
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConsensusId(t *testing.T) {
|
||||
func TestDefaultConsensusId(t *testing.T) {
|
||||
id := ConsensusMini.CalculateId()
|
||||
if id != ConsensusMini.Id() {
|
||||
t.Fatalf("wrong mini sidechain id, expected %s, got %s", ConsensusMini.Id().String(), id.String())
|
||||
|
@ -13,3 +16,14 @@ func TestConsensusId(t *testing.T) {
|
|||
t.Fatalf("wrong default sidechain id, expected %s, got %s", ConsensusDefault.Id().String(), id.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverlyLongConsensus(t *testing.T) {
|
||||
|
||||
c := NewConsensus(NetworkMainnet, strings.Repeat("A", 128), strings.Repeat("A", 128), 10, 100000, 2160, 20)
|
||||
|
||||
c2 := NewConsensus(NetworkMainnet, strings.Repeat("A", 128), strings.Repeat("A", 128), 100, 1000000, 1000, 30)
|
||||
|
||||
if c.Id() == c2.Id() {
|
||||
t.Fatalf("consensus is different but ids are equal, %s, %s", c.Id().String(), c2.Id().String())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,19 @@ import (
|
|||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"github.com/holiman/uint256"
|
||||
"io"
|
||||
"lukechampine.com/uint128"
|
||||
"math/bits"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type CoinbaseExtraTag int
|
||||
|
||||
const SideExtraNonceSize = 4
|
||||
const SideExtraNonceMaxSize = SideExtraNonceSize + 10
|
||||
|
||||
const (
|
||||
SideCoinbasePublicKey = transaction.TxExtraTagPubKey
|
||||
SideExtraNonce = transaction.TxExtraTagNonce
|
||||
SideTemplateId = transaction.TxExtraTagMergeMining
|
||||
)
|
||||
|
||||
type Share struct {
|
||||
|
@ -21,36 +32,23 @@ type Share struct {
|
|||
|
||||
Side SideData
|
||||
|
||||
CoinbaseExtra struct {
|
||||
PublicKey types.Hash
|
||||
ExtraNonce []byte
|
||||
SideId types.Hash
|
||||
}
|
||||
|
||||
Extra struct {
|
||||
MainId types.Hash
|
||||
PowHash types.Hash
|
||||
MainDifficulty types.Difficulty
|
||||
Peer []byte
|
||||
c struct {
|
||||
lock sync.RWMutex
|
||||
mainId types.Hash
|
||||
mainDifficulty types.Difficulty
|
||||
templateId types.Hash
|
||||
powHash types.Hash
|
||||
}
|
||||
}
|
||||
|
||||
func NewShareFromBytes(buf []byte) (*Share, error) {
|
||||
func NewShareFromExportedBytes(buf []byte) (*Share, error) {
|
||||
b := &Share{}
|
||||
return b, b.UnmarshalBinary(buf)
|
||||
}
|
||||
|
||||
func (b *Share) TemplateId(consensus *Consensus) types.Hash {
|
||||
return consensus.CalculateSideChainId(&b.Main, &b.Side)
|
||||
}
|
||||
|
||||
func (b *Share) UnmarshalBinary(data []byte) error {
|
||||
|
||||
if len(data) < 32 {
|
||||
return errors.New("invalid block data")
|
||||
if len(buf) < 32 {
|
||||
return nil, errors.New("invalid block data")
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(data)
|
||||
reader := bytes.NewReader(buf)
|
||||
|
||||
var (
|
||||
err error
|
||||
|
@ -64,112 +62,246 @@ func (b *Share) UnmarshalBinary(data []byte) error {
|
|||
)
|
||||
|
||||
if err = binary.Read(reader, binary.BigEndian, &version); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch version {
|
||||
case 1:
|
||||
|
||||
if _, err = io.ReadFull(reader, b.Extra.MainId[:]); err != nil {
|
||||
return err
|
||||
if _, err = io.ReadFull(reader, b.c.mainId[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(reader, b.Extra.PowHash[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Read(reader, binary.BigEndian, &b.Extra.MainDifficulty.Hi); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Read(reader, binary.BigEndian, &b.Extra.MainDifficulty.Lo); err != nil {
|
||||
return err
|
||||
if _, err = io.ReadFull(reader, b.c.powHash[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.Extra.MainDifficulty.ReverseBytes()
|
||||
if err = binary.Read(reader, binary.BigEndian, &b.c.mainDifficulty.Hi); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = binary.Read(reader, binary.BigEndian, &b.c.mainDifficulty.Lo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.c.mainDifficulty.ReverseBytes()
|
||||
|
||||
if err = binary.Read(reader, binary.BigEndian, &mainDataSize); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
mainData = make([]byte, mainDataSize)
|
||||
if _, err = io.ReadFull(reader, mainData); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = binary.Read(reader, binary.BigEndian, &sideDataSize); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
sideData = make([]byte, sideDataSize)
|
||||
if _, err = io.ReadFull(reader, sideData); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Ignore error when unable to read peer
|
||||
_ = func() error {
|
||||
var peerSize uint64
|
||||
/*
|
||||
//Ignore error when unable to read peer
|
||||
_ = func() error {
|
||||
var peerSize uint64
|
||||
|
||||
if err = binary.Read(reader, binary.BigEndian, &peerSize); err != nil {
|
||||
return err
|
||||
}
|
||||
b.Extra.Peer = make([]byte, peerSize)
|
||||
if _, err = io.ReadFull(reader, b.Extra.Peer); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Read(reader, binary.BigEndian, &peerSize); err != nil {
|
||||
return err
|
||||
}
|
||||
b.Extra.Peer = make([]byte, peerSize)
|
||||
if _, err = io.ReadFull(reader, b.Extra.Peer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
return nil
|
||||
}()
|
||||
*/
|
||||
|
||||
case 0:
|
||||
if err = binary.Read(reader, binary.BigEndian, &mainDataSize); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
mainData = make([]byte, mainDataSize)
|
||||
if _, err = io.ReadFull(reader, mainData); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if sideData, err = io.ReadAll(reader); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown block version %d", version)
|
||||
return nil, fmt.Errorf("unknown block version %d", version)
|
||||
}
|
||||
|
||||
if err = b.Main.UnmarshalBinary(mainData); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = b.Side.UnmarshalBinary(sideData); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = b.fillFromTxExtra(); err != nil {
|
||||
return err
|
||||
b.c.templateId = types.HashFromBytes(b.CoinbaseExtra(SideTemplateId))
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (b *Share) CoinbaseExtra(tag CoinbaseExtraTag) []byte {
|
||||
switch tag {
|
||||
case SideExtraNonce:
|
||||
if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
|
||||
if len(t.Data) < SideExtraNonceSize || len(t.Data) > SideExtraNonceMaxSize {
|
||||
return nil
|
||||
}
|
||||
return t.Data
|
||||
}
|
||||
case SideTemplateId:
|
||||
if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
|
||||
if len(t.Data) != types.HashSize {
|
||||
return nil
|
||||
}
|
||||
return t.Data
|
||||
}
|
||||
case SideCoinbasePublicKey:
|
||||
if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
|
||||
if len(t.Data) != types.HashSize {
|
||||
return nil
|
||||
}
|
||||
return t.Data
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Share) fillFromTxExtra() error {
|
||||
func (b *Share) MainId() types.Hash {
|
||||
if hash, ok := func() (types.Hash, bool) {
|
||||
b.c.lock.RLock()
|
||||
defer b.c.lock.RUnlock()
|
||||
|
||||
for _, tag := range b.Main.Coinbase.Extra {
|
||||
switch tag.Tag {
|
||||
case transaction.TxExtraTagNonce:
|
||||
b.CoinbaseExtra.ExtraNonce = tag.Data
|
||||
case transaction.TxExtraTagMergeMining:
|
||||
if len(tag.Data) != types.HashSize {
|
||||
return fmt.Errorf("hash size %d is not %d", len(tag.Data), types.HashSize)
|
||||
}
|
||||
b.CoinbaseExtra.SideId = types.HashFromBytes(tag.Data)
|
||||
if b.c.mainId != types.ZeroHash {
|
||||
return b.c.mainId, true
|
||||
}
|
||||
return types.ZeroHash, false
|
||||
}(); ok {
|
||||
return hash
|
||||
} else {
|
||||
b.c.lock.Lock()
|
||||
defer b.c.lock.Unlock()
|
||||
if b.c.mainId == types.ZeroHash { //check again for race
|
||||
b.c.mainId = b.Main.Id()
|
||||
}
|
||||
return b.c.mainId
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Share) MainDifficulty() types.Difficulty {
|
||||
if difficulty, ok := func() (types.Difficulty, bool) {
|
||||
b.c.lock.RLock()
|
||||
defer b.c.lock.RUnlock()
|
||||
|
||||
if b.c.mainDifficulty != types.ZeroDifficulty {
|
||||
return b.c.mainDifficulty, true
|
||||
}
|
||||
return types.ZeroDifficulty, false
|
||||
}(); ok {
|
||||
return difficulty
|
||||
} else {
|
||||
b.c.lock.Lock()
|
||||
defer b.c.lock.Unlock()
|
||||
if b.c.mainDifficulty == types.ZeroDifficulty { //check again for race
|
||||
b.c.mainDifficulty = b.Main.Difficulty()
|
||||
}
|
||||
return b.c.mainDifficulty
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Share) SideTemplateId(consensus *Consensus) types.Hash {
|
||||
if hash, ok := func() (types.Hash, bool) {
|
||||
b.c.lock.RLock()
|
||||
defer b.c.lock.RUnlock()
|
||||
|
||||
if b.c.templateId != types.ZeroHash {
|
||||
return b.c.templateId, true
|
||||
}
|
||||
return types.ZeroHash, false
|
||||
}(); ok {
|
||||
return hash
|
||||
} else {
|
||||
b.c.lock.Lock()
|
||||
defer b.c.lock.Unlock()
|
||||
if b.c.templateId == types.ZeroHash { //check again for race
|
||||
b.c.templateId = consensus.CalculateSideTemplateId(&b.Main, &b.Side)
|
||||
}
|
||||
return b.c.templateId
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Share) PowHash() types.Hash {
|
||||
if hash, ok := func() (types.Hash, bool) {
|
||||
b.c.lock.RLock()
|
||||
defer b.c.lock.RUnlock()
|
||||
|
||||
if b.c.powHash != types.ZeroHash {
|
||||
return b.c.powHash, true
|
||||
}
|
||||
return types.ZeroHash, false
|
||||
}(); ok {
|
||||
return hash
|
||||
} else {
|
||||
b.c.lock.Lock()
|
||||
defer b.c.lock.Unlock()
|
||||
if b.c.powHash == types.ZeroHash { //check again for race
|
||||
b.c.powHash = b.Main.PowHash()
|
||||
}
|
||||
return b.c.powHash
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Share) UnmarshalBinary(data []byte) error {
|
||||
reader := bytes.NewReader(data)
|
||||
return b.FromReader(reader)
|
||||
}
|
||||
|
||||
func (b *Share) MarshalBinary() ([]byte, error) {
|
||||
if mainData, err := b.Main.MarshalBinary(); err != nil {
|
||||
return nil, err
|
||||
} else if sideData, err := b.Side.MarshalBinary(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
data := make([]byte, 0, len(mainData)+len(sideData))
|
||||
data = append(data, mainData...)
|
||||
data = append(data, sideData...)
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Share) FromReader(reader readerAndByteReader) (err error) {
|
||||
if err = b.Main.FromReader(reader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = b.Side.FromReader(reader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Share) IsProofHigherThanDifficulty() bool {
|
||||
return b.GetProofDifficulty().Cmp(b.Extra.MainDifficulty.Uint128) >= 0
|
||||
if mainDifficulty := b.MainDifficulty(); mainDifficulty == types.ZeroDifficulty {
|
||||
//TODO: err
|
||||
return false
|
||||
} else {
|
||||
return b.GetProofDifficulty().Cmp(mainDifficulty) >= 0
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Share) GetProofDifficulty() types.Difficulty {
|
||||
base := uint256.NewInt(0).SetBytes32(bytes.Repeat([]byte{0xff}, 32))
|
||||
pow := uint256.NewInt(0).SetBytes32(b.Extra.PowHash[:])
|
||||
|
||||
powHash := b.PowHash()
|
||||
pow := uint256.NewInt(0).SetBytes32(powHash[:])
|
||||
pow = &uint256.Int{bits.ReverseBytes64(pow[3]), bits.ReverseBytes64(pow[2]), bits.ReverseBytes64(pow[1]), bits.ReverseBytes64(pow[0])}
|
||||
|
||||
if pow.Eq(uint256.NewInt(0)) {
|
||||
|
@ -177,7 +309,7 @@ func (b *Share) GetProofDifficulty() types.Difficulty {
|
|||
}
|
||||
|
||||
powResult := uint256.NewInt(0).Div(base, pow).Bytes32()
|
||||
return types.Difficulty{Uint128: uint128.FromBytes(powResult[16:]).ReverseBytes()}
|
||||
return types.DifficultyFromBytes(powResult[16:])
|
||||
}
|
||||
|
||||
func (b *Share) GetAddress() *address.Address {
|
||||
|
|
|
@ -29,16 +29,18 @@ func TestBlockDecode(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
block, err := NewShareFromBytes(contents)
|
||||
block, err := NewShareFromExportedBytes(contents)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if hex.EncodeToString(block.Extra.MainId[:]) != "05892769e709b6cfebd5d71e5cadf38ba0abde8048a0eea3792d981861ad9a69" {
|
||||
t.Fatalf("expected main id 05892769e709b6cfebd5d71e5cadf38ba0abde8048a0eea3792d981861ad9a69, got %s", hex.EncodeToString(block.Extra.MainId[:]))
|
||||
mainId := block.MainId()
|
||||
|
||||
if hex.EncodeToString(mainId[:]) != "05892769e709b6cfebd5d71e5cadf38ba0abde8048a0eea3792d981861ad9a69" {
|
||||
t.Fatalf("expected main id 05892769e709b6cfebd5d71e5cadf38ba0abde8048a0eea3792d981861ad9a69, got %s", mainId.String())
|
||||
}
|
||||
if hex.EncodeToString(block.CoinbaseExtra.SideId[:]) != "1783223a701d16192ce9ff83c603b48b3e1785e3779b42079ede6e52ea7f0d2d" {
|
||||
t.Fatalf("expected side id 1783223a701d16192ce9ff83c603b48b3e1785e3779b42079ede6e52ea7f0d2d, got %s", hex.EncodeToString(block.CoinbaseExtra.SideId[:]))
|
||||
if hex.EncodeToString(block.CoinbaseExtra(SideTemplateId)) != "1783223a701d16192ce9ff83c603b48b3e1785e3779b42079ede6e52ea7f0d2d" {
|
||||
t.Fatalf("expected side id 1783223a701d16192ce9ff83c603b48b3e1785e3779b42079ede6e52ea7f0d2d, got %s", hex.EncodeToString(block.CoinbaseExtra(SideTemplateId)))
|
||||
}
|
||||
|
||||
b1, _ := block.Main.MarshalBinary()
|
||||
|
@ -56,10 +58,10 @@ func TestBlockDecode(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
log.Print(block.TemplateId(ConsensusDefault).String())
|
||||
t.Log(block.SideTemplateId(ConsensusDefault).String())
|
||||
|
||||
log.Print(block.Side.CoinbasePrivateKey.String())
|
||||
log.Print(types.HashFromBytes(block.GetAddress().GetDeterministicTransactionPrivateKey(block.Main.PreviousId).Bytes()).String())
|
||||
t.Log(block.Side.CoinbasePrivateKey.String())
|
||||
t.Log(types.HashFromBytes(block.GetAddress().GetDeterministicTransactionPrivateKey(block.Main.PreviousId).Bytes()).String())
|
||||
|
||||
txId := block.Main.Coinbase.Id()
|
||||
|
||||
|
@ -69,7 +71,7 @@ func TestBlockDecode(t *testing.T) {
|
|||
|
||||
proofResult, _ := types.DifficultyFromString("00000000000000000000006ef6334490")
|
||||
|
||||
if block.GetProofDifficulty().Cmp(proofResult.Uint128) != 0 {
|
||||
if block.GetProofDifficulty().Cmp(proofResult) != 0 {
|
||||
t.Fatalf("expected PoW difficulty %s, got %s", proofResult.String(), block.GetProofDifficulty().String())
|
||||
}
|
||||
|
||||
|
@ -81,12 +83,12 @@ func TestBlockDecode(t *testing.T) {
|
|||
t.Fatal("expected proof higher than difficulty")
|
||||
}
|
||||
|
||||
block.Extra.PowHash[31] = 1
|
||||
block.c.powHash[31] = 1
|
||||
|
||||
if block.IsProofHigherThanDifficulty() {
|
||||
t.Fatal("expected proof lower than difficulty")
|
||||
}
|
||||
|
||||
log.Print(block.GetProofDifficulty().String())
|
||||
log.Print(block.Extra.MainDifficulty.String())
|
||||
log.Print(block.MainDifficulty().String())
|
||||
}
|
||||
|
|
231
types/difficulty.go
Normal file
231
types/difficulty.go
Normal file
|
@ -0,0 +1,231 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"lukechampine.com/uint128"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
const DifficultySize = 16
|
||||
|
||||
var ZeroDifficulty = Difficulty(uint128.Zero)
|
||||
|
||||
type Difficulty uint128.Uint128
|
||||
|
||||
func (d Difficulty) IsZero() bool {
|
||||
return uint128.Uint128(d).IsZero()
|
||||
}
|
||||
|
||||
func (d Difficulty) Equals(v Difficulty) bool {
|
||||
return uint128.Uint128(d).Equals(uint128.Uint128(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) Equals64(v uint64) bool {
|
||||
return uint128.Uint128(d).Equals64(v)
|
||||
}
|
||||
|
||||
func (d Difficulty) Cmp(v Difficulty) int {
|
||||
return uint128.Uint128(d).Cmp(uint128.Uint128(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) Cmp64(v uint64) int {
|
||||
return uint128.Uint128(d).Cmp64(v)
|
||||
}
|
||||
|
||||
func (d Difficulty) And(v Difficulty) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).And(uint128.Uint128(v)))
|
||||
}
|
||||
|
||||
func (d Difficulty) And64(v uint64) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).And64(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) Or(v Difficulty) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Or(uint128.Uint128(v)))
|
||||
}
|
||||
|
||||
func (d Difficulty) Or64(v uint64) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Or64(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) Xor(v Difficulty) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Xor(uint128.Uint128(v)))
|
||||
}
|
||||
|
||||
func (d Difficulty) Xor64(v uint64) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Xor64(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) Add(v Difficulty) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Add(uint128.Uint128(v)))
|
||||
}
|
||||
|
||||
func (d Difficulty) AddWrap(v Difficulty) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).AddWrap(uint128.Uint128(v)))
|
||||
}
|
||||
|
||||
func (d Difficulty) Add64(v uint64) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Add64(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) AddWrap64(v uint64) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).AddWrap64(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) Sub(v Difficulty) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Sub(uint128.Uint128(v)))
|
||||
}
|
||||
|
||||
func (d Difficulty) SubWrap(v Difficulty) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).SubWrap(uint128.Uint128(v)))
|
||||
}
|
||||
|
||||
func (d Difficulty) Sub64(v uint64) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Sub64(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) SubWrap64(v uint64) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).SubWrap64(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) Mul(v Difficulty) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Mul(uint128.Uint128(v)))
|
||||
}
|
||||
|
||||
func (d Difficulty) MulWrap(v Difficulty) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).MulWrap(uint128.Uint128(v)))
|
||||
}
|
||||
|
||||
func (d Difficulty) Mul64(v uint64) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Mul64(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) MulWrap64(v uint64) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).MulWrap64(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) Div(v Difficulty) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Div(uint128.Uint128(v)))
|
||||
}
|
||||
|
||||
func (d Difficulty) Div64(v uint64) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Div64(v))
|
||||
}
|
||||
|
||||
func (d Difficulty) QuoRem(v Difficulty) (q, r Difficulty) {
|
||||
qq, rr := uint128.Uint128(d).QuoRem(uint128.Uint128(v))
|
||||
return Difficulty(qq), Difficulty(rr)
|
||||
}
|
||||
|
||||
func (d Difficulty) QuoRem64(v uint64) (q Difficulty, r uint64) {
|
||||
qq, rr := uint128.Uint128(d).QuoRem64(v)
|
||||
return Difficulty(qq), rr
|
||||
}
|
||||
|
||||
func (d Difficulty) Mod(v Difficulty) (r Difficulty) {
|
||||
return Difficulty(uint128.Uint128(d).Mod(uint128.Uint128(v)))
|
||||
}
|
||||
|
||||
func (d Difficulty) Mod64(v uint64) (r uint64) {
|
||||
return uint128.Uint128(d).Mod64(v)
|
||||
}
|
||||
|
||||
func (d Difficulty) Lsh(n uint) (s Difficulty) {
|
||||
return Difficulty(uint128.Uint128(d).Lsh(n))
|
||||
}
|
||||
|
||||
func (d Difficulty) Rsh(n uint) (s Difficulty) {
|
||||
return Difficulty(uint128.Uint128(d).Rsh(n))
|
||||
}
|
||||
|
||||
func (d Difficulty) LeadingZeros() int {
|
||||
return uint128.Uint128(d).LeadingZeros()
|
||||
}
|
||||
|
||||
func (d Difficulty) TrailingZeros() int {
|
||||
return uint128.Uint128(d).TrailingZeros()
|
||||
}
|
||||
|
||||
func (d Difficulty) OnesCount() int {
|
||||
return uint128.Uint128(d).OnesCount()
|
||||
}
|
||||
|
||||
func (d Difficulty) RotateLeft(k int) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).RotateLeft(k))
|
||||
}
|
||||
|
||||
func (d Difficulty) RotateRight(k int) Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).RotateRight(k))
|
||||
}
|
||||
|
||||
func (d Difficulty) Reverse() Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).Reverse())
|
||||
}
|
||||
|
||||
func (d Difficulty) ReverseBytes() Difficulty {
|
||||
return Difficulty(uint128.Uint128(d).ReverseBytes())
|
||||
}
|
||||
|
||||
func (d Difficulty) Len() int {
|
||||
return uint128.Uint128(d).Len()
|
||||
}
|
||||
|
||||
func (d Difficulty) PutBytes(b []byte) {
|
||||
uint128.Uint128(d).PutBytes(b)
|
||||
}
|
||||
|
||||
// Big returns u as a *big.Int.
|
||||
func (d Difficulty) Big() *big.Int {
|
||||
return uint128.Uint128(d).Big()
|
||||
}
|
||||
|
||||
func (d Difficulty) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(d.String())
|
||||
}
|
||||
|
||||
func DifficultyFromString(s string) (Difficulty, error) {
|
||||
if buf, err := hex.DecodeString(s); err != nil {
|
||||
return Difficulty{}, err
|
||||
} else {
|
||||
if len(buf) != DifficultySize {
|
||||
return Difficulty{}, errors.New("wrong hash size")
|
||||
}
|
||||
|
||||
return DifficultyFromBytes(buf), nil
|
||||
}
|
||||
}
|
||||
|
||||
func DifficultyFromBytes(buf []byte) Difficulty {
|
||||
return Difficulty(uint128.FromBytes(buf).ReverseBytes())
|
||||
}
|
||||
|
||||
func NewDifficulty(lo, hi uint64) Difficulty {
|
||||
return Difficulty{Lo: lo, Hi: hi}
|
||||
}
|
||||
|
||||
func DifficultyFrom64(v uint64) Difficulty {
|
||||
return NewDifficulty(v, 0)
|
||||
}
|
||||
|
||||
func (d *Difficulty) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if diff, err := DifficultyFromString(s); err != nil {
|
||||
return err
|
||||
} else {
|
||||
*d = diff
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d Difficulty) String() string {
|
||||
var buf [DifficultySize]byte
|
||||
d.ReverseBytes().PutBytes(buf[:])
|
||||
return hex.EncodeToString(buf[:])
|
||||
}
|
|
@ -5,14 +5,14 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"lukechampine.com/uint128"
|
||||
)
|
||||
|
||||
const HashSize = 32
|
||||
const DifficultySize = 16
|
||||
|
||||
type Hash [HashSize]byte
|
||||
|
||||
var ZeroHash Hash
|
||||
|
||||
func (h Hash) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(h.String())
|
||||
}
|
||||
|
@ -63,44 +63,3 @@ func (h *Hash) UnmarshalJSON(b []byte) error {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type Difficulty struct {
|
||||
uint128.Uint128
|
||||
}
|
||||
|
||||
func (d Difficulty) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(d.String())
|
||||
}
|
||||
|
||||
func DifficultyFromString(s string) (Difficulty, error) {
|
||||
if buf, err := hex.DecodeString(s); err != nil {
|
||||
return Difficulty{}, err
|
||||
} else {
|
||||
if len(buf) != DifficultySize {
|
||||
return Difficulty{}, errors.New("wrong hash size")
|
||||
}
|
||||
|
||||
return Difficulty{Uint128: uint128.FromBytes(buf).ReverseBytes()}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Difficulty) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if diff, err := DifficultyFromString(s); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.Uint128 = diff.Uint128
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d Difficulty) String() string {
|
||||
var buf [DifficultySize]byte
|
||||
d.ReverseBytes().PutBytes(buf[:])
|
||||
return hex.EncodeToString(buf[:])
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue