Split and cleanup merkle tree for transactions
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
951b1105ff
commit
3b268558d8
|
@ -10,7 +10,6 @@ import (
|
||||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||||
"git.gammaspectra.live/P2Pool/sha3"
|
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -244,7 +243,10 @@ func (b *Block) HashingBlobBufferLength() int {
|
||||||
func (b *Block) HashingBlob(preAllocatedBuf []byte) []byte {
|
func (b *Block) HashingBlob(preAllocatedBuf []byte) []byte {
|
||||||
buf := b.HeaderBlob(preAllocatedBuf)
|
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 = append(buf, txTreeHash[:]...)
|
||||||
|
|
||||||
buf = binary.AppendUvarint(buf, uint64(len(b.Transactions)+1))
|
buf = binary.AppendUvarint(buf, uint64(len(b.Transactions)+1))
|
||||||
|
@ -252,69 +254,6 @@ func (b *Block) HashingBlob(preAllocatedBuf []byte) []byte {
|
||||||
return buf
|
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 {
|
func (b *Block) Difficulty(f GetDifficultyByHeightFunc) types.Difficulty {
|
||||||
//cached by sidechain.Share
|
//cached by sidechain.Share
|
||||||
return f(b.Coinbase.GenHeight)
|
return f(b.Coinbase.GenHeight)
|
||||||
|
@ -335,9 +274,3 @@ func (b *Block) Id() types.Hash {
|
||||||
buf := b.HashingBlob(make([]byte, 0, b.HashingBlobBufferLength()))
|
buf := b.HashingBlob(make([]byte, 0, b.HashingBlobBufferLength()))
|
||||||
return crypto.PooledKeccak256(varIntBuf[:binary.PutUvarint(varIntBuf[:], uint64(len(buf)))], buf)
|
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
57
monero/crypto/merkle.go
Normal 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
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/jxskiss/base62"
|
"github.com/jxskiss/base62"
|
||||||
"golang.org/x/exp/constraints"
|
"golang.org/x/exp/constraints"
|
||||||
|
"math/bits"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -83,3 +84,10 @@ func Max[T constraints.Ordered](v0 T, values ...T) (result T) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PreviousPowerOfTwo(x uint64) int {
|
||||||
|
if x == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1 << (64 - bits.LeadingZeros64(x) - 1)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestNumber(t *testing.T) {
|
func TestNumber(t *testing.T) {
|
||||||
s := "S"
|
s := "S"
|
||||||
|
@ -10,3 +12,19 @@ func TestNumber(t *testing.T) {
|
||||||
t.Fail()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue