go-ethereum/qkc/account/account_test.go
blockchaindevsh 4160ab81ee
qkc: port QuarkChain helper packages (account, common, params, config) (#16)
* qkc/{common,account,params,config}: port QuarkChain helper packages

- account: Branch (fullShardId codec), Address, Identity, keystore Account
  (pborman/uuid -> google/uuid); python-generated golden testdata, sampled
  to 1600 Branch vectors (all 16 shard sizes); golden keystore files
  proving on-disk load compatibility across the uuid swap.
- common (+hexutil): IsP2/IntLeftMostBit/token-id codec and the QuarkChain
  hexutil fork (lenient leading zeros) the config JSON depends on, with
  goquarkchain's modified tests locking in the lenient behaviour.
- params: QKC EVM constants and DefaultConstantinople.
- config: QuarkChainConfig/ChainConfig/ShardConfig/RootConfig (passive),
  JSON-identical; verified against goquarkchain's
  cluster_config_template.json (its derivative of the python testnet
  template).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* qkc/params: remove unused MainnetBootnodes (master's concern, not the slave's)

bootnodes.go only defined an empty, unused MainnetBootnodes var (no references
anywhere in the tree). In QuarkChain's master/slave architecture, public-network
P2P peer discovery is handled by the master; a slave connects to its master and
has no use for a bootnode list, so it does not belong in the shard params.

Addresses review feedback from @qzhodl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* qkc/params: set goshard version to 1.0.0

The ported version.go carried goquarkchain's 1.9.0; goshard should have its own
version number, which we'll bump by phase going forward. Start it at 1.0.0.

Addresses review feedback from @qzhodl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* qkc/params: set PetersburgBlock in DefaultConstantinople

goquarkchain doesn't set PetersburgBlock, but its forked gasSStore hard-codes
the legacy (pre-EIP-1283) SSTORE metering via an `if true` (the EIP-1283 net-
metering path is left as dead code) — so its EVM already runs Petersburg
behavior. Stock geth gates that revert on PetersburgBlock, so set it to 0
alongside Constantinople to reproduce goquarkchain's EVM faithfully.

No effect until execution lands (deferred); this fixes the target fork config.

Addresses review feedback from @qzhodl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* qkc/params: drop non-versioning constants from version.go

NEW_TRANSACTION_LIST_LIMIT and TPS_Num are tx-pool/throughput constants that
goquarkchain happened to keep in version.go; they're unused here and unrelated
to versioning. Remove them so version.go holds only version info. They'll be
reintroduced where they belong when the tx-pool/execution work lands.

Addresses review feedback from @qzhodl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* qkc/account: remove empty keystore/ placeholder folder

qkc/account/keystore/ held only a .gitignore (*.json) — an empty folder carried
over verbatim from goquarkchain's layout. Nothing references it, and runtime
keystores live under the datadir, not the source tree. Removing the .gitignore
drops the empty dir.

Addresses review feedback from @qzhodl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* qkc/config: match pyquarkchain's WEBSOCKET_JSON_RPC_PORT (single optional port)

slave_config carried goquarkchain's WEBSOCKET_JSON_RPC_PORT_LIST ([]uint16, with
a per-shard DefaultWSPort+offset derivation), but the goshard slave reads a
pyquarkchain master's cluster config, where SlaveConfig has a single optional
WEBSOCKET_JSON_RPC_PORT (default None; the WS server only starts when it's set).
Switch to WSPort *uint16 (nil = None) and drop the goquarkchain per-shard
derivation plus the now-unused DefaultWSPort.

Addresses review feedback from @qzhodl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* qkc/config: drop goquarkchain-only GRPCHost/GRPCPort

gRPC is goquarkchain's master<->slave transport and has no pyquarkchain
counterpart (cluster_config.py has no gRPC), so it isn't needed for faithful
pyquarkchain config compatibility. Both fields were json:"-" (invisible to the
JSON round-trip / byte-compat test) and were never read anywhere under qkc/
(set in constructors only). Remove GRPCHost/GRPCPort from RootConfig and
QuarkChainConfig, their constructor assignments, and the DefaultGrpcPort const.

common.GetIPV4Addr is kept as a general util (likely needed for the slave's own
HOST auto-detection later).

Addresses review feedback from @qzhodl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* qkc/config: round-trip DEFAULT_CHAIN_TOKEN (was json:"-")

ChainConfig.DefaultChainToken was tagged json:"-" (faithfully ported from
goquarkchain), so DEFAULT_CHAIN_TOKEN was silently dropped on marshal/unmarshal.
pyquarkchain's ChainConfig has DEFAULT_CHAIN_TOKEN ("QKC") as a real config
field, so losing it isn't pyquarkchain-faithful. Tag it DEFAULT_CHAIN_TOKEN so
it round-trips (the custom Marshal/UnmarshalJSON embed ChainConfigAlias, which
inherits the tag).

Addresses review feedback from @syntrust.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* qkc/config: reject missing CHAIN_ID=0 in chain-id validation

initAndValidate checked chain ids with i != chainIDMap[i], but chainIDMap is
built chainID->chainID, so a missing key returns the zero value 0. For i=0 that
collides with the expected value (0 != 0 is false), so a config missing
CHAIN_ID=0 silently passed validation. Check key presence instead
(_, ok := chainIDMap[i]; !ok), which correctly rejects any missing chain id,
including 0.

Addresses review feedback from @syntrust.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* qkc/config: drop inverted dead comparisons in TestLoadClusterConfig

The per-chain Genesis/PoswConfig/ConsensusConfig comparisons used
if reflect.DeepEqual(go, py) { Fatalf } -- inverted (missing !), so they only
fire when the values are equal and otherwise validate nothing. The bug exists
upstream too (goquarkchain cluster/config/config_test.go:142-150) and was
carried over by the verbatim port.

They cannot be fixed by adding !: the two fixtures (test_config.json and the
python cluster_config_template.json) deliberately differ on those value fields
(difficulty, block time, extra data, stake), so an equality assertion would
always fail. The test's meaningful purpose is to verify the Go and Python
configs describe the same cluster topology, so drop the three broken checks and
keep the slave-list / chain-count / per-chain CHAIN_ID+SHARD_SIZE assertions.

Addresses review feedback from @syntrust.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* qkc/config: derive per-shard coinbase and GENESIS.ALLOC like pyquarkchain

QuarkChainConfig.UnmarshalJSON copied the chain coinbase and the full
GENESIS.ALLOC to every shard verbatim (goquarkchain's behavior). pyquarkchain's
ClusterConfig.from_dict instead derives each shard config by rewriting the
coinbase into the shard via address_in_shard(full_shard_id), and filtering
GENESIS.ALLOC to the addresses whose shard bits match the shard
(full_shard_key & (SHARD_SIZE-1) == shard_id).

The goshard slave shares its config with a pyquarkchain master, so the per-shard
config must be derived exactly as pyquarkchain derives it.

TestShardConfigDerivation verifies both against test_config.json: every shard's
coinbase full-shard-key equals its full shard id, and chain 2's three
allocations (all with shard bits 0) land on shard 0 with shard 1 filtered empty.

Addresses review feedback from @syntrust.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* qkc/config: fix RootSignerPrivateKey json:"_" typo that leaked the key

RootSignerPrivateKey ([]byte) was tagged json:"_", which Go treats as a literal
field name "_", not "ignore" (only json:"-" ignores). The field is excluded
from the alias and re-emitted as hex by the jsonConfig wrapper
(ROOT_SIGNER_PRIVATE_KEY), like the sibling GuardianPublicKey which correctly uses
json:"-". As written, MarshalJSON emitted both ROOT_SIGNER_PRIVATE_KEY and a
spurious "_":"<base64>" key exposing the raw private-key bytes. Use json:"-"
to match GuardianPublicKey and pyquarkchain (which emits only
ROOT_SIGNER_PRIVATE_KEY). goquarkchain has the same typo at
cluster/config/cluster_config.go:149.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 20:17:40 +08:00

105 lines
2.5 KiB
Go

// Ported verbatim from github.com/QuarkChain/goquarkchain/account (byte-compatible).
package account
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
)
type AccountTestStruct struct {
TKey string `json:"tKey"`
Address string `json:"address"`
PrivateKey string `json:"privateKey"`
UUID string `json:"uuid"`
}
// get abs file path
func GetFilesAndDirs(dirPth string) (files []string, err error) {
dir, err := ioutil.ReadDir(dirPth)
if err != nil { //read dir err
return nil, err
}
PthSep := string(os.PathSeparator)
for _, fi := range dir {
if fi.IsDir() {
continue
} else {
ok := strings.HasSuffix(fi.Name(), ".json")
if ok {
files = append(files, dirPth+PthSep+fi.Name()) // is keystore file
}
}
}
return files, nil
}
func CheckAccountUnitTest(data AccountTestStruct, pathAll []string) bool {
accountPath := ""
for _, v := range pathAll { //find keystore file depend on uuid
if strings.Contains(v, data.UUID) {
accountPath = v
}
}
if accountPath == "" { //can not find file
fmt.Println("can not find path")
return false
}
account, err := Load(accountPath, data.TKey)
if err != nil { //load err
fmt.Println("Load err", err)
return false
}
address := account.Address()
if "0x"+data.Address != address { //address is not match
fmt.Printf("address is not match unexcepted %s,excepted %s\n", data.Address, address)
return false
}
if data.UUID != account.UUID().String() { //uuid is not match
fmt.Println("uuid is not match")
return false
}
if data.PrivateKey != account.PrivateKey() { //privateKey is not match
fmt.Println("privateKey is not match")
return false
}
return true
}
// 1.python generate keystore and it's value
// 2.go.exe test it
func TestAccount(t *testing.T) {
files, err := GetFilesAndDirs("./testdata/keystore/") //read test keystore file
JSONParse := NewJSONStruct()
data := []AccountTestStruct{}
err = JSONParse.Load("./testdata/testAccount.json", &data) //analysis test data
if err != nil {
panic(err)
}
count := 0
for _, v := range data {
err := CheckAccountUnitTest(v, files) //unit test
if err == false {
panic(-1)
}
count++
}
fmt.Println("TestAccount:success test num:", count)
}
// 1.dump file
// 2.use python to load and check it's value
func TestDump(t *testing.T) {
account, err := NewAccountWithoutKey()
fmt.Println("err", err)
fmt.Println("id", account.ID.String())
fmt.Println("Private", account.PrivateKey())
fmt.Println("Address", account.Address())
_, err = account.Dump("test_password", true, true, "")
fmt.Println("dump err", err)
}