Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions qkc/types/cross_shard_transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2026-2027, QuarkChain.

// Cross-shard transactions follow pyquarkchain-compatible QKC wire encoding.

package types

import (
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/qkc/account"
"github.com/ethereum/go-ethereum/qkc/serialize"
)

const crossShardTransactionListVersion = uint32(1)

type CrossShardTransactionDepositV0 struct {
TxHash common.Hash
From account.Address
To account.Address
Value *serialize.Uint256
GasPrice *serialize.Uint256
GasTokenID uint64
TransferTokenID uint64
GasRemained *serialize.Uint256
MessageData []byte `bytesizeofslicelen:"4"`
CreateContract bool
// Follow pyquarkchain (core.py CrossShardTransactionDepositV0.FIELDS):
// is_from_root_chain is serialized last for master/slave wire compatibility.
IsFromRootChain bool
}

type CrossShardTransactionDeposit struct {
CrossShardTransactionDepositV0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We can remove the CrossShardTransactionDepositV0 type and merge it into CrossShardTransactionDeposit

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 0734b84.

RefundRate uint8
}

// CrossShardTransactionList is pyquarkchain's CrossShardTransactionList
// version 1 wire type.
type CrossShardTransactionList struct {
TXList []*CrossShardTransactionDeposit
}

func NewCrossShardTransactionList(txList []*CrossShardTransactionDeposit) *CrossShardTransactionList {
if txList == nil {
txList = make([]*CrossShardTransactionDeposit, 0)
}
return &CrossShardTransactionList{
TXList: txList,
}
}

// Serialize writes pyquarkchain CrossShardTransactionList.FIELDS order:
// tx_list followed by version(uint32).
func (c *CrossShardTransactionList) Serialize(w *[]byte) error {
if c == nil {
return fmt.Errorf("nil cross-shard transaction list")
}
if err := serialize.SerializeWithTags(w, c.TXList, serialize.Tags{ByteSizeOfSliceLen: 4}); err != nil {
return err
}
return serialize.Serialize(w, crossShardTransactionListVersion)
}

// Deserialize reads the current pyquarkchain CrossShardTransactionList version.
func (c *CrossShardTransactionList) Deserialize(bb *serialize.ByteBuffer) error {
if c == nil {
return fmt.Errorf("nil cross-shard transaction list")
}
var txList []*CrossShardTransactionDeposit
if err := serialize.DeserializeWithTags(bb, &txList, serialize.Tags{ByteSizeOfSliceLen: 4}); err != nil {
Comment thread
ping-ke marked this conversation as resolved.
return err
}
var version uint32
Comment thread
ping-ke marked this conversation as resolved.
if err := serialize.Deserialize(bb, &version); err != nil {
return err
}
if version != crossShardTransactionListVersion {
return fmt.Errorf("unsupported cross-shard transaction list version %d", version)
}
c.TXList = txList
return nil
}
156 changes: 156 additions & 0 deletions qkc/types/cross_shard_transaction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright 2026-2027, QuarkChain.

// Cross-shard transaction tests exercise pyquarkchain-compatible QKC wire bytes.

package types

import (
"bytes"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/qkc/account"
"github.com/ethereum/go-ethereum/qkc/serialize"
"github.com/stretchr/testify/assert"
)

type legacyDepositV0FieldOrderForTest struct {
TxHash common.Hash
From account.Address
To account.Address
Value *serialize.Uint256
GasPrice *serialize.Uint256
GasTokenID uint64
TransferTokenID uint64
IsFromRootChain bool
GasRemained *serialize.Uint256
MessageData []byte `bytesizeofslicelen:"4"`
CreateContract bool
}

func TestCrossShardDepositV0PyquarkchainFieldOrder(t *testing.T) {
u256 := &serialize.Uint256{Value: big.NewInt(1)}
deposit := CrossShardTransactionDepositV0{
Value: u256,
GasPrice: u256,
GasTokenID: 2,
TransferTokenID: 3,
GasRemained: u256,
MessageData: []byte{0xaa},
CreateContract: false,
IsFromRootChain: true,
}
encoded, err := serialize.SerializeToBytes(deposit)
assert.NoError(t, err)
assert.Equal(t, byte(1), encoded[len(encoded)-1], "pyquarkchain serializes is_from_root_chain last")

wrongOrder := legacyDepositV0FieldOrderForTest{
Value: u256,
GasPrice: u256,
GasTokenID: 2,
TransferTokenID: 3,
IsFromRootChain: true,
GasRemained: u256,
MessageData: []byte{0xaa},
CreateContract: false,
}
wrongEncoded, err := serialize.SerializeToBytes(wrongOrder)
assert.NoError(t, err)
assert.False(t, bytes.Equal(encoded, wrongEncoded), "legacy field order is not pyquarkchain-compatible")
}

