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 }