consensus/p2pool/block/block.go
2022-10-08 20:55:01 +02:00

348 lines
7.7 KiB
Go

package block
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"git.gammaspectra.live/P2Pool/moneroutil"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"github.com/holiman/uint256"
"io"
"lukechampine.com/uint128"
"math/bits"
)
type Block struct {
Main struct {
MajorVersion uint8
MinorVersion uint8
Timestamp uint64
Parent types.Hash
Nonce types.Nonce
Coinbase *CoinbaseTransaction
CoinbaseExtra struct {
PublicKey types.Hash
ExtraNonce []byte
SideId types.Hash
}
Transactions []types.Hash
}
Side struct {
PublicSpendKey types.Hash
PublicViewKey types.Hash
CoinbasePrivateKey types.Hash
Parent types.Hash
Uncles []types.Hash
Height uint64
Difficulty types.Difficulty
CumulativeDifficulty types.Difficulty
}
Extra struct {
MainId types.Hash
PowHash types.Hash
MainDifficulty types.Difficulty
Peer []byte
}
}
func NewBlockFromBytes(buf []byte) (*Block, error) {
b := &Block{}
return b, b.UnmarshalBinary(buf)
}
func (b *Block) UnmarshalBinary(data []byte) error {
if len(data) < 32 {
return errors.New("invalid block data")
}
reader := bytes.NewReader(data)
var (
err error
version uint64
mainDataSize uint64
mainData []byte
sideDataSize uint64
sideData []byte
)
if err = binary.Read(reader, binary.BigEndian, &version); err != nil {
return err
}
switch version {
case 1:
if _, err = io.ReadFull(reader, b.Extra.MainId[:]); err != nil {
return 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
}
b.Extra.MainDifficulty.ReverseBytes()
if err = binary.Read(reader, binary.BigEndian, &mainDataSize); err != nil {
return err
}
mainData = make([]byte, mainDataSize)
if _, err = io.ReadFull(reader, mainData); err != nil {
return err
}
if err = binary.Read(reader, binary.BigEndian, &sideDataSize); err != nil {
return err
}
sideData = make([]byte, sideDataSize)
if _, err = io.ReadFull(reader, sideData); err != nil {
return err
}
//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
}
return nil
}()
case 0:
if err = binary.Read(reader, binary.BigEndian, &mainDataSize); err != nil {
return err
}
mainData = make([]byte, mainDataSize)
if _, err = io.ReadFull(reader, mainData); err != nil {
return err
}
if sideData, err = io.ReadAll(reader); err != nil {
return err
}
default:
return fmt.Errorf("unknown block version %d", version)
}
if err = b.unmarshalMainData(mainData); err != nil {
return err
}
if err = b.unmarshalTxExtra(b.Main.Coinbase.Extra); err != nil {
return err
}
if err = b.unmarshalSideData(sideData); err != nil {
return err
}
return nil
}
func (b *Block) unmarshalMainData(data []byte) error {
reader := bytes.NewReader(data)
var (
err error
txCount uint64
transactionHash types.Hash
)
if err = binary.Read(reader, binary.BigEndian, &b.Main.MajorVersion); err != nil {
return err
}
if err = binary.Read(reader, binary.BigEndian, &b.Main.MinorVersion); err != nil {
return err
}
if b.Main.Timestamp, err = binary.ReadUvarint(reader); err != nil {
return err
}
if _, err = io.ReadFull(reader, b.Main.Parent[:]); err != nil {
return err
}
if _, err = io.ReadFull(reader, b.Main.Nonce[:]); err != nil {
return err
}
// Coinbase Tx Decoding
{
b.Main.Coinbase = &CoinbaseTransaction{}
if err = b.Main.Coinbase.FromReader(reader); err != nil {
return err
}
}
if txCount, err = binary.ReadUvarint(reader); err != nil {
return err
}
if txCount < 8192 {
b.Main.Transactions = make([]types.Hash, 0, txCount)
}
for i := 0; i < int(txCount); i++ {
if _, err = io.ReadFull(reader, transactionHash[:]); err != nil {
return err
}
//TODO: check if copy is needed
b.Main.Transactions = append(b.Main.Transactions, transactionHash)
}
return nil
}
func (b *Block) unmarshalTxExtra(data []byte) error {
reader := bytes.NewReader(data)
var (
err error
extraTag uint8
nonceSize uint64
mergeSize uint8
)
for {
if err = binary.Read(reader, binary.BigEndian, &extraTag); err != nil {
if err == io.EOF {
return nil
}
return err
}
switch extraTag {
default:
fallthrough
case TxExtraTagPadding, TxExtraTagAdditionalPubKeys:
return fmt.Errorf("unknown extra tag %d", extraTag)
case TxExtraTagPubKey:
if _, err = io.ReadFull(reader, b.Main.CoinbaseExtra.PublicKey[:]); err != nil {
return err
}
case TxExtraNonce:
if nonceSize, err = binary.ReadUvarint(reader); err != nil {
return err
}
b.Main.CoinbaseExtra.ExtraNonce = make([]byte, nonceSize)
if _, err = io.ReadFull(reader, b.Main.CoinbaseExtra.ExtraNonce); err != nil {
return err
}
case TxExtraMergeMiningTag:
if err = binary.Read(reader, binary.BigEndian, &mergeSize); err != nil {
return err
}
if mergeSize != types.HashSize {
return fmt.Errorf("hash size %d is not %d", mergeSize, types.HashSize)
}
if _, err = io.ReadFull(reader, b.Main.CoinbaseExtra.SideId[:]); err != nil {
return err
}
}
}
}
func (b *Block) IsProofHigherThanDifficulty() bool {
return b.GetProofDifficulty().Cmp(b.Extra.MainDifficulty.Uint128) >= 0
}
func (b *Block) GetProofDifficulty() types.Difficulty {
base := uint256.NewInt(0).SetBytes32(bytes.Repeat([]byte{0xff}, 32))
pow := uint256.NewInt(0).SetBytes32(b.Extra.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)) {
return types.Difficulty{}
}
powResult := uint256.NewInt(0).Div(base, pow).Bytes32()
return types.Difficulty{Uint128: uint128.FromBytes(powResult[16:]).ReverseBytes()}
}
func (b *Block) GetAddress() *address.Address {
return address.FromRawAddress(moneroutil.MainNetwork, b.Side.PublicSpendKey, b.Side.PublicViewKey)
}
func (b *Block) unmarshalSideData(data []byte) error {
reader := bytes.NewReader(data)
var (
err error
uncleCount uint64
uncleHash types.Hash
)
if _, err = io.ReadFull(reader, b.Side.PublicSpendKey[:]); err != nil {
return err
}
if _, err = io.ReadFull(reader, b.Side.PublicViewKey[:]); err != nil {
return err
}
if _, err = io.ReadFull(reader, b.Side.CoinbasePrivateKey[:]); err != nil {
return err
}
if _, err = io.ReadFull(reader, b.Side.Parent[:]); err != nil {
return err
}
if uncleCount, err = binary.ReadUvarint(reader); err != nil {
return err
}
for i := 0; i < int(uncleCount); i++ {
if _, err = io.ReadFull(reader, uncleHash[:]); err != nil {
return err
}
//TODO: check if copy is needed
b.Side.Uncles = append(b.Side.Uncles, uncleHash)
}
if b.Side.Height, err = binary.ReadUvarint(reader); err != nil {
return err
}
{
if b.Side.Difficulty.Lo, err = binary.ReadUvarint(reader); err != nil {
return err
}
if b.Side.Difficulty.Hi, err = binary.ReadUvarint(reader); err != nil {
return err
}
}
{
if b.Side.CumulativeDifficulty.Lo, err = binary.ReadUvarint(reader); err != nil {
return err
}
if b.Side.CumulativeDifficulty.Hi, err = binary.ReadUvarint(reader); err != nil {
return err
}
}
return nil
}