Refactor Difficulty, cache derivations and client actions, faster miner transaction matching

This commit is contained in:
DataHoarder 2022-10-28 11:47:14 +02:00
parent 135fbd8973
commit ab0e335b9a
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
22 changed files with 933 additions and 421 deletions

View file

@ -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,

View file

@ -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{

View file

@ -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))
}
}
}

View file

@ -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 {

View file

@ -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:])
}

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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
View 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
View 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
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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()

View file

@ -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())
}
}

View file

@ -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 {

View file

@ -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
View 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[:])
}

View file

@ -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[:])
}