git.gammaspectra.live/P2Pool/consensus@v0.0.0-20240403173234-a039820b20c9/monero/block/block.go (about)

     1  package block
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"git.gammaspectra.live/P2Pool/consensus/monero"
     9  	"git.gammaspectra.live/P2Pool/consensus/monero/crypto"
    10  	"git.gammaspectra.live/P2Pool/consensus/monero/randomx"
    11  	"git.gammaspectra.live/P2Pool/consensus/monero/transaction"
    12  	"git.gammaspectra.live/P2Pool/consensus/types"
    13  	"git.gammaspectra.live/P2Pool/consensus/utils"
    14  	"io"
    15  )
    16  
    17  type Block struct {
    18  	MajorVersion uint8 `json:"major_version"`
    19  	MinorVersion uint8 `json:"minor_version"`
    20  	// Nonce re-arranged here to improve memory layout space
    21  	Nonce uint32 `json:"nonce"`
    22  
    23  	Timestamp  uint64     `json:"timestamp"`
    24  	PreviousId types.Hash `json:"previous_id"`
    25  	//Nonce would be here
    26  
    27  	Coinbase transaction.CoinbaseTransaction `json:"coinbase"`
    28  
    29  	Transactions []types.Hash `json:"transactions,omitempty"`
    30  	// 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.
    31  	TransactionParentIndices []uint64 `json:"transaction_parent_indices,omitempty"`
    32  }
    33  
    34  type Header struct {
    35  	MajorVersion uint8 `json:"major_version"`
    36  	MinorVersion uint8 `json:"minor_version"`
    37  	// Nonce re-arranged here to improve memory layout space
    38  	Nonce uint32 `json:"nonce"`
    39  
    40  	Timestamp  uint64     `json:"timestamp"`
    41  	PreviousId types.Hash `json:"previous_id"`
    42  	Height     uint64     `json:"height"`
    43  	//Nonce would be here
    44  	Reward     uint64           `json:"reward"`
    45  	Difficulty types.Difficulty `json:"difficulty"`
    46  	Id         types.Hash       `json:"id"`
    47  }
    48  
    49  func (b *Block) MarshalBinary() (buf []byte, err error) {
    50  	return b.MarshalBinaryFlags(false, false)
    51  }
    52  
    53  func (b *Block) BufferLength() int {
    54  	return 1 + 1 +
    55  		utils.UVarInt64Size(b.Timestamp) +
    56  		types.HashSize +
    57  		4 +
    58  		b.Coinbase.BufferLength() +
    59  		utils.UVarInt64Size(len(b.Transactions)) + types.HashSize*len(b.Transactions)
    60  }
    61  
    62  func (b *Block) MarshalBinaryFlags(pruned, compact bool) (buf []byte, err error) {
    63  	return b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), pruned, compact)
    64  }
    65  
    66  func (b *Block) AppendBinaryFlags(preAllocatedBuf []byte, pruned, compact bool) (buf []byte, err error) {
    67  	buf = preAllocatedBuf
    68  	buf = append(buf, b.MajorVersion)
    69  	if b.MajorVersion > monero.HardForkSupportedVersion {
    70  		return nil, fmt.Errorf("unsupported version %d", b.MajorVersion)
    71  	}
    72  	buf = append(buf, b.MinorVersion)
    73  	if b.MinorVersion < b.MajorVersion {
    74  		return nil, fmt.Errorf("minor version %d smaller than major %d", b.MinorVersion, b.MajorVersion)
    75  	}
    76  	buf = binary.AppendUvarint(buf, b.Timestamp)
    77  	buf = append(buf, b.PreviousId[:]...)
    78  	buf = binary.LittleEndian.AppendUint32(buf, b.Nonce)
    79  
    80  	if buf, err = b.Coinbase.AppendBinaryFlags(buf, pruned); err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	buf = binary.AppendUvarint(buf, uint64(len(b.Transactions)))
    85  	if compact {
    86  		for i, txId := range b.Transactions {
    87  			if i < len(b.TransactionParentIndices) && b.TransactionParentIndices[i] != 0 {
    88  				buf = binary.AppendUvarint(buf, b.TransactionParentIndices[i])
    89  			} else {
    90  				buf = binary.AppendUvarint(buf, 0)
    91  				buf = append(buf, txId[:]...)
    92  			}
    93  		}
    94  	} else {
    95  		for _, txId := range b.Transactions {
    96  			buf = append(buf, txId[:]...)
    97  		}
    98  	}
    99  
   100  	return buf, nil
   101  }
   102  
   103  func (b *Block) FromReader(reader utils.ReaderAndByteReader) (err error) {
   104  	return b.FromReaderFlags(reader, false)
   105  }
   106  
   107  func (b *Block) FromCompactReader(reader utils.ReaderAndByteReader) (err error) {
   108  	return b.FromReaderFlags(reader, true)
   109  }
   110  
   111  func (b *Block) UnmarshalBinary(data []byte) error {
   112  	reader := bytes.NewReader(data)
   113  	return b.FromReader(reader)
   114  }
   115  
   116  func (b *Block) FromReaderFlags(reader utils.ReaderAndByteReader, compact bool) (err error) {
   117  	var (
   118  		txCount         uint64
   119  		transactionHash types.Hash
   120  	)
   121  
   122  	if b.MajorVersion, err = reader.ReadByte(); err != nil {
   123  		return err
   124  	}
   125  	if b.MinorVersion, err = reader.ReadByte(); err != nil {
   126  		return err
   127  	}
   128  
   129  	if b.Timestamp, err = binary.ReadUvarint(reader); err != nil {
   130  		return err
   131  	}
   132  
   133  	if _, err = io.ReadFull(reader, b.PreviousId[:]); err != nil {
   134  		return err
   135  	}
   136  
   137  	if err = binary.Read(reader, binary.LittleEndian, &b.Nonce); err != nil {
   138  		return err
   139  	}
   140  
   141  	// Coinbase Tx Decoding
   142  	{
   143  		if err = b.Coinbase.FromReader(reader); err != nil {
   144  			return err
   145  		}
   146  	}
   147  
   148  	//TODO: verify hardfork major versions
   149  
   150  	if txCount, err = binary.ReadUvarint(reader); err != nil {
   151  		return err
   152  	}
   153  
   154  	if compact {
   155  		if txCount < 8192 {
   156  			b.Transactions = make([]types.Hash, 0, txCount)
   157  			b.TransactionParentIndices = make([]uint64, 0, txCount)
   158  		}
   159  
   160  		var parentIndex uint64
   161  		for i := 0; i < int(txCount); i++ {
   162  			if parentIndex, err = binary.ReadUvarint(reader); err != nil {
   163  				return err
   164  			}
   165  
   166  			if parentIndex == 0 {
   167  				//not in lookup
   168  				if _, err = io.ReadFull(reader, transactionHash[:]); err != nil {
   169  					return err
   170  				}
   171  
   172  				b.Transactions = append(b.Transactions, transactionHash)
   173  			} else {
   174  				b.Transactions = append(b.Transactions, types.ZeroHash)
   175  			}
   176  
   177  			b.TransactionParentIndices = append(b.TransactionParentIndices, parentIndex)
   178  		}
   179  	} else {
   180  		if txCount < 8192 {
   181  			b.Transactions = make([]types.Hash, 0, txCount)
   182  		}
   183  
   184  		for i := 0; i < int(txCount); i++ {
   185  			if _, err = io.ReadFull(reader, transactionHash[:]); err != nil {
   186  				return err
   187  			}
   188  			b.Transactions = append(b.Transactions, transactionHash)
   189  		}
   190  	}
   191  
   192  	return nil
   193  }
   194  
   195  func (b *Block) Header() *Header {
   196  	return &Header{
   197  		MajorVersion: b.MajorVersion,
   198  		MinorVersion: b.MinorVersion,
   199  		Timestamp:    b.Timestamp,
   200  		PreviousId:   b.PreviousId,
   201  		Height:       b.Coinbase.GenHeight,
   202  		Nonce:        b.Nonce,
   203  		Reward:       b.Coinbase.TotalReward,
   204  		Id:           b.Id(),
   205  		Difficulty:   types.ZeroDifficulty,
   206  	}
   207  }
   208  
   209  func (b *Block) HeaderBlobBufferLength() int {
   210  	return 1 + 1 +
   211  		utils.UVarInt64Size(b.Timestamp) +
   212  		types.HashSize +
   213  		4
   214  }
   215  
   216  func (b *Block) HeaderBlob(preAllocatedBuf []byte) []byte {
   217  	buf := preAllocatedBuf
   218  	buf = append(buf, b.MajorVersion)
   219  	buf = append(buf, b.MinorVersion)
   220  	buf = binary.AppendUvarint(buf, b.Timestamp)
   221  	buf = append(buf, b.PreviousId[:]...)
   222  	buf = binary.LittleEndian.AppendUint32(buf, b.Nonce)
   223  
   224  	return buf
   225  }
   226  
   227  // SideChainHashingBlob Same as MarshalBinary but with nonce or template id set to 0
   228  func (b *Block) SideChainHashingBlob(preAllocatedBuf []byte, zeroTemplateId bool) (buf []byte, err error) {
   229  	buf = preAllocatedBuf
   230  	buf = append(buf, b.MajorVersion)
   231  	buf = append(buf, b.MinorVersion)
   232  	buf = binary.AppendUvarint(buf, b.Timestamp)
   233  	buf = append(buf, b.PreviousId[:]...)
   234  	buf = binary.LittleEndian.AppendUint32(buf, 0) //replaced
   235  
   236  	if buf, err = b.Coinbase.SideChainHashingBlob(buf, zeroTemplateId); err != nil {
   237  		return nil, err
   238  	}
   239  
   240  	buf = binary.AppendUvarint(buf, uint64(len(b.Transactions)))
   241  	for _, txId := range b.Transactions {
   242  		buf = append(buf, txId[:]...)
   243  	}
   244  
   245  	return buf, nil
   246  }
   247  
   248  func (b *Block) HashingBlobBufferLength() int {
   249  	return b.HeaderBlobBufferLength() +
   250  		types.HashSize + utils.UVarInt64Size(len(b.Transactions)+1)
   251  }
   252  
   253  func (b *Block) HashingBlob(preAllocatedBuf []byte) []byte {
   254  	buf := b.HeaderBlob(preAllocatedBuf)
   255  
   256  	merkleTree := make(crypto.BinaryTreeHash, len(b.Transactions)+1)
   257  	//TODO: cache?
   258  	merkleTree[0] = b.Coinbase.CalculateId()
   259  	copy(merkleTree[1:], b.Transactions)
   260  	txTreeHash := merkleTree.RootHash()
   261  	buf = append(buf, txTreeHash[:]...)
   262  
   263  	buf = binary.AppendUvarint(buf, uint64(len(b.Transactions)+1))
   264  
   265  	return buf
   266  }
   267  
   268  func (b *Block) Difficulty(f GetDifficultyByHeightFunc) types.Difficulty {
   269  	//cached by sidechain.Share
   270  	return f(b.Coinbase.GenHeight)
   271  }
   272  
   273  func (b *Block) PowHashWithError(hasher randomx.Hasher, f GetSeedByHeightFunc) (types.Hash, error) {
   274  	//not cached
   275  	if seed := f(b.Coinbase.GenHeight); seed == types.ZeroHash {
   276  		return types.ZeroHash, errors.New("could not get seed")
   277  	} else {
   278  		return hasher.Hash(seed[:], b.HashingBlob(make([]byte, 0, b.HashingBlobBufferLength())))
   279  	}
   280  }
   281  
   282  func (b *Block) Id() types.Hash {
   283  	//cached by sidechain.Share
   284  	var varIntBuf [binary.MaxVarintLen64]byte
   285  	buf := b.HashingBlob(make([]byte, 0, b.HashingBlobBufferLength()))
   286  	return crypto.PooledKeccak256(varIntBuf[:binary.PutUvarint(varIntBuf[:], uint64(len(buf)))], buf)
   287  }