mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-21 20:26:41 +00:00
Revert "feat(types): add QuarkChain MNT account and token types"
This reverts commit 6ab6553457.
This commit is contained in:
parent
32adfb24a2
commit
1a1d5c5d3d
9 changed files with 33 additions and 785 deletions
21
core/types/gen_account_rlp.go
Normal file
21
core/types/gen_account_rlp.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Code generated by rlpgen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/ethereum/go-ethereum/rlp"
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func (obj *StateAccount) EncodeRLP(_w io.Writer) error {
|
||||||
|
w := rlp.NewEncoderBuffer(_w)
|
||||||
|
_tmp0 := w.List()
|
||||||
|
w.WriteUint64(obj.Nonce)
|
||||||
|
if obj.Balance == nil {
|
||||||
|
w.Write(rlp.EmptyString)
|
||||||
|
} else {
|
||||||
|
w.WriteUint256(obj.Balance)
|
||||||
|
}
|
||||||
|
w.WriteBytes(obj.Root[:])
|
||||||
|
w.WriteBytes(obj.CodeHash)
|
||||||
|
w.ListEnd(_tmp0)
|
||||||
|
return w.Flush()
|
||||||
|
}
|
||||||
|
|
@ -29,13 +29,10 @@ import (
|
||||||
// StateAccount is the Ethereum consensus representation of accounts.
|
// StateAccount is the Ethereum consensus representation of accounts.
|
||||||
// These objects are stored in the main account trie.
|
// These objects are stored in the main account trie.
|
||||||
type StateAccount struct {
|
type StateAccount struct {
|
||||||
Nonce uint64
|
Nonce uint64
|
||||||
Balance *uint256.Int
|
Balance *uint256.Int
|
||||||
Root common.Hash // merkle root of the storage trie
|
Root common.Hash // merkle root of the storage trie
|
||||||
CodeHash []byte
|
CodeHash []byte
|
||||||
MntBalances *TokenBalances `rlp:"optional"` // non-QKC MNT balances; nil = no MNT tokens
|
|
||||||
FullShardKey uint32 // QuarkChain shard key; set on first tx, preserved thereafter
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEmptyStateAccount constructs an empty state account.
|
// NewEmptyStateAccount constructs an empty state account.
|
||||||
|
|
@ -53,17 +50,11 @@ func (acct *StateAccount) Copy() *StateAccount {
|
||||||
if acct.Balance != nil {
|
if acct.Balance != nil {
|
||||||
balance = new(uint256.Int).Set(acct.Balance)
|
balance = new(uint256.Int).Set(acct.Balance)
|
||||||
}
|
}
|
||||||
var mnt *TokenBalances
|
|
||||||
if acct.MntBalances != nil {
|
|
||||||
mnt = acct.MntBalances.Copy()
|
|
||||||
}
|
|
||||||
return &StateAccount{
|
return &StateAccount{
|
||||||
Nonce: acct.Nonce,
|
Nonce: acct.Nonce,
|
||||||
Balance: balance,
|
Balance: balance,
|
||||||
Root: acct.Root,
|
Root: acct.Root,
|
||||||
CodeHash: common.CopyBytes(acct.CodeHash),
|
CodeHash: common.CopyBytes(acct.CodeHash),
|
||||||
MntBalances: mnt,
|
|
||||||
FullShardKey: acct.FullShardKey,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
)
|
|
||||||
|
|
||||||
// qkcAccountRLP is the wire struct for QuarkChain's 6-element account format:
|
|
||||||
// [Nonce, TokenBal(bytes), Root, CodeHash, FullShardKey(4B fixed), Optional].
|
|
||||||
// TokenBal is stored as raw serialized bytes (matching pyquarkchain's `binary` type),
|
|
||||||
// so nil encodes as 0x80 (empty string), not 0xC0 (empty list).
|
|
||||||
type qkcAccountRLP struct {
|
|
||||||
Nonce uint64
|
|
||||||
TokenBal []byte // SerializeToBytes output; nil = no balances
|
|
||||||
Root common.Hash
|
|
||||||
CodeHash []byte
|
|
||||||
FullShardKey Uint32
|
|
||||||
Optional []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeQKCTokenBalances combines the QKC native balance (tokenID=35760) and MNT
|
|
||||||
// balances into a single TokenBalances for wire encoding. Returns nil if both empty,
|
|
||||||
// which causes EncodeRLP to write 0x80 (RLP nil/empty) matching goquarkchain behavior.
|
|
||||||
func mergeQKCTokenBalances(balance *uint256.Int, mnt *TokenBalances) *TokenBalances {
|
|
||||||
if (balance == nil || balance.IsZero()) && (mnt == nil || mnt.IsBlank()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
merged := NewEmptyTokenBalances()
|
|
||||||
if mnt != nil {
|
|
||||||
for id, bal := range mnt.GetBalanceMap() {
|
|
||||||
merged.SetValue(bal, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if balance != nil && !balance.IsZero() {
|
|
||||||
merged.SetValue(balance, DefaultTokenID)
|
|
||||||
}
|
|
||||||
return merged
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeRLP implements rlp.Encoder for StateAccount using QuarkChain's
|
|
||||||
// 6-element format. Root is always written as 32 bytes (no nil optimization).
|
|
||||||
func (acct *StateAccount) EncodeRLP(w io.Writer) error {
|
|
||||||
var tokenBal []byte
|
|
||||||
switch {
|
|
||||||
case acct.MntBalances == nil && (acct.Balance == nil || acct.Balance.IsZero()):
|
|
||||||
// No QKC balance, no MNT tokens — encode TokenBal as nil → 0x80.
|
|
||||||
// Covers: new accounts and accounts that never entered the TokenBalances map.
|
|
||||||
tokenBal = nil
|
|
||||||
|
|
||||||
case acct.MntBalances != nil && acct.MntBalances.IsBlank() && acct.Balance != nil && acct.Balance.IsZero():
|
|
||||||
// QKC balance zero, MNT map explicitly empty → re-serialize as
|
|
||||||
// list-format with zero pairs → 0x8200c0. This preserves the
|
|
||||||
// "account touched TokenBalances map then emptied it" history.
|
|
||||||
tokenBal, _ = acct.MntBalances.SerializeToBytes()
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Normal case: has QKC balance and/or non-empty MNT tokens.
|
|
||||||
tb := mergeQKCTokenBalances(acct.Balance, acct.MntBalances)
|
|
||||||
var err error
|
|
||||||
tokenBal, err = tb.SerializeToBytes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
qkc := &qkcAccountRLP{
|
|
||||||
Nonce: acct.Nonce,
|
|
||||||
Root: acct.Root,
|
|
||||||
CodeHash: acct.CodeHash,
|
|
||||||
TokenBal: tokenBal,
|
|
||||||
FullShardKey: Uint32(acct.FullShardKey),
|
|
||||||
Optional: nil,
|
|
||||||
}
|
|
||||||
return rlp.Encode(w, qkc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeRLP implements rlp.Decoder for StateAccount using QuarkChain's
|
|
||||||
// 6-element format.
|
|
||||||
func (acct *StateAccount) DecodeRLP(s *rlp.Stream) error {
|
|
||||||
raw, err := s.Raw()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var qkc qkcAccountRLP
|
|
||||||
if err := rlp.DecodeBytes(raw, &qkc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
acct.Nonce = qkc.Nonce
|
|
||||||
acct.CodeHash = qkc.CodeHash
|
|
||||||
acct.Root = qkc.Root
|
|
||||||
acct.FullShardKey = uint32(qkc.FullShardKey)
|
|
||||||
acct.Balance = new(uint256.Int)
|
|
||||||
if len(qkc.TokenBal) > 0 {
|
|
||||||
tb, err := NewTokenBalancesFromBytes(qkc.TokenBal)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
balMap := tb.GetBalanceMap()
|
|
||||||
if qkcBal, ok := balMap[DefaultTokenID]; ok {
|
|
||||||
acct.Balance.Set(qkcBal)
|
|
||||||
delete(balMap, DefaultTokenID)
|
|
||||||
}
|
|
||||||
// Always set MntBalances when TokenBal has content — even if empty
|
|
||||||
// after stripping QKC, this lets EncodeRLP produce 0x8200c0 instead
|
|
||||||
// of 0x80, preserving byte-identical round-trip.
|
|
||||||
acct.MntBalances = &TokenBalances{balances: balMap}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,285 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
// pyquarkchain test vectors generated by:
|
|
||||||
//
|
|
||||||
// from quarkchain.evm.state import _Account, TokenBalancePair
|
|
||||||
// import rlp
|
|
||||||
// from rlp.sedes import BigEndianInt, binary, CountableList, big_endian_int
|
|
||||||
//
|
|
||||||
// See pyquarkchain/quarkchain/evm/state.py _Account for the canonical format.
|
|
||||||
//
|
|
||||||
// All vectors use:
|
|
||||||
// - storage = EmptyRootHash (keccak256 of empty)
|
|
||||||
// - code_hash = EmptyCodeHash (keccak256 of empty string)
|
|
||||||
// - full_shard_key = 1 (BigEndianInt(4) → always 4 bytes on the wire)
|
|
||||||
// - optional = b""
|
|
||||||
var (
|
|
||||||
pyqkcVecNonce1QKC1000 = mustHex("f853018900c7c6828bb08203e8a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470840000000180")
|
|
||||||
pyqkcVecNonce5QKC2000MNT500 = mustHex("f858058e00ccc4648201f4c6828bb08207d0a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470840000000180")
|
|
||||||
pyqkcVecZeroAccount = mustHex("f84a8080a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470840000000180")
|
|
||||||
)
|
|
||||||
|
|
||||||
func mustHex(s string) []byte {
|
|
||||||
b, err := hex.DecodeString(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestStateAccountEncodeDecodeRoundtrip verifies that EncodeRLP→DecodeRLP is
|
|
||||||
// a lossless roundtrip for all fields including FullShardKey.
|
|
||||||
func TestStateAccountEncodeDecodeRoundtrip(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
acct StateAccount
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
acct: *NewEmptyStateAccount(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nonce1 QKC=1000 shard=1",
|
|
||||||
acct: StateAccount{
|
|
||||||
Nonce: 1,
|
|
||||||
Balance: uint256.NewInt(1000),
|
|
||||||
Root: EmptyRootHash,
|
|
||||||
CodeHash: EmptyCodeHash[:],
|
|
||||||
FullShardKey: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nonce5 QKC=2000 MNT[100]=500 shard=0x2f3e",
|
|
||||||
acct: StateAccount{
|
|
||||||
Nonce: 5,
|
|
||||||
Balance: uint256.NewInt(2000),
|
|
||||||
Root: EmptyRootHash,
|
|
||||||
CodeHash: EmptyCodeHash[:],
|
|
||||||
MntBalances: NewTokenBalancesWithMap(map[uint64]*uint256.Int{
|
|
||||||
100: uint256.NewInt(500),
|
|
||||||
}),
|
|
||||||
FullShardKey: 0x2f3e,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-empty root and codehash shard=0x72ea",
|
|
||||||
acct: StateAccount{
|
|
||||||
Nonce: 3,
|
|
||||||
Balance: uint256.NewInt(1e18),
|
|
||||||
Root: common.HexToHash("0xdeadbeef"),
|
|
||||||
CodeHash: common.Hex2Bytes("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"),
|
|
||||||
FullShardKey: 0x72ea,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
encoded, err := rlp.EncodeToBytes(&tc.acct)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var decoded StateAccount
|
|
||||||
require.NoError(t, rlp.DecodeBytes(encoded, &decoded))
|
|
||||||
|
|
||||||
assert.Equal(t, tc.acct.Nonce, decoded.Nonce)
|
|
||||||
assert.Equal(t, tc.acct.Root, decoded.Root)
|
|
||||||
assert.Equal(t, tc.acct.CodeHash, decoded.CodeHash)
|
|
||||||
assert.Equal(t, tc.acct.FullShardKey, decoded.FullShardKey)
|
|
||||||
require.NotNil(t, decoded.Balance)
|
|
||||||
assert.Equal(t, tc.acct.Balance, decoded.Balance)
|
|
||||||
if tc.acct.MntBalances == nil || tc.acct.MntBalances.IsBlank() {
|
|
||||||
assert.True(t, decoded.MntBalances == nil || decoded.MntBalances.IsBlank())
|
|
||||||
} else {
|
|
||||||
require.NotNil(t, decoded.MntBalances)
|
|
||||||
assert.Equal(t, tc.acct.MntBalances.GetBalanceMap(), decoded.MntBalances.GetBalanceMap())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestStateAccountPyquarkchainDecodeCompatibility verifies that goshard can
|
|
||||||
// decode blobs produced by pyquarkchain. These are the authoritative wire
|
|
||||||
// vectors for the QKC 6-element RLP format. All vectors use full_shard_key=1.
|
|
||||||
func TestStateAccountPyquarkchainDecodeCompatibility(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
blob []byte
|
|
||||||
wantNonce uint64
|
|
||||||
wantQKC *uint256.Int
|
|
||||||
wantMNT map[uint64]*uint256.Int // nil = no MNT tokens expected
|
|
||||||
wantFullShardKey uint32
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nonce=1 QKC=1000",
|
|
||||||
blob: pyqkcVecNonce1QKC1000,
|
|
||||||
wantNonce: 1,
|
|
||||||
wantQKC: uint256.NewInt(1000),
|
|
||||||
wantFullShardKey: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nonce=5 QKC=2000 MNT[100]=500",
|
|
||||||
blob: pyqkcVecNonce5QKC2000MNT500,
|
|
||||||
wantNonce: 5,
|
|
||||||
wantQKC: uint256.NewInt(2000),
|
|
||||||
wantMNT: map[uint64]*uint256.Int{100: uint256.NewInt(500)},
|
|
||||||
wantFullShardKey: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "zero account",
|
|
||||||
blob: pyqkcVecZeroAccount,
|
|
||||||
wantNonce: 0,
|
|
||||||
wantQKC: uint256.NewInt(0),
|
|
||||||
wantFullShardKey: 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
var acct StateAccount
|
|
||||||
require.NoError(t, rlp.DecodeBytes(tc.blob, &acct))
|
|
||||||
|
|
||||||
assert.Equal(t, tc.wantNonce, acct.Nonce)
|
|
||||||
assert.Equal(t, EmptyRootHash, acct.Root)
|
|
||||||
assert.Equal(t, EmptyCodeHash[:], acct.CodeHash)
|
|
||||||
assert.Equal(t, tc.wantFullShardKey, acct.FullShardKey)
|
|
||||||
require.NotNil(t, acct.Balance)
|
|
||||||
assert.Equal(t, tc.wantQKC, acct.Balance)
|
|
||||||
|
|
||||||
if tc.wantMNT == nil {
|
|
||||||
assert.True(t, acct.MntBalances == nil || acct.MntBalances.IsBlank())
|
|
||||||
} else {
|
|
||||||
require.NotNil(t, acct.MntBalances)
|
|
||||||
assert.Equal(t, tc.wantMNT, acct.MntBalances.GetBalanceMap())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestStateAccountPyquarkchainEncodeCompatibility verifies that goshard
|
|
||||||
// produces byte-for-byte identical output to pyquarkchain for the same account.
|
|
||||||
// This is the strongest compatibility check: same input → same wire bytes.
|
|
||||||
func TestStateAccountPyquarkchainEncodeCompatibility(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
acct StateAccount
|
|
||||||
want []byte
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nonce=1 QKC=1000",
|
|
||||||
acct: StateAccount{
|
|
||||||
Nonce: 1,
|
|
||||||
Balance: uint256.NewInt(1000),
|
|
||||||
Root: EmptyRootHash,
|
|
||||||
CodeHash: EmptyCodeHash[:],
|
|
||||||
FullShardKey: 1,
|
|
||||||
},
|
|
||||||
want: pyqkcVecNonce1QKC1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nonce=5 QKC=2000 MNT[100]=500",
|
|
||||||
acct: StateAccount{
|
|
||||||
Nonce: 5,
|
|
||||||
Balance: uint256.NewInt(2000),
|
|
||||||
Root: EmptyRootHash,
|
|
||||||
CodeHash: EmptyCodeHash[:],
|
|
||||||
MntBalances: NewTokenBalancesWithMap(map[uint64]*uint256.Int{
|
|
||||||
100: uint256.NewInt(500),
|
|
||||||
}),
|
|
||||||
FullShardKey: 1,
|
|
||||||
},
|
|
||||||
want: pyqkcVecNonce5QKC2000MNT500,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "zero account",
|
|
||||||
acct: StateAccount{
|
|
||||||
Nonce: 0,
|
|
||||||
Balance: uint256.NewInt(0),
|
|
||||||
Root: EmptyRootHash,
|
|
||||||
CodeHash: EmptyCodeHash[:],
|
|
||||||
FullShardKey: 1,
|
|
||||||
},
|
|
||||||
want: pyqkcVecZeroAccount,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
got, err := rlp.EncodeToBytes(&tc.acct)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, hex.EncodeToString(tc.want), hex.EncodeToString(got),
|
|
||||||
"goshard encoding must match pyquarkchain byte-for-byte")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestSlimRLPRoundTripEquivalence verifies that for non-MNT accounts (no MntBalances),
|
|
||||||
// the round-trip slim-RLP → decode → QKC-encode produces the same bytes as direct
|
|
||||||
// QKC-encode. This is the invariant that allows execute.go to use slim-RLP as the
|
|
||||||
// accountOrigin format for history reversal without affecting trie root correctness.
|
|
||||||
//
|
|
||||||
// Note: SlimAccount does not carry FullShardKey. Both paths therefore encode with
|
|
||||||
// FullShardKey=0, so the test holds — but the slim-RLP path should only be used
|
|
||||||
// for accounts where FullShardKey is known to be zero (e.g. freshly created accounts
|
|
||||||
// before any shard key is set).
|
|
||||||
func TestSlimRLPRoundTripEquivalence(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
acc StateAccount
|
|
||||||
}{
|
|
||||||
{"zero balance, empty root", StateAccount{Nonce: 0, Balance: new(uint256.Int), Root: EmptyRootHash, CodeHash: EmptyCodeHash.Bytes()}},
|
|
||||||
{"nonzero balance, empty root", StateAccount{Nonce: 5, Balance: uint256.NewInt(1_000_000), Root: EmptyRootHash, CodeHash: EmptyCodeHash.Bytes()}},
|
|
||||||
{"with storage root", StateAccount{Nonce: 1, Balance: uint256.NewInt(42), Root: common.HexToHash("0xabcdef"), CodeHash: EmptyCodeHash.Bytes()}},
|
|
||||||
{"with code hash", StateAccount{Nonce: 99, Balance: uint256.NewInt(999), Root: EmptyRootHash, CodeHash: common.FromHex("0xdeadbeef" + strings.Repeat("00", 28))}},
|
|
||||||
}
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
// Path A: direct QKC encode
|
|
||||||
directQKC, err := rlp.EncodeToBytes(&tc.acc)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("direct QKC encode: %v", err)
|
|
||||||
}
|
|
||||||
// Path B: slim-RLP → FullAccount decode → QKC encode (execute.go path)
|
|
||||||
slim := SlimAccountRLP(tc.acc)
|
|
||||||
decoded, err := FullAccount(slim)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("FullAccount decode: %v", err)
|
|
||||||
}
|
|
||||||
viaSlim, err := rlp.EncodeToBytes(decoded)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("QKC encode after slim round-trip: %v", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(directQKC, viaSlim) {
|
|
||||||
t.Errorf("mismatch:\n direct QKC: %x\n via slim: %x", directQKC, viaSlim)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,200 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TokenTrieThreshold is the maximum number of non-zero token balances supported
|
|
||||||
// in list format. Accounts exceeding this limit are not supported.
|
|
||||||
//
|
|
||||||
// NOTE: goquarkchain supports a trie format (0x01 prefix + 32-byte SecureTrie root)
|
|
||||||
// for accounts with > 16 MNT tokens. That format is NOT implemented here because:
|
|
||||||
// 1. The trie format requires a SecureTrie backed by a database, which would
|
|
||||||
// introduce an import cycle (core/types → trie → core/rawdb → core/types).
|
|
||||||
// 2. No real mainnet accounts with > 16 MNT tokens have been observed in practice.
|
|
||||||
//
|
|
||||||
// If trie-format support becomes necessary, a standalone MPT implementation must
|
|
||||||
// be added (without importing the trie package) and verified byte-for-byte against
|
|
||||||
// goquarkchain's SecureTrie output.
|
|
||||||
const TokenTrieThreshold = 16
|
|
||||||
|
|
||||||
// listFormatPrefix is the prefix byte for the list serialization format (≤ 16 tokens).
|
|
||||||
const listFormatPrefix = byte(0x00)
|
|
||||||
|
|
||||||
// trieFormatPrefix is the goquarkchain trie format prefix (> 16 tokens).
|
|
||||||
// Deserialization of this format returns an error; serialization is unsupported.
|
|
||||||
const trieFormatPrefix = byte(0x01)
|
|
||||||
|
|
||||||
// DefaultTokenID is the QKC token ID (= TokenIDEncode("QKC") = 35760).
|
|
||||||
const DefaultTokenID = uint64(35760)
|
|
||||||
|
|
||||||
// TokenBalancePair is a (TokenID, Balance) pair used in list-format encoding.
|
|
||||||
type TokenBalancePair struct {
|
|
||||||
TokenID uint64
|
|
||||||
Balance *uint256.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// TokenBalances holds multi-token balances for an account.
|
|
||||||
// Serialization uses list format (0x00 prefix) for ≤ TokenTrieThreshold non-zero
|
|
||||||
// balances. Trie format (> 16 tokens) is not supported; SerializeToBytes returns
|
|
||||||
// an error if more than TokenTrieThreshold non-zero balances are present.
|
|
||||||
type TokenBalances struct {
|
|
||||||
balances map[uint64]*uint256.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEmptyTokenBalances creates an empty TokenBalances.
|
|
||||||
func NewEmptyTokenBalances() *TokenBalances {
|
|
||||||
return &TokenBalances{
|
|
||||||
balances: make(map[uint64]*uint256.Int),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTokenBalancesWithMap creates a TokenBalances from a map.
|
|
||||||
// The provided values are deep-copied.
|
|
||||||
func NewTokenBalancesWithMap(data map[uint64]*uint256.Int) *TokenBalances {
|
|
||||||
tb := NewEmptyTokenBalances()
|
|
||||||
for id, bal := range data {
|
|
||||||
if bal != nil && !bal.IsZero() {
|
|
||||||
tb.balances[id] = new(uint256.Int).Set(bal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tb
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTokenBalancesFromBytes deserializes a TokenBalances from its serialized form.
|
|
||||||
// Prefix 0x00 → list format (RLP-decoded, ≤ 16 tokens).
|
|
||||||
// Prefix 0x01 → trie format (> 16 tokens); NOT SUPPORTED — returns an error.
|
|
||||||
func NewTokenBalancesFromBytes(data []byte) (*TokenBalances, error) {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return NewEmptyTokenBalances(), nil
|
|
||||||
}
|
|
||||||
switch data[0] {
|
|
||||||
case listFormatPrefix:
|
|
||||||
var pairs []TokenBalancePair
|
|
||||||
if err := rlp.DecodeBytes(data[1:], &pairs); err != nil {
|
|
||||||
return nil, fmt.Errorf("token_balances: list decode error: %w", err)
|
|
||||||
}
|
|
||||||
tb := NewEmptyTokenBalances()
|
|
||||||
for _, p := range pairs {
|
|
||||||
if p.Balance != nil && !p.Balance.IsZero() {
|
|
||||||
tb.balances[p.TokenID] = new(uint256.Int).Set(p.Balance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tb, nil
|
|
||||||
|
|
||||||
case trieFormatPrefix:
|
|
||||||
// Trie format (> 16 MNT tokens) is not implemented.
|
|
||||||
// See TokenTrieThreshold comment for details.
|
|
||||||
return nil, fmt.Errorf("token_balances: trie format (0x01, >%d tokens) is not supported", TokenTrieThreshold)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("token_balances: unknown format prefix 0x%02x", data[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetValue sets the balance for the given tokenID.
|
|
||||||
// A nil or zero amount removes the entry.
|
|
||||||
func (t *TokenBalances) SetValue(amount *uint256.Int, tokenID uint64) {
|
|
||||||
if amount == nil || amount.IsZero() {
|
|
||||||
delete(t.balances, tokenID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.balances[tokenID] = new(uint256.Int).Set(amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTokenBalance returns the balance for the given tokenID.
|
|
||||||
// Returns a new zero uint256 if not present.
|
|
||||||
func (t *TokenBalances) GetTokenBalance(tokenID uint64) *uint256.Int {
|
|
||||||
if bal, ok := t.balances[tokenID]; ok {
|
|
||||||
return new(uint256.Int).Set(bal)
|
|
||||||
}
|
|
||||||
return new(uint256.Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBalanceMap returns a copy of the internal balance map.
|
|
||||||
func (t *TokenBalances) GetBalanceMap() map[uint64]*uint256.Int {
|
|
||||||
out := make(map[uint64]*uint256.Int, len(t.balances))
|
|
||||||
for id, bal := range t.balances {
|
|
||||||
out[id] = new(uint256.Int).Set(bal)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsBlank reports whether there are no non-zero balances.
|
|
||||||
func (t *TokenBalances) IsBlank() bool {
|
|
||||||
return len(t.balances) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of non-zero token balances.
|
|
||||||
func (t *TokenBalances) Len() int {
|
|
||||||
return len(t.balances)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy returns a deep copy of the TokenBalances.
|
|
||||||
func (t *TokenBalances) Copy() *TokenBalances {
|
|
||||||
cp := NewEmptyTokenBalances()
|
|
||||||
for id, bal := range t.balances {
|
|
||||||
cp.balances[id] = new(uint256.Int).Set(bal)
|
|
||||||
}
|
|
||||||
return cp
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeToBytes encodes TokenBalances to bytes.
|
|
||||||
//
|
|
||||||
// List format (≤ TokenTrieThreshold non-zero balances):
|
|
||||||
// - Output: 0x00 + RLP([]TokenBalancePair sorted by TokenID ascending)
|
|
||||||
//
|
|
||||||
// Trie format (> TokenTrieThreshold) is NOT supported and returns an error.
|
|
||||||
// See TokenTrieThreshold comment for details.
|
|
||||||
func (t *TokenBalances) SerializeToBytes() ([]byte, error) {
|
|
||||||
if t.Len() > TokenTrieThreshold {
|
|
||||||
// Trie format (0x01) is not implemented.
|
|
||||||
// See TokenTrieThreshold comment for details.
|
|
||||||
return nil, fmt.Errorf("token_balances: %d tokens exceeds the supported maximum of %d; trie format (0x01) is not implemented", t.Len(), TokenTrieThreshold)
|
|
||||||
}
|
|
||||||
return t.serializeListFormat()
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeListFormat encodes in list format: 0x00 + RLP(sorted pairs).
|
|
||||||
func (t *TokenBalances) serializeListFormat() ([]byte, error) {
|
|
||||||
pairs := make([]TokenBalancePair, 0, len(t.balances))
|
|
||||||
for id, bal := range t.balances {
|
|
||||||
pairs = append(pairs, TokenBalancePair{
|
|
||||||
TokenID: id,
|
|
||||||
Balance: bal,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
sort.Slice(pairs, func(i, j int) bool {
|
|
||||||
return pairs[i].TokenID < pairs[j].TokenID
|
|
||||||
})
|
|
||||||
|
|
||||||
encoded, err := rlp.EncodeToBytes(pairs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("token_balances: list encode error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out := make([]byte, 1+len(encoded))
|
|
||||||
out[0] = listFormatPrefix
|
|
||||||
copy(out[1:], encoded)
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTokenBalancesListFormat(t *testing.T) {
|
|
||||||
tb := NewEmptyTokenBalances()
|
|
||||||
tb.SetValue(uint256.NewInt(1000), DefaultTokenID)
|
|
||||||
tb.SetValue(uint256.NewInt(500), 100)
|
|
||||||
|
|
||||||
data, err := tb.SerializeToBytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, byte(0x00), data[0], "list format prefix")
|
|
||||||
|
|
||||||
tb2, err := NewTokenBalancesFromBytes(data)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, uint256.NewInt(1000), tb2.GetTokenBalance(DefaultTokenID))
|
|
||||||
assert.Equal(t, uint256.NewInt(500), tb2.GetTokenBalance(100))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTokenBalancesIsBlank(t *testing.T) {
|
|
||||||
tb := NewEmptyTokenBalances()
|
|
||||||
assert.True(t, tb.IsBlank())
|
|
||||||
tb.SetValue(uint256.NewInt(0), DefaultTokenID)
|
|
||||||
assert.True(t, tb.IsBlank(), "zero balance is blank")
|
|
||||||
tb.SetValue(uint256.NewInt(1), DefaultTokenID)
|
|
||||||
assert.False(t, tb.IsBlank())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestTokenBalancesTrieFormatUnsupported verifies that serializing more than
|
|
||||||
// TokenTrieThreshold (16) non-zero token balances returns an error, since the
|
|
||||||
// trie format (0x01) is not implemented.
|
|
||||||
func TestTokenBalancesTrieFormatUnsupported(t *testing.T) {
|
|
||||||
tb := NewEmptyTokenBalances()
|
|
||||||
for i := uint64(1); i <= 17; i++ {
|
|
||||||
tb.SetValue(uint256.NewInt(i*100), i)
|
|
||||||
}
|
|
||||||
_, err := tb.SerializeToBytes()
|
|
||||||
require.Error(t, err, "expected error for >16 token balances (trie format unsupported)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestTokenBalancesFromBytesTrieFormatUnsupported verifies that deserializing
|
|
||||||
// a 0x01-prefixed (trie format) byte slice returns an error.
|
|
||||||
func TestTokenBalancesFromBytesTrieFormatUnsupported(t *testing.T) {
|
|
||||||
data := make([]byte, 33)
|
|
||||||
data[0] = 0x01 // trie format prefix
|
|
||||||
_, err := NewTokenBalancesFromBytes(data)
|
|
||||||
require.Error(t, err, "expected error for trie format (0x01) deserialization")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTokenBalancesCopy(t *testing.T) {
|
|
||||||
tb := NewTokenBalancesWithMap(map[uint64]*uint256.Int{
|
|
||||||
DefaultTokenID: uint256.NewInt(1e18),
|
|
||||||
100: uint256.NewInt(500),
|
|
||||||
})
|
|
||||||
cp := tb.Copy()
|
|
||||||
cp.SetValue(uint256.NewInt(0), DefaultTokenID)
|
|
||||||
assert.Equal(t, uint256.NewInt(1e18), tb.GetTokenBalance(DefaultTokenID), "original unaffected")
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Uint32 uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
rlpUint32Prefix = byte(0x84) // RLP "string of length 4"
|
|
||||||
rlpUint32Len = 5
|
|
||||||
)
|
|
||||||
|
|
||||||
func (u *Uint32) GetValue() uint32 { return uint32(*u) }
|
|
||||||
|
|
||||||
func (u *Uint32) EncodeRLP(w io.Writer) error {
|
|
||||||
b := [rlpUint32Len]byte{rlpUint32Prefix}
|
|
||||||
binary.BigEndian.PutUint32(b[1:], uint32(*u))
|
|
||||||
_, err := w.Write(b[:])
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Uint32) DecodeRLP(s *rlp.Stream) error {
|
|
||||||
data, err := s.Raw()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(data) != rlpUint32Len {
|
|
||||||
return fmt.Errorf("Uint32 RLP: expected %d bytes, got %d", rlpUint32Len, len(data))
|
|
||||||
}
|
|
||||||
if data[0] != rlpUint32Prefix {
|
|
||||||
return fmt.Errorf("Uint32 RLP: expected prefix 0x%02x, got 0x%02x", rlpUint32Prefix, data[0])
|
|
||||||
}
|
|
||||||
*u = Uint32(binary.BigEndian.Uint32(data[1:]))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUint32RLP(t *testing.T) {
|
|
||||||
v := Uint32(0x12345678)
|
|
||||||
encoded, err := rlp.EncodeToBytes(&v)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Expected: 0x84 + big-endian uint32
|
|
||||||
want := []byte{0x84, 0x12, 0x34, 0x56, 0x78}
|
|
||||||
if !bytes.Equal(encoded, want) {
|
|
||||||
t.Fatalf("encode: want %x, got %x", want, encoded)
|
|
||||||
}
|
|
||||||
var v2 Uint32
|
|
||||||
if err := rlp.DecodeBytes(encoded, &v2); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if v2 != v {
|
|
||||||
t.Fatalf("decode: want %d, got %d", v, v2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,14 +27,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Genesis hashes to enforce below configs on.
|
// Genesis hashes to enforce below configs on.
|
||||||
// NOTE: These reflect the QKC (QuarkChain) 6-element account encoding which differs
|
|
||||||
// from standard Ethereum encoding. The state root and therefore block hash changes
|
|
||||||
// when the account RLP format changes.
|
|
||||||
var (
|
var (
|
||||||
MainnetGenesisHash = common.HexToHash("0x7a86192e8901d313f5ca3af78e13a660aae94173959c69a3f33e0ae5f2749d7c")
|
MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
|
||||||
HoleskyGenesisHash = common.HexToHash("0x0706dbd8f023baa1808450dd3dfce54335339247b2116c5613e178037b2ccc8f")
|
HoleskyGenesisHash = common.HexToHash("0xb5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4")
|
||||||
SepoliaGenesisHash = common.HexToHash("0xc61eb78385a53483f9de8a5710f91d0921bb4ff968ed482c8955b3a9c6d3ccaf")
|
SepoliaGenesisHash = common.HexToHash("0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9")
|
||||||
HoodiGenesisHash = common.HexToHash("0x21a5ed4cc25beb5aa3e1fe4552e2ff7bcf785aeb6cf0472e1b529bd3346c3ba3")
|
HoodiGenesisHash = common.HexToHash("0xbbe312868b376a3001692a646dd2d7d1e4406380dfd86b98aa8a34d1557c971b")
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUint64(val uint64) *uint64 { return &val }
|
func newUint64(val uint64) *uint64 { return &val }
|
||||||
|
|
@ -1413,4 +1410,3 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
|
||||||
IsEIP4762: isUBT,
|
IsEIP4762: isUBT,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue