Split and cleanup merkle tree for transactions
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
DataHoarder 2023-05-23 15:33:54 +02:00
parent 951b1105ff
commit 3b268558d8
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
4 changed files with 88 additions and 72 deletions

View file

@ -10,7 +10,6 @@ import (
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"git.gammaspectra.live/P2Pool/sha3"
"io"
)
@ -244,7 +243,10 @@ func (b *Block) HashingBlobBufferLength() int {
func (b *Block) HashingBlob(preAllocatedBuf []byte) []byte {
buf := b.HeaderBlob(preAllocatedBuf)
txTreeHash := b.TxTreeHash()
merkleTree := make(crypto.BinaryTreeHash, len(b.Transactions)+1)
merkleTree[0] = b.Coinbase.Id()
copy(merkleTree[1:], b.Transactions)
txTreeHash := merkleTree.RootHash()
buf = append(buf, txTreeHash[:]...)
buf = binary.AppendUvarint(buf, uint64(len(b.Transactions)+1))
@ -252,69 +254,6 @@ func (b *Block) HashingBlob(preAllocatedBuf []byte) []byte {
return buf
}
func (b *Block) TxTreeHash() (rootHash types.Hash) {
//TODO: cache
//transaction hashes
h := make([]byte, 0, types.HashSize*len(b.Transactions)+types.HashSize)
coinbaseTxId := b.Coinbase.Id()
h = append(h, coinbaseTxId[:]...)
for _, txId := range b.Transactions {
h = append(h, txId[:]...)
}
count := len(b.Transactions) + 1
if count == 1 {
rootHash = types.HashFromBytes(h)
} else if count == 2 {
rootHash = crypto.PooledKeccak256(h)
} else {
hashInstance := crypto.GetKeccak256Hasher()
defer crypto.PutKeccak256Hasher(hashInstance)
var cnt int
{
//TODO: expand this loop properly
//find closest low power of two
for cnt = 1; cnt <= count; cnt <<= 1 {
}
cnt >>= 1
}
ints := make([]byte, cnt*types.HashSize)
copy(ints, h[:(cnt*2-count)*types.HashSize])
{
i := cnt*2 - count
j := cnt*2 - count
for j < cnt {
keccakl(hashInstance, ints[j*types.HashSize:], h[i*types.HashSize:], types.HashSize*2)
i += 2
j++
}
}
for cnt > 2 {
cnt >>= 1
{
i := 0
j := 0
for j < cnt {
keccakl(hashInstance, ints[j*types.HashSize:], ints[i*types.HashSize:], types.HashSize*2)
i += 2
j++
}
}
}
keccakl(hashInstance, rootHash[:], ints, types.HashSize*2)
}
return
}
func (b *Block) Difficulty(f GetDifficultyByHeightFunc) types.Difficulty {
//cached by sidechain.Share
return f(b.Coinbase.GenHeight)
@ -335,9 +274,3 @@ func (b *Block) Id() types.Hash {
buf := b.HashingBlob(make([]byte, 0, b.HashingBlobBufferLength()))
return crypto.PooledKeccak256(varIntBuf[:binary.PutUvarint(varIntBuf[:], uint64(len(buf)))], buf)
}
func keccakl(hasher *sha3.HasherState, dst []byte, data []byte, len int) {
hasher.Reset()
_, _ = hasher.Write(data[:len])
crypto.HashFastSum(hasher, dst)
}

57
monero/crypto/merkle.go Normal file
View file

@ -0,0 +1,57 @@
package crypto
import (
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
"git.gammaspectra.live/P2Pool/sha3"
)
type BinaryTreeHash []types.Hash
func (t BinaryTreeHash) leafHash(hasher *sha3.HasherState) (rootHash types.Hash) {
switch len(t) {
case 0:
panic("unsupported length")
case 1:
return t[0]
case 2:
hasher.Reset()
_, _ = hasher.Write(t[0][:])
_, _ = hasher.Write(t[1][:])
HashFastSum(hasher, rootHash[:])
return rootHash
default:
panic("unsupported length")
}
}
func (t BinaryTreeHash) RootHash() (rootHash types.Hash) {
hasher := GetKeccak256Hasher()
defer PutKeccak256Hasher(hasher)
count := len(t)
if count <= 2 {
return t.leafHash(hasher)
}
cnt := utils.PreviousPowerOfTwo(uint64(len(t)))
temporaryTree := make(BinaryTreeHash, cnt)
copy(temporaryTree, t[:cnt*2-count])
offset := cnt*2 - count
for i := 0; (i + offset) < cnt; i++ {
temporaryTree[offset+i] = t[offset+i*2 : offset+i*2+2].leafHash(hasher)
}
for cnt > 2 {
cnt >>= 1
for i := 0; i < cnt; i++ {
temporaryTree[i] = temporaryTree[i*2 : i*2+2].leafHash(hasher)
}
}
rootHash = temporaryTree[:2].leafHash(hasher)
return
}

View file

@ -4,6 +4,7 @@ import (
"encoding/hex"
"github.com/jxskiss/base62"
"golang.org/x/exp/constraints"
"math/bits"
"strconv"
"strings"
)
@ -83,3 +84,10 @@ func Max[T constraints.Ordered](v0 T, values ...T) (result T) {
}
return
}
func PreviousPowerOfTwo(x uint64) int {
if x == 0 {
return 0
}
return 1 << (64 - bits.LeadingZeros64(x) - 1)
}

View file

@ -1,6 +1,8 @@
package utils
import "testing"
import (
"testing"
)
func TestNumber(t *testing.T) {
s := "S"
@ -10,3 +12,19 @@ func TestNumber(t *testing.T) {
t.Fail()
}
}
func TestPreviousPowerOfTwo(t *testing.T) {
loopPath := func(x uint64) int {
//find closest low power of two
var cnt uint64
for cnt = 1; cnt <= x; cnt <<= 1 {
}
cnt >>= 1
return int(cnt)
}
for i := uint64(1); i < 65536; i++ {
if PreviousPowerOfTwo(i) != loopPath(i) {
t.Fatalf("expected %d, got %d for iteration %d", loopPath(i), PreviousPowerOfTwo(i), i)
}
}
}