qkc/types: add transactions and signing - #35
Conversation
| "github.com/ethereum/go-ethereum/qkc/account" | ||
| ) | ||
|
|
||
| func bigIntToHex(data *big.Int) string { |
There was a problem hiding this comment.
The filename solidity_abi_utils.go seems a bit unrelated to the transaction-related changes. Maybe we should choose a more descriptive name.
Also, are all the functions in this file used by the transaction module?
There was a problem hiding this comment.
Good point. I renamed this to transaction_typed_hash.go and renamed the test accordingly in a53fef1.
I also checked the helpers here: they are all used by the transaction signing path via EvmTransaction.typedHash() -> typedSignatureHash(evmTxToTypedData(tx)). The tests cover both the typed-data conversion and intermediate packed/hash outputs.
92c7c91 to
1d9f03d
Compare
a53fef1 to
45f102f
Compare
| TXList []*CrossShardTransactionDeposit `bytesizeofslicelen:"4"` | ||
| } | ||
|
|
||
| type CrossShardTransactionDepositList struct { |
There was a problem hiding this comment.
I cannot find CrossShardTransactionDepositList in pyquarkchain. The corresponding Python type seems to be CrossShardTransactionList.
Could you confirm this type is intended to match Python CrossShardTransactionList(version=1)? The field order looks different: Python serializes tx_list followed by version(uint32), while this implementation appears to encode version into the size prefix (bytes[0] = 1).
There was a problem hiding this comment.
You’re right. The existing CrossShardTransactionDepositList was based on goquarkchain’s packed-prefix format and does not match pyquarkchain.
I replaced it with CrossShardTransactionList in 1e3f38e), matching Python v1 exactly: tx_list with its normal 4-byte count prefix, followed by version as a trailing uint32(1). I also removed the unused legacy-v0 list path and added Python-generated golden vectors for empty and one-entry v1 lists, plus an unsupported-version test.
bd79a6f to
1e3f38e
Compare
|
|
||
| // Hash returns the hash to be signed by the sender. | ||
| // It does not uniquely identify the transaction. | ||
| func (s EIP155Signer) Hash(tx *EvmTransaction) common.Hash { |
There was a problem hiding this comment.
The Hash function may also need to branch by tx version, as in Sender.
| func data(tx []map[string]string) []string { | ||
| t := make([]string, 0) | ||
| for _, v := range tx { | ||
| if v["types"] == "bytes" { |
There was a problem hiding this comment.
The key in the map is "type" instead of "types".
| } | ||
|
|
||
| type CrossShardTransactionDeposit struct { | ||
| CrossShardTransactionDepositV0 |
There was a problem hiding this comment.
We can remove the CrossShardTransactionDepositV0 type and merge it into CrossShardTransactionDeposit
| updated bool | ||
| hash atomic.Value |
There was a problem hiding this comment.
Why are there three copies of hashes in Transaction, EvmTransaction, and txdata?
There was a problem hiding this comment.
Removed the one in txdata in 77ed4e4, now it aligns with pyquarkchain:
EvmTransaction.hash is the RLP hash of the inner EVM transaction, while Transaction.hash is the hash of the outer typed QKC envelope.
|
|
||
| func (e *EvmTransaction) SetFromFullShardKey(data uint32) { | ||
| t := Uint32(data) | ||
| e.data.FromFullShardKey = &t |
There was a problem hiding this comment.
Why do some setters set updated to true while others don't? What does updated indicate?
There was a problem hiding this comment.
Replaced the vague updated flag with hashDirty in 754a0de. It now tracks changes to fields encoded in the inner EVM RLP hash, and all relevant setters (SetGas, SetFromFullShardKey, SetNonce, and SetVRS) mark it dirty. Hash() recomputes once and then caches the new value.
SetSender does not invalidate it because it only updates the derived sender cache, which is not part of the RLP payload. Added regression coverage for each hash-affecting setter.
|
|
||
| func (e *EvmTransaction) SetFromFullShardKey(data uint32) { | ||
| t := Uint32(data) | ||
| e.data.FromFullShardKey = &t |
There was a problem hiding this comment.
Why do some setters set updated to true while others don't? What does updated indicate?
| func (tx *EvmTransaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) } | ||
| func (tx *EvmTransaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) } | ||
| func (tx *EvmTransaction) Nonce() uint64 { return tx.data.AccountNonce } | ||
| func (tx *EvmTransaction) CheckNonce() bool { return true } |
| // | ||
| // Note, the input map is reowned so the caller should not interact any more with | ||
| // if after providing it to the constructor. | ||
| func NewTransactionsByPriceAndNonce(signer Signer, txs map[account.Recipient]Transactions) (*TransactionsByPriceAndNonce, error) { |
There was a problem hiding this comment.
New Geth includes this function in the miner module, not in this file
There was a problem hiding this comment.
Removed in 3221b8b, it can be introduced in the future QKC mining or transaction-pool integration.
| // Message is a fully derived transaction and implements core.Message | ||
| // | ||
| // NOTE: In a future PR this will be removed. | ||
| type Message struct { |
There was a problem hiding this comment.
New Geth includes this type in the state_transition.go, not in this file
There was a problem hiding this comment.
Removed in 432ed4c, it can be introduced later with the state-transition integration.
| } | ||
|
|
||
| // EIP155Transaction implements Signer using the EIP155 rules. | ||
| type EIP155Signer struct { |
There was a problem hiding this comment.
EIP155Signer is misleading because this signer handles all QKC transaction versions, including the non-EIP-155 versions 0 and 1. The networkId field also has inconsistent semantics: it is the expected QKC network ID for v0/v1, but is ignored for v2, where tx.NetworkId() is interpreted as the Ethereum chain ID.
Could we rename this type to QKCSigner (or VersionedSigner) and explicitly carry both IDs?
type QKCSigner struct {
qkcNetworkID uint32
ethChainID uint32
}Sender could then validate qkcNetworkID for v0/v1 and ethChainID for v2. This would make signer construction unambiguous and ensure v2 chain-ID validation does not depend solely on a separate validation layer.
| } | ||
| R = new(big.Int).SetBytes(sig[:32]) | ||
| S = new(big.Int).SetBytes(sig[32:64]) | ||
| V = new(big.Int).SetBytes([]byte{sig[64] + 27}) |
There was a problem hiding this comment.
For version 2, this currently stores V as only 27/28. However, Sender() treats version 2 as EIP-155 and subtracts 2*chainID + 8, so a v2 transaction produced by SignTx cannot be recovered correctly. It also differs from pyquarkchain, which stores V = 35/36 + 2*chainID.
Please add the EIP-155 offset for v2, for example:
V = new(big.Int).SetUint64(uint64(sig[64]) + 27)
if tx.Version() == 2 {
V.Add(V, new(big.Int).SetUint64(8+2*uint64(tx.NetworkId())))
}Please also add a regression test that signs a v2 transaction with SignTx, checks that V is 35/36 + 2*chainID, and verifies that Sender recovers the original address.
| return hexutil.Encode(new(big.Int).SetUint64(uint64(data)).Bytes()) | ||
| } | ||
|
|
||
| func receiptToHex(data *account.Recipient) string { |
4th PR of QuarkChain/pm#155.