consensus/monero/block/block.go

332 lines
8 KiB
Go

package block
import (
"bytes"
"encoding/binary"
"errors"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"hash"
"io"
)
type Block struct {
MajorVersion uint8
MinorVersion uint8
Timestamp uint64
PreviousId types.Hash
Nonce uint32
Coinbase *transaction.CoinbaseTransaction
Transactions []types.Hash
// TransactionParentIndices amount of reward existing Outputs. Used by p2pool serialized compact broadcasted blocks in protocol >= 1.1, filled only in compact blocks or by pre-processing.
TransactionParentIndices []uint64
}
type Header struct {
MajorVersion uint8
MinorVersion uint8
Timestamp uint64
PreviousId types.Hash
Height uint64
Nonce uint32
Reward uint64
Difficulty types.Difficulty
Id types.Hash
}
type readerAndByteReader interface {
io.Reader
io.ByteReader
}
func (b *Block) MarshalBinary() (buf []byte, err error) {
return b.MarshalBinaryFlags(false, false)
}
func (b *Block) MarshalBinaryFlags(pruned, compact bool) (buf []byte, err error) {
var txBuf []byte
if txBuf, err = b.Coinbase.MarshalBinaryFlags(pruned); err != nil {
return nil, err
}
buf = make([]byte, 0, 1+1+binary.MaxVarintLen64+types.HashSize+4+len(txBuf)+binary.MaxVarintLen64+types.HashSize*len(b.Transactions))
buf = append(buf, b.MajorVersion)
buf = append(buf, b.MinorVersion)
buf = binary.AppendUvarint(buf, b.Timestamp)
buf = append(buf, b.PreviousId[:]...)
buf = binary.LittleEndian.AppendUint32(buf, b.Nonce)
buf = append(buf, txBuf[:]...)
buf = binary.AppendUvarint(buf, uint64(len(b.Transactions)))
if compact {
for i, txId := range b.Transactions {
if i < len(b.TransactionParentIndices) && b.TransactionParentIndices[i] != 0 {
buf = binary.AppendUvarint(buf, b.TransactionParentIndices[i])
} else {
buf = binary.AppendUvarint(buf, 0)
buf = append(buf, txId[:]...)
}
}
} else {
for _, txId := range b.Transactions {
buf = append(buf, txId[:]...)
}
}
return buf, nil
}
func (b *Block) FromReader(reader readerAndByteReader) (err error) {
return b.FromReaderFlags(reader, false)
}
func (b *Block) FromCompactReader(reader readerAndByteReader) (err error) {
return b.FromReaderFlags(reader, true)
}
func (b *Block) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data)
return b.FromReader(reader)
}
func (b *Block) FromReaderFlags(reader readerAndByteReader, compact bool) (err error) {
var (
txCount uint64
transactionHash types.Hash
)
if b.MajorVersion, err = reader.ReadByte(); err != nil {
return err
}
if b.MinorVersion, err = reader.ReadByte(); err != nil {
return err
}
if b.Timestamp, err = binary.ReadUvarint(reader); err != nil {
return err
}
if _, err = io.ReadFull(reader, b.PreviousId[:]); err != nil {
return err
}
if err = binary.Read(reader, binary.LittleEndian, &b.Nonce); err != nil {
return err
}
// Coinbase Tx Decoding
{
b.Coinbase = &transaction.CoinbaseTransaction{}
if err = b.Coinbase.FromReader(reader); err != nil {
return err
}
}
if txCount, err = binary.ReadUvarint(reader); err != nil {
return err
}
if compact {
if txCount < 8192 {
b.Transactions = make([]types.Hash, 0, txCount)
b.TransactionParentIndices = make([]uint64, 0, txCount)
}
var parentIndex uint64
for i := 0; i < int(txCount); i++ {
if parentIndex, err = binary.ReadUvarint(reader); err != nil {
return err
}
if parentIndex == 0 {
//not in lookup
if _, err = io.ReadFull(reader, transactionHash[:]); err != nil {
return err
}
b.Transactions = append(b.Transactions, transactionHash)
} else {
b.Transactions = append(b.Transactions, types.ZeroHash)
}
b.TransactionParentIndices = append(b.TransactionParentIndices, parentIndex)
}
} else {
if txCount < 8192 {
b.Transactions = make([]types.Hash, 0, txCount)
}
for i := 0; i < int(txCount); i++ {
if _, err = io.ReadFull(reader, transactionHash[:]); err != nil {
return err
}
b.Transactions = append(b.Transactions, transactionHash)
}
}
return nil
}
func (b *Block) Header() *Header {
return &Header{
MajorVersion: b.MajorVersion,
MinorVersion: b.MinorVersion,
Timestamp: b.Timestamp,
PreviousId: b.PreviousId,
Height: b.Coinbase.GenHeight,
Nonce: b.Nonce,
Reward: b.Coinbase.TotalReward,
Id: b.Id(),
Difficulty: types.ZeroDifficulty,
}
}
func (b *Block) HeaderBlob() []byte {
//TODO: cache
buf := make([]byte, 0, 1+1+binary.MaxVarintLen64+types.HashSize+4+types.HashSize+binary.MaxVarintLen64) //predict its use on HashingBlob
buf = append(buf, b.MajorVersion)
buf = append(buf, b.MinorVersion)
buf = binary.AppendUvarint(buf, b.Timestamp)
buf = append(buf, b.PreviousId[:]...)
buf = binary.LittleEndian.AppendUint32(buf, b.Nonce)
return buf
}
// SideChainHashingBlob Same as MarshalBinary but with nonce set to 0
func (b *Block) SideChainHashingBlob() (buf []byte, err error) {
var txBuf []byte
if txBuf, err = b.Coinbase.SideChainHashingBlob(); err != nil {
return nil, err
}
buf = make([]byte, 0, 1+1+binary.MaxVarintLen64+types.HashSize+4+len(txBuf)+binary.MaxVarintLen64+types.HashSize*len(b.Transactions))
buf = append(buf, b.MajorVersion)
buf = append(buf, b.MinorVersion)
buf = binary.AppendUvarint(buf, b.Timestamp)
buf = append(buf, b.PreviousId[:]...)
buf = binary.LittleEndian.AppendUint32(buf, 0) //replaced
buf = append(buf, txBuf[:]...)
buf = binary.AppendUvarint(buf, uint64(len(b.Transactions)))
for _, txId := range b.Transactions {
buf = append(buf, txId[:]...)
}
return buf, nil
}
func (b *Block) HashingBlob() []byte {
//TODO: cache
buf := b.HeaderBlob()
txTreeHash := b.TxTreeHash()
buf = append(buf, txTreeHash[:]...)
buf = binary.AppendUvarint(buf, uint64(len(b.Transactions)+1))
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)
}
func (b *Block) PowHash(f GetSeedByHeightFunc) types.Hash {
//cached by sidechain.Share
h, _ := b.PowHashWithError(f)
return h
}
func (b *Block) PowHashWithError(f GetSeedByHeightFunc) (types.Hash, error) {
//not cached
if seed := f(b.Coinbase.GenHeight); seed == types.ZeroHash {
return types.ZeroHash, errors.New("could not get seed")
} else {
return hasher.Hash(seed[:], b.HashingBlob())
}
}
func (b *Block) Id() types.Hash {
//cached by sidechain.Share
buf := b.HashingBlob()
return crypto.PooledKeccak256(binary.AppendUvarint(nil, uint64(len(buf))), buf)
}
var hasher = randomx.NewRandomX()
func keccakl(hasher hash.Hash, dst []byte, data []byte, len int) {
hasher.Reset()
hasher.Write(data[:len])
crypto.HashFastSum(hasher, dst)
}