L
ling-wallet
↓ 0 downloadsLing Wallet: a post-quantum privacy-coin wallet, full node and HyperTorus miner for LNG
Keywords wallet, cryptocurrency, lng, node, miner, ling
lingfu install ling-walletCode / Files (116)
ling/tests/chain/t_chain.ling
# End-to-end consensus + wallet test (single isolate, no network):
# mine, mature, scan, send with a real LRS-87 ring signature, mine the
# payment, double-spend rejection, reorg.
use "../../core/params.ling"
use "../../core/codec.ling"
use "../../core/money.ling"
use "../../core/chain.ling"
use "../../core/mempool.ling"
use "../../core/wallet.ling"
fn check(name, cond) {
if cond {
print("PASS ", name)
st_add("pass", 1)
} else {
print("FAIL ", name)
st_add("fail", 1)
}
return cond
}
fn mine_on(parent, view_pk, spend_pk, txs) {
bind pm = block_meta(parent).x
bind height = pm.height + 1
bind fees = 0
bind i = 0
while i < len(txs) {
bind fees = fees + list_get(txs, i).fee
bind i = i + 1
}
bind cb = coinbase_tx(height, coinbase_split(block_reward(pm.generated) + fees), view_pk, spend_pk)
bind all = [cb]
bind i = 0
while i < len(txs) {
bind all = list_push(all, list_get(txs, i))
bind i = i + 1
}
bind ts = GENESIS_TIMESTAMP + height * TARGET_BLOCK_SECS
bind b = Block(Header(BLOCK_VERSION, NET_ID, height, parent, ts, "", 0), all)
bind b = Block(with_merkle(b.header, block_merkle(b)), all)
return [b, accept_block(b, ts)]
}
fn mine(view_pk, spend_pk, txs) {
bind r = mine_on(chain_tip().id, view_pk, spend_pk, txs)
bind res = list_get(r, 1)
bind what = match res {
ok(v) => list_get(v, 0),
bad(e) => "bad: " + e,
}
if what != "extended" {
print("mine failed: ", what)
}
return list_get(r, 0)
}
bind start = do {
st_set("test_genesis_difficulty", "1")
bind dir = env_get("TEMP", ".") + "/lwtest-chain-" + lw_random(4)
make_dir(dir)
chain_open(dir + "/chain.db")
check("genesis present", chain_tip().height == 0)
# Two wallets in one isolate: derive Bob's publics, then load Alice.
keys_load(lw_xof("test/bob", [], 32))
bind bob_view = st_get("k_view_pk", "")
bind bob_spend = st_get("k_spend_pk", "")
bind bob_bundle = bundle_parse(st_get("k_bundle", "")).x
keys_drop()
keys_load(lw_xof("test/alice", [], 32))
bind a_view = st_get("k_view_pk", "")
bind a_spend = st_get("k_spend_pk", "")
check("bundle verifies and roundtrips", bundle_parse(st_get("k_bundle", "")).x.hash == bob_bundle.hash == false)
check("short address parses", parse_short_address(short_address(bob_bundle.hash)).x == bob_bundle.hash)
bind t0 = epoch_now()
bind n = 0
while n < 64 {
mine(a_view, a_spend, [])
bind n = n + 1
}
print("mined 64 blocks in ", floor(epoch_now() - t0), " s")
check("tip at 64", chain_tip().height == 64)
scan_reset()
scan_sync(1000)
bind bal = balance(64)
check("matured coinbase spendable", list_get(bal, 0) > 0)
check("young coinbases pending", list_get(bal, 1) > 0)
# Alice pays Bob 3 LNG.
bind amount = 3 * COIN
bind plan = plan_send(64, amount).x
bind built = build_tx(plan, bob_bundle, amount, "candy order #1", 64)
bind out = match built {
ok(v) => v,
bad(e) => (),
}
check("tx built and signed", out != ())
bind tx = list_get(out, 0)
check("bootstrap ring flagged", list_get(out, 2))
check("encoding roundtrips", tx_id(dec_tx(enc_tx(tx)).x) == tx_id(tx))
check("pool accepts", pool_add(tx, 0) == "added")
check("pool dedupes", pool_add(tx, 0) == "have")
bind fork_parent = chain_tip().id
mine(a_view, a_spend, pool_select(65536))
pool_on_new_tip([], 0)
check("mined tx leaves the pool", pool_len() == 0)
# Double spend: same key images now spent.
bind dbl = validate_tx(tx, chain_tip().height + 1, "dbl")
bind dbl_err = match dbl {
ok(v) => "",
bad(e) => e,
}
check("double spend rejected", dbl_err == "key image already spent")
scan_sync(1000)
check("alice sees her spend", st_llen("spend_txs") == 1)
# Bob scans and sees exactly the payment with its memo.
keys_drop()
keys_load(lw_xof("test/bob", [], 32))
scan_reset()
scan_sync(1000)
bind total = 0
bind memo_ok = false
bind i = 0
while i < st_llen("owned") {
bind o = st_lget("owned", i)
bind total = total + list_get(o, 0)
if list_get(o, 10) == "candy order #1" {
bind memo_ok = true
}
bind i = i + 1
}
check("bob received exactly 3 LNG", total == amount)
check("bob decrypted the memo", memo_ok)
# Reorg: a longer branch from before the payment wins.
bind b1 = list_get(mine_on(fork_parent, bob_view, bob_spend, []), 0)
bind r1 = list_get(mine_on(fork_parent, bob_view, bob_spend, []), 1)
bind r2 = mine_on(block_id(b1.header), bob_view, bob_spend, [])
bind what = match list_get(r2, 1) {
ok(v) => list_get(v, 0),
bad(e) => "bad: " + e,
}
check("longer branch reorgs", what == "reorg")
pool_on_new_tip(list_get(list_get(list_get(r2, 1).x, 2), 0).txs, 0)
check("payment back in the pool", pool_has(tx_id(tx)))
scan_sync(1000)
bind still = false
bind i = 0
while i < st_llen("owned") {
if list_get(st_lget("owned", i), 10) == "candy order #1" {
bind still = true
}
bind i = i + 1
}
check("bob's payment unconfirmed after reorg", still == false)
print("")
print(format("{} passed, {} failed", st_get("pass", 0), st_get("fail", 0)))
}