package block import ( "bytes" "encoding/binary" "errors" "fmt" "git.gammaspectra.live/P2Pool/consensus/v4/monero" "git.gammaspectra.live/P2Pool/consensus/v4/monero/crypto" "git.gammaspectra.live/P2Pool/consensus/v4/monero/randomx" "git.gammaspectra.live/P2Pool/consensus/v4/monero/transaction" "git.gammaspectra.live/P2Pool/consensus/v4/types" "git.gammaspectra.live/P2Pool/consensus/v4/utils" "io" "math" ) const MaxTransactionCount = uint64(math.MaxUint64) / types.HashSize type Block struct { MajorVersion uint8 `json:"major_version"` MinorVersion uint8 `json:"minor_version"` // Nonce re-arranged here to improve memory layout space Nonce uint32 `json:"nonce"` Timestamp uint64 `json:"timestamp"` PreviousId types.Hash `json:"previous_id"` //Nonce would be here Coinbase transaction.CoinbaseTransaction `json:"coinbase"` Transactions []types.Hash `json:"transactions,omitempty"` // 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 `json:"transaction_parent_indices,omitempty"` } type Header struct { MajorVersion uint8 `json:"major_version"` MinorVersion uint8 `json:"minor_version"` // Nonce re-arranged here to improve memory layout space Nonce uint32 `json:"nonce"` Timestamp uint64 `json:"timestamp"` PreviousId types.Hash `json:"previous_id"` Height uint64 `json:"height"` //Nonce would be here Reward uint64 `json:"reward"` Difficulty types.Difficulty `json:"difficulty"` Id types.Hash `json:"id"` } func (b *Block) MarshalBinary() (buf []byte, err error) { return b.MarshalBinaryFlags(false, false, false) } func (b *Block) BufferLength() int { return utils.UVarInt64Size(b.MajorVersion) + utils.UVarInt64Size(b.MinorVersion) + utils.UVarInt64Size(b.Timestamp) + types.HashSize + 4 + b.Coinbase.BufferLength() + utils.UVarInt64Size(len(b.Transactions)) + types.HashSize*len(b.Transactions) } func (b *Block) MarshalBinaryFlags(compact, pruned, containsAuxiliaryTemplateId bool) (buf []byte, err error) { return b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), pruned, compact, containsAuxiliaryTemplateId) } func (b *Block) AppendBinaryFlags(preAllocatedBuf []byte, compact, pruned, containsAuxiliaryTemplateId bool) (buf []byte, err error) { buf = preAllocatedBuf if b.MajorVersion > monero.HardForkSupportedVersion { return nil, fmt.Errorf("unsupported version %d", b.MajorVersion) } if b.MinorVersion < b.MajorVersion { return nil, fmt.Errorf("minor version %d smaller than major %d", b.MinorVersion, b.MajorVersion) } buf = binary.AppendUvarint(buf, uint64(b.MajorVersion)) buf = binary.AppendUvarint(buf, uint64(b.MinorVersion)) buf = binary.AppendUvarint(buf, b.Timestamp) buf = append(buf, b.PreviousId[:]...) buf = binary.LittleEndian.AppendUint32(buf, b.Nonce) if buf, err = b.Coinbase.AppendBinaryFlags(buf, pruned, containsAuxiliaryTemplateId); err != nil { return nil, err } 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 } type PrunedFlagsFunc func() (containsAuxiliaryTemplateId bool) func (b *Block) FromReader(reader utils.ReaderAndByteReader, canBePruned bool, f PrunedFlagsFunc) (err error) { return b.FromReaderFlags(reader, false, canBePruned, f) } func (b *Block) FromCompactReader(reader utils.ReaderAndByteReader, canBePruned bool, f PrunedFlagsFunc) (err error) { return b.FromReaderFlags(reader, true, canBePruned, f) } func (b *Block) UnmarshalBinary(data []byte, canBePruned bool, f PrunedFlagsFunc) error { reader := bytes.NewReader(data) return b.FromReader(reader, canBePruned, f) } func (b *Block) FromReaderFlags(reader utils.ReaderAndByteReader, compact, canBePruned bool, f PrunedFlagsFunc) (err error) { var ( txCount uint64 transactionHash types.Hash ) if b.MajorVersion, err = reader.ReadByte(); err != nil { return err } if b.MajorVersion > monero.HardForkSupportedVersion { return fmt.Errorf("unsupported version %d", b.MajorVersion) } if b.MinorVersion, err = reader.ReadByte(); err != nil { return err } if b.MinorVersion < b.MajorVersion { return fmt.Errorf("minor version %d smaller than major version %d", b.MinorVersion, b.MajorVersion) } 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 } var containsAuxiliaryTemplateId bool if canBePruned && f != nil { containsAuxiliaryTemplateId = f() } // Coinbase Tx Decoding { if err = b.Coinbase.FromReader(reader, canBePruned, containsAuxiliaryTemplateId); err != nil { return err } } //TODO: verify hardfork major versions if txCount, err = binary.ReadUvarint(reader); err != nil { return err } else if txCount > MaxTransactionCount { return fmt.Errorf("transaction count count too large: %d > %d", txCount, MaxTransactionCount) } else if txCount > 0 { if compact { // preallocate with soft cap b.Transactions = make([]types.Hash, 0, min(8192, txCount)) b.TransactionParentIndices = make([]uint64, 0, min(8192, 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 { // preallocate with soft cap b.Transactions = make([]types.Hash, 0, min(8192, 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.AuxiliaryData.TotalReward, Id: b.Id(), Difficulty: types.ZeroDifficulty, } } func (b *Block) HeaderBlobBufferLength() int { return 1 + 1 + utils.UVarInt64Size(b.Timestamp) + types.HashSize + 4 } func (b *Block) HeaderBlob(preAllocatedBuf []byte) []byte { buf := preAllocatedBuf 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 or template id set to 0 func (b *Block) SideChainHashingBlob(preAllocatedBuf []byte, zeroTemplateId bool) (buf []byte, err error) { buf = preAllocatedBuf 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 if buf, err = b.Coinbase.SideChainHashingBlob(buf, zeroTemplateId); err != nil { return nil, err } buf = binary.AppendUvarint(buf, uint64(len(b.Transactions))) for _, txId := range b.Transactions { buf = append(buf, txId[:]...) } return buf, nil } func (b *Block) HashingBlobBufferLength() int { return b.HeaderBlobBufferLength() + types.HashSize + utils.UVarInt64Size(len(b.Transactions)+1) } func (b *Block) HashingBlob(preAllocatedBuf []byte) []byte { buf := b.HeaderBlob(preAllocatedBuf) merkleTree := make(crypto.BinaryTreeHash, len(b.Transactions)+1) //TODO: cache? merkleTree[0] = b.Coinbase.CalculateId() copy(merkleTree[1:], b.Transactions) txTreeHash := merkleTree.RootHash() buf = append(buf, txTreeHash[:]...) buf = binary.AppendUvarint(buf, uint64(len(b.Transactions)+1)) return buf } func (b *Block) Difficulty(f GetDifficultyByHeightFunc) types.Difficulty { //cached by sidechain.Share return f(b.Coinbase.GenHeight) } func (b *Block) PowHashWithError(hasher randomx.Hasher, 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(make([]byte, 0, b.HashingBlobBufferLength()))) } } func (b *Block) Id() types.Hash { //cached by sidechain.Share var varIntBuf [binary.MaxVarintLen64]byte buf := b.HashingBlob(make([]byte, 0, b.HashingBlobBufferLength())) return crypto.PooledKeccak256(varIntBuf[:binary.PutUvarint(varIntBuf[:], uint64(len(buf)))], buf) }