灵符lingfu
L

ling-wallet

↓ 0 downloads

Ling Wallet: a post-quantum privacy-coin wallet, full node and HyperTorus miner for LNG

Windows · x86-64 ✕Linux · x86-64 ✕LingOS · x86-64LingOS · Pi 5 ARM64 ✕
Keywords wallet, cryptocurrency, lng, node, miner, ling
lingfu install ling-wallet
LicenseMIT
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / 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)))
}

← Back to index