func TestCrossShardTransactionList(t *testing.T) {
c1 := NewCrossShardTransactionList(nil)
for index := uint64(0); index < 100; index++ {
u256 := new(serialize.Uint256)
u256.Value = new(big.Int).SetUint64(index)
c1.TXList = append(c1.TXList, &CrossShardTransactionDeposit{
CrossShardTransactionDepositV0: CrossShardTransactionDepositV0{
TxHash: common.BigToHash(new(big.Int).SetUint64(index)),
From: account.Address{
Recipient: common.BigToAddress(new(big.Int).SetUint64(2)),
FullShardKey: 2,
},
To: account.Address{
Recipient: common.BigToAddress(new(big.Int).SetUint64(3)),
FullShardKey: 3,
},
Value: u256,
GasPrice: u256,
GasTokenID: 123,
TransferTokenID: 456,
IsFromRootChain: false,
GasRemained: u256,
MessageData: []byte{},
CreateContract: true,
},
RefundRate: uint8(index),
})
}

data, err := serialize.SerializeToBytes(c1)
assert.NoError(t, err)

d1 := NewCrossShardTransactionList(nil)
err = serialize.DeserializeFromBytes(data, d1)
assert.NoError(t, err)
for k, v := range c1.TXList {
assert.Equal(t, v.TxHash, (*d1).TXList[k].TxHash)
assert.Equal(t, v.From, (*d1).TXList[k].From)
assert.Equal(t, v.To, (*d1).TXList[k].To)
assert.Equal(t, v.Value.Value.Uint64(), (*d1).TXList[k].Value.Value.Uint64())
assert.Equal(t, v.GasPrice.Value.Uint64(), (*d1).TXList[k].GasPrice.Value.Uint64())
assert.Equal(t, v.GasTokenID, (*d1).TXList[k].GasTokenID)
assert.Equal(t, v.TransferTokenID, (*d1).TXList[k].TransferTokenID)
assert.Equal(t, v.IsFromRootChain, (*d1).TXList[k].IsFromRootChain)
assert.Equal(t, v.GasRemained.Value.Uint64(), (*d1).TXList[k].GasRemained.Value.Uint64())
assert.Equal(t, v.MessageData, (*d1).TXList[k].MessageData)
assert.Equal(t, v.CreateContract, (*d1).TXList[k].CreateContract)
assert.Equal(t, uint8(k), (*d1).TXList[k].RefundRate)
}

}

func TestCrossShardTransactionListPyquarkchainGolden(t *testing.T) {
assertSerialized := func(t *testing.T, list *CrossShardTransactionList, expected string) {
t.Helper()
encoded, err := serialize.SerializeToBytes(list)
assert.NoError(t, err)
assert.Equal(t, expected, common.Bytes2Hex(encoded))

var decoded CrossShardTransactionList
err = serialize.DeserializeFromBytes(common.FromHex(expected), &decoded)
assert.NoError(t, err)
reencoded, err := serialize.SerializeToBytes(&decoded)
assert.NoError(t, err)
assert.Equal(t, expected, common.Bytes2Hex(reencoded))
}

// Generated by pyquarkchain's CrossShardTransactionList.serialize().
assertSerialized(t, NewCrossShardTransactionList(nil), "0000000000000001")

u256 := func(value int64) *serialize.Uint256 { return &serialize.Uint256{Value: big.NewInt(value)} }
assertSerialized(t, NewCrossShardTransactionList([]*CrossShardTransactionDeposit{{
CrossShardTransactionDepositV0: CrossShardTransactionDepositV0{
TxHash: common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111"),
From: account.Address{Recipient: common.HexToAddress("0x2222222222222222222222222222222222222222"), FullShardKey: 3},
To: account.Address{Recipient: common.HexToAddress("0x3333333333333333333333333333333333333333"), FullShardKey: 4},
Value: u256(5),
GasPrice: u256(6),
GasTokenID: 7,
TransferTokenID: 8,
GasRemained: u256(9),
MessageData: []byte{0xaa, 0xbb},
CreateContract: true,
IsFromRootChain: true,
},
RefundRate: 10,
}}), "0000000111111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222000000033333333333333333333333333333333333333333000000040000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000070000000000000008000000000000000000000000000000000000000000000000000000000000000900000002aabb01010a00000001")
}

func TestCrossShardTransactionListRejectsUnsupportedVersion(t *testing.T) {
var list CrossShardTransactionList
err := serialize.DeserializeFromBytes(common.FromHex("0000000000000000"), &list)
assert.Error(t, err)
}
Loading