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

     1  package transaction
     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/types"
    11  	"git.gammaspectra.live/P2Pool/consensus/utils"
    12  )
    13  
    14  type CoinbaseTransaction struct {
    15  	Version uint8 `json:"version"`
    16  	// UnlockTime would be here
    17  	InputCount uint8 `json:"input_count"`
    18  	InputType  uint8 `json:"input_type"`
    19  	// UnlockTime re-arranged here to improve memory layout space
    20  	UnlockTime uint64  `json:"unlock_time"`
    21  	GenHeight  uint64  `json:"gen_height"`
    22  	Outputs    Outputs `json:"outputs"`
    23  
    24  	// OutputsBlobSize length of serialized Outputs. Used by p2pool serialized pruned blocks, filled regardless
    25  	OutputsBlobSize uint64 `json:"outputs_blob_size"`
    26  	// TotalReward amount of reward existing Outputs. Used by p2pool serialized pruned blocks, filled regardless
    27  	TotalReward uint64 `json:"total_reward"`
    28  
    29  	Extra ExtraTags `json:"extra"`
    30  
    31  	ExtraBaseRCT uint8 `json:"extra_base_rct"`
    32  }
    33  
    34  func (c *CoinbaseTransaction) UnmarshalBinary(data []byte) error {
    35  	reader := bytes.NewReader(data)
    36  	return c.FromReader(reader)
    37  }
    38  
    39  func (c *CoinbaseTransaction) FromReader(reader utils.ReaderAndByteReader) (err error) {
    40  	var (
    41  		txExtraSize uint64
    42  	)
    43  
    44  	c.TotalReward = 0
    45  	c.OutputsBlobSize = 0
    46  
    47  	if c.Version, err = reader.ReadByte(); err != nil {
    48  		return err
    49  	}
    50  
    51  	if c.Version != 2 {
    52  		return errors.New("version not supported")
    53  	}
    54  
    55  	if c.UnlockTime, err = binary.ReadUvarint(reader); err != nil {
    56  		return err
    57  	}
    58  
    59  	if c.InputCount, err = reader.ReadByte(); err != nil {
    60  		return err
    61  	}
    62  
    63  	if c.InputCount != 1 {
    64  		return errors.New("invalid input count")
    65  	}
    66  
    67  	if c.InputType, err = reader.ReadByte(); err != nil {
    68  		return err
    69  	}
    70  
    71  	if c.InputType != TxInGen {
    72  		return errors.New("invalid coinbase input type")
    73  	}
    74  
    75  	if c.GenHeight, err = binary.ReadUvarint(reader); err != nil {
    76  		return err
    77  	}
    78  
    79  	if c.UnlockTime != (c.GenHeight + monero.MinerRewardUnlockTime) {
    80  		return errors.New("invalid unlock time")
    81  	}
    82  
    83  	if err = c.Outputs.FromReader(reader); err != nil {
    84  		return err
    85  	} else if len(c.Outputs) != 0 {
    86  		for _, o := range c.Outputs {
    87  			switch o.Type {
    88  			case TxOutToTaggedKey:
    89  				c.OutputsBlobSize += 1 + types.HashSize + 1
    90  			case TxOutToKey:
    91  				c.OutputsBlobSize += 1 + types.HashSize
    92  			default:
    93  				return fmt.Errorf("unknown %d TXOUT key", o.Type)
    94  			}
    95  			c.TotalReward += o.Reward
    96  		}
    97  	} else {
    98  		// Outputs are not in the buffer and must be calculated from sidechain data
    99  		// We only have total reward and outputs blob size here
   100  		//special case, pruned block. outputs have to be generated from chain
   101  
   102  		if c.TotalReward, err = binary.ReadUvarint(reader); err != nil {
   103  			return err
   104  		}
   105  
   106  		if c.OutputsBlobSize, err = binary.ReadUvarint(reader); err != nil {
   107  			return err
   108  		}
   109  	}
   110  
   111  	if txExtraSize, err = binary.ReadUvarint(reader); err != nil {
   112  		return err
   113  	}
   114  	if txExtraSize > 65536 {
   115  		return errors.New("tx extra too large")
   116  	}
   117  
   118  	limitReader := utils.LimitByteReader(reader, int64(txExtraSize))
   119  	if err = c.Extra.FromReader(limitReader); err != nil {
   120  		return err
   121  	}
   122  	if limitReader.Left() > 0 {
   123  		return errors.New("bytes leftover in extra data")
   124  	}
   125  	if err = binary.Read(reader, binary.LittleEndian, &c.ExtraBaseRCT); err != nil {
   126  		return err
   127  	}
   128  
   129  	if c.ExtraBaseRCT != 0 {
   130  		return errors.New("invalid extra base RCT")
   131  	}
   132  
   133  	return nil
   134  }
   135  
   136  func (c *CoinbaseTransaction) MarshalBinary() ([]byte, error) {
   137  	return c.MarshalBinaryFlags(false)
   138  }
   139  
   140  func (c *CoinbaseTransaction) BufferLength() int {
   141  	return 1 +
   142  		utils.UVarInt64Size(c.UnlockTime) +
   143  		1 + 1 +
   144  		utils.UVarInt64Size(c.GenHeight) +
   145  		c.Outputs.BufferLength() +
   146  		utils.UVarInt64Size(c.Extra.BufferLength()) + c.Extra.BufferLength() + 1
   147  }
   148  
   149  func (c *CoinbaseTransaction) MarshalBinaryFlags(pruned bool) ([]byte, error) {
   150  	return c.AppendBinaryFlags(make([]byte, 0, c.BufferLength()), pruned)
   151  }
   152  
   153  func (c *CoinbaseTransaction) AppendBinaryFlags(preAllocatedBuf []byte, pruned bool) ([]byte, error) {
   154  	buf := preAllocatedBuf
   155  
   156  	buf = append(buf, c.Version)
   157  	buf = binary.AppendUvarint(buf, c.UnlockTime)
   158  	buf = append(buf, c.InputCount)
   159  	buf = append(buf, c.InputType)
   160  	buf = binary.AppendUvarint(buf, c.GenHeight)
   161  
   162  	if pruned {
   163  		//pruned output
   164  		buf = binary.AppendUvarint(buf, 0)
   165  		buf = binary.AppendUvarint(buf, c.TotalReward)
   166  		outputs := make([]byte, 0, c.Outputs.BufferLength())
   167  		outputs, _ = c.Outputs.AppendBinary(outputs)
   168  		buf = binary.AppendUvarint(buf, uint64(len(outputs)))
   169  	} else {
   170  		buf, _ = c.Outputs.AppendBinary(buf)
   171  	}
   172  
   173  	buf = binary.AppendUvarint(buf, uint64(c.Extra.BufferLength()))
   174  	buf, _ = c.Extra.AppendBinary(buf)
   175  	buf = append(buf, c.ExtraBaseRCT)
   176  
   177  	return buf, nil
   178  }
   179  
   180  func (c *CoinbaseTransaction) OutputsBlob() ([]byte, error) {
   181  	return c.Outputs.MarshalBinary()
   182  }
   183  
   184  func (c *CoinbaseTransaction) SideChainHashingBlob(preAllocatedBuf []byte, zeroTemplateId bool) ([]byte, error) {
   185  	buf := preAllocatedBuf
   186  
   187  	buf = append(buf, c.Version)
   188  	buf = binary.AppendUvarint(buf, c.UnlockTime)
   189  	buf = append(buf, c.InputCount)
   190  	buf = append(buf, c.InputType)
   191  	buf = binary.AppendUvarint(buf, c.GenHeight)
   192  
   193  	buf, _ = c.Outputs.AppendBinary(buf)
   194  
   195  	buf = binary.AppendUvarint(buf, uint64(c.Extra.BufferLength()))
   196  	buf, _ = c.Extra.SideChainHashingBlob(buf, zeroTemplateId)
   197  	buf = append(buf, c.ExtraBaseRCT)
   198  
   199  	return buf, nil
   200  }
   201  
   202  func (c *CoinbaseTransaction) CalculateId() (hash types.Hash) {
   203  
   204  	txBytes, _ := c.AppendBinaryFlags(make([]byte, 0, c.BufferLength()), false)
   205  
   206  	return crypto.PooledKeccak256(
   207  		// remove base RCT
   208  		hashKeccak(txBytes[:len(txBytes)-1]),
   209  		// Base RCT, single 0 byte in miner tx
   210  		hashKeccak([]byte{c.ExtraBaseRCT}),
   211  		// Prunable RCT, empty in miner tx
   212  		types.ZeroHash[:],
   213  	)
   214  }
   215  
   216  func hashKeccak(data ...[]byte) []byte {
   217  	d := crypto.PooledKeccak256(data...)
   218  	return d[:]
   219  }