Skip to content

qkc/types: add logs and receipts - #34

Merged
blockchaindevsh merged 11 commits into
goshard/basefrom
qkc-3-types-03-logs-receipts
Jul 23, 2026
Merged

qkc/types: add logs and receipts#34
blockchaindevsh merged 11 commits into
goshard/basefrom
qkc-3-types-03-logs-receipts

Conversation

@blockchaindevsh

Copy link
Copy Markdown

3rd PR for QuarkChain/pm#155.

Comment thread qkc/types/bloom9.go
return hexutil.UnmarshalFixedText("Bloom", input, b[:])
}

func CreateBloom(receipts Receipts) Bloom {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we only keep the CreateBloom function and remove other Bloom related code since it is the same as geth

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fa57d72 .

Comment thread qkc/types/log.go Outdated

// Log represents a contract log event. These events are generated by the LOG opcode and
// stored/indexed by the node.
type Log struct {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the key design question is whether the Python master/slave serialization format requires us to introduce qkc/types.Log as a new general-purpose log model.

Modern geth already uses *core/types.Log throughout StateDB, EVM execution, receipt processing, filtering, indexing, and related APIs. If we replace it with qkc/types.Log, or propagate a parallel log model into these layers, we will need changes or conversions across many geth call sites. This would also increase the maintenance cost of future geth updates.

Reusing core/types.Log should not change the QKC receipt root: geth’s RLP encoding of an individual log contains only Address, Topics, and Data, which matches the log payload used by QKC receipt RLP. QKC can still keep its own receipt type for QKC-specific receipt fields such as contract_address and contract_full_shard_key.

The place where QKC does need a different representation is the Python master/slave protocol. Python expects the following qkc/serialize schema:

recipient, topics, data,
block_number, block_hash, tx_idx, tx_hash, log_idx

This schema differs from the fields and types in core/types.Log. However, a different cluster serialization format does not necessarily require a different execution-layer model. We can define serialization-only cluster types at the master/slave RPC boundary.

This is also how pyquarkchain is structured today. It has separate classes for the two roles:

quarkchain.evm.messages.Log / Receipt
    EVM and receipt-trie objects

quarkchain.core.Log / TransactionReceipt
    query and master/slave serialization objects

In MinorBlock.get_receipt(), Python reads an evm.messages.Receipt, converts each EVM log with Log.create_from_eth_log(), and returns a core.TransactionReceipt containing core.Log objects. The converted object is then used by the cluster RPC response.

We can mirror that boundary in Go:

qkc/types.Receipt
  Logs: []*core/types.Log
            |
            | toClusterTransactionReceipt()
            v
ClusterTransactionReceipt
  Logs: []*ClusterLog
            |
            | qkc/serialize
            v
Python master

The cluster types could look approximately like:

type ClusterLog struct {
    Recipient   common.Address
    Topics      []common.Hash
    Data        []byte `bytesizeofslicelen:"4"`
    BlockNumber uint64
    BlockHash   common.Hash
    TxIndex     uint32
    TxHash      common.Hash
    Index       uint32
}

type ClusterTransactionReceipt struct {
    Success         []byte
    GasUsed         uint64
    PrevGasUsed     uint64
    Bloom           coretypes.Bloom
    ContractAddress account.Address
    Logs            []*ClusterLog `bytesizeofslicelen:"4"`
}

Introducing only ClusterLog would not be sufficient because receipt serialization recursively serializes its Logs field. The cluster layer should convert the complete object graph before calling qkc/serialize:

internal QKC receipt
    -> convert every core/types.Log to ClusterLog
    -> construct ClusterTransactionReceipt
    -> construct GetTransactionReceiptResponse
    -> qkc/serialize

GetLogResponse would similarly convert its []*core/types.Log into []*ClusterLog.

This boundary also gives us an explicit place to:

  • Omit geth-only fields such as BlockTimestamp and Removed.
  • Convert geth’s uint indexes to the Python protocol’s uint32.
  • Check for index overflow.
  • Preserve Python’s exact field order and length-prefix rules.

There are also two concrete compatibility issues in the current implementation.

First, qkc/types.Log does not implement an explicit QKC Serialize method. qkc/serialize therefore encodes it through reflection, making the Go struct declaration order part of the wire protocol.

The current Go order is:

Recipient, Topics, Data, BlockNumber, TxHash, TxIndex, BlockHash, Index

Python expects:

recipient, topics, data, block_number, block_hash, tx_idx, tx_hash, log_idx

TxHash and BlockHash currently occupy the opposite wire positions, so the encoded bytes are not compatible with the Python master. A test can miss this if it uses the same value for both hashes.

Second, the current receiptSer starts with:

TxHash common.Hash

but Python core.TransactionReceipt.FIELDS does not contain tx_hash; its first field is success. The current receipt encoding therefore has an extra leading 32 bytes and is also incompatible with Python.

Could we keep core/types.Log as the internal log model and move the Python-specific receipt/log representation to explicitly named cluster types? The current receiptSer could be refactored into ClusterTransactionReceipt, with the extra TxHash removed and its Logs field changed to []*ClusterLog.

It would also be useful to add Python-generated golden-vector tests using different TxHash and BlockHash values, plus a complete TransactionReceipt vector, to verify byte-for-byte compatibility.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 19db4b5 .

Comment thread qkc/types/receipt.go Outdated
// Receipt represents the results of a transaction.
type Receipt struct {
// Consensus fields
PostState []byte `json:"root"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QKC mainnet receipts do not use a 32-byte post-state here. pyquarkchain.mk_receipt always writes 0x01 for success and empty bytes for failure, and the Python master treats only 0x01 as success. Can we remove PostState from the normal QKC model, or clearly isolate it as legacy-only, and update the golden tests to cover real success and failure values? The current 32-byte test value would be reported as failed by the master.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c6d5874.

Comment thread qkc/types/receipt.go

// Implementation fields (don't reorder!)
TxHash common.Hash `json:"transactionHash" gencodec:"required"`
ContractAddress account.Recipient `json:"contractAddress"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ContractAddress and ContractFullShardKey are included by receiptRLP, so they affect the receipt trie root and are QKC consensus fields. Can we move them under Consensus fields? Only TxHash and GasUsed are derived/implementation fields here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8389246.

Comment thread qkc/types/receipt.go Outdated
"github.com/ethereum/go-ethereum/rlp"
)

//go:generate gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

receiptMarshaling currently has no effect because gen_receipt_json.go is not in this PR, and go generate ./qkc/types fails because gencodec is not on PATH. Please use the repository pattern go run github.com/fjl/gencodec ... and commit the generated file, or remove this directive and override if a JSON codec is not needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked both pyquarkchain and goquarkchain. Neither implementation marshals the QKC TransactionReceipt type directly to JSON. Pyquarkchain uses receipt_encoder to build a dictionary, while goquarkchain uses ReceiptEncoder or constructs a map[string]interface{} in the RPC layer. Goquarkchain contains the same generator directive, but it also has no generated receipt JSON file, and I could not find any direct json.Marshal or json.Unmarshal use of this type.

Since there is no current consumer for a generated JSON codec, could we remove the generator directive, receiptMarshaling, the now-unused hexutil import, and the gencodec tags? If direct JSON serialization is needed later, it can be added together with its actual caller and generated implementation.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in dc3f5e8 .

Comment thread qkc/types/receipt.go Outdated
ContractFullShardKey *Uint32
}

type receiptStorageRLP struct {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which storage format is this intended to preserve? It matches old goquarkchain's full receipt storage, but this PR has no consumer or storage golden test; pyquarkchain stores consensus receipts in the receipt trie, and modern geth uses a different minimal storage format. Can we defer this until the consumer and schema are defined, or add the intended consumer and a compatibility vector?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0e3ea51.

Comment thread qkc/types/receipt.go Outdated
}

// Serialize serialize the QKC minor block.
func (r *Receipt) Serialize(w *[]byte) error {

@qzhodl qzhodl Jul 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Receipt is the internal receipt model, while ClusterTransactionReceipt represents the Python master/slave wire format. They have different purposes.

Currently, Receipt.Serialize and Receipt.Deserialize hide the conversion between these two types. This makes the internal Receipt depend on the cluster protocol and makes it unclear which format is being serialized.

Since the cluster RPC is not implemented in this PR, could we:

  1. Remove Serialize and Deserialize from Receipt.
  2. Keep wire serialization only on ClusterTransactionReceipt.
  3. Add explicit conversion functions between the two types:
NewClusterTransactionReceipt(*Receipt) (*ClusterTransactionReceipt, error)
(*ClusterTransactionReceipt).ToReceipt() (*Receipt, error)

When the cluster RPC is added, it can call these conversions at the RPC boundary. This keeps the internal receipt independent of the Python wire format and makes the serialization format explicit.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c0664e4.

Comment thread qkc/types/cluster_receipt.go Outdated
return nil, err
}
return &ClusterTransactionReceipt{
Success: status,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

status points to the global receiptStatusSuccessfulRLP slice. Assigning it directly to the public Success field exposes the same underlying bytes, so clusterReceipt.Success[0] = 2 would also change future receipt status encoding.

Could we copy it here?

Success: common.CopyBytes(status),

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8ba5f6a.

Comment thread qkc/types/receipt.go
return err
}
r.CumulativeGasUsed, r.Bloom, r.Logs, r.ContractAddress, r.ContractFullShardKey = dec.CumulativeGasUsed,
dec.Bloom, dec.Logs, common.BytesToAddress(dec.ContractAddress), dec.ContractFullShardKey.GetValue()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dec.ContractAddress is an unrestricted []byte, and common.BytesToAddress accepts any length by padding or truncating it. Python receipt RLP accepts only an empty value or exactly 20 bytes. As written, values with lengths such as 1, 19, 21, or 32 bytes are accepted and normalized, so decoding and re-encoding changes the receipt bytes.

Could we validate that len(dec.ContractAddress) is either 0 or common.AddressLength before converting it?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1c12ebf .

Comment thread qkc/types/cluster_receipt.go Outdated
BlockHash: log.BlockHash,
TxIndex: uint32(log.TxIndex),
TxHash: log.TxHash,
Index: uint32(log.Index),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coretypes.Log.Index and Python Log.log_idx have different meanings. Geth sets Log.Index to the global log index in the block, while pyquarkchain sets log_idx from enumerate(receipt.logs), so it starts from 0 for every receipt.

For example, if transaction 0 emits two logs, the first log of transaction 1 has Index 2 in geth but log_idx 0 in Python. Copying log.Index here will therefore return a different value to the Python master.

Could we pass the QKC per-receipt log index into NewClusterLog explicitly, or set it from the position in r.Logs inside NewClusterTransactionReceipt? A test with logs from a second transaction would cover this difference.

@blockchaindevsh
blockchaindevsh force-pushed the qkc-3-types-02-token-balances branch from 625320b to f666e52 Compare July 22, 2026 15:46
@blockchaindevsh
blockchaindevsh force-pushed the qkc-3-types-03-logs-receipts branch from 92c7c91 to 1d9f03d Compare July 22, 2026 15:55
@blockchaindevsh
blockchaindevsh changed the base branch from qkc-3-types-02-token-balances to goshard/base July 22, 2026 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants