灵符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/core/mempool.ling
# mempool.ling — validated, unconfirmed transactions (DESIGN §7.6), kept in
# SQLite tables owned by the node isolate.

use "params.ling"
use "codec.ling"
use "chain.ling"

fn pool_len() {
    return list_get(db_query("SELECT COUNT(*) AS n FROM mempool"), 0).n
}

fn pool_has(txid) {
    return len(db_query("SELECT 1 AS x FROM mempool WHERE txid = ?1", txid)) > 0
}

fn pool_remove(txid) {
    db_exec("DELETE FROM mempool WHERE txid = ?1", txid)
    db_exec("DELETE FROM mempool_ki WHERE txid = ?1", txid)
    return 0
}

# → "added" | "have" | "rejected: <reason>"
fn pool_add(tx, now) {
    bind txid = tx_id(tx)
    if pool_has(txid) {
        return "have"
    }
    bind next_height = chain_tip().height + 1
    bind r = validate_tx(tx, next_height, format("pool{}", lw_random(8)))
    bind err = match r {
        ok(v) => "",
        bad(e) => e,
    }
    if err != "" {
        return "rejected: " + err
    }
    bind size = tx_size(tx)
    bind rate = tx.fee / size
    # Key-image conflicts: the higher fee rate wins.
    bind conflicts = []
    bind i = 0
    while i < len(tx.inputs) {
        bind rows = db_query("SELECT m.txid AS txid, p.fee_rate AS rate FROM mempool_ki m JOIN mempool p ON p.txid = m.txid WHERE m.ki = ?1", ki_id(list_get(tx.inputs, i).key_image))
        if len(rows) > 0 {
            bind c = list_get(rows, 0)
            if c.rate >= rate {
                return "rejected: conflicts with a pooled transaction paying more"
            }
            bind conflicts = list_push(conflicts, c.txid)
        }
        bind i = i + 1
    }
    bind i = 0
    while i < len(conflicts) {
        pool_remove(list_get(conflicts, i))
        bind i = i + 1
    }
    db_exec("INSERT INTO mempool (txid, data, fee_rate, size, added) VALUES (?1, ?2, ?3, ?4, ?5)", txid, enc_tx(tx), rate, size, now)
    bind i = 0
    while i < len(tx.inputs) {
        db_exec("INSERT OR REPLACE INTO mempool_ki (ki, txid) VALUES (?1, ?2)", ki_id(list_get(tx.inputs, i).key_image), txid)
        bind i = i + 1
    }
    return "added"
}

# After the tip moves: revalidate everything (drops mined, conflicting and
# expired txs) and re-add txs from blocks a reorg removed.
fn pool_on_new_tip(returned_txs, now) {
    bind rows = db_query("SELECT txid, data, added FROM mempool")
    db_exec("DELETE FROM mempool")
    db_exec("DELETE FROM mempool_ki")
    bind i = 0
    while i < len(returned_txs) {
        bind tx = list_get(returned_txs, i)
        if tx.coinbase == false {
            pool_add(tx, now)
        }
        bind i = i + 1
    }
    bind i = 0
    while i < len(rows) {
        bind r = list_get(rows, i)
        if now - r.added <= MEMPOOL_EXPIRY_SECS {
            match dec_tx(r.data) {
                ok(tx) => pool_add(tx, r.added),
                bad(e) => 0,
            }
        }
        bind i = i + 1
    }
    return 0
}

fn pool_expire(now) {
    db_exec("DELETE FROM mempool_ki WHERE txid IN (SELECT txid FROM mempool WHERE added < ?1)", now - MEMPOOL_EXPIRY_SECS)
    db_exec("DELETE FROM mempool WHERE added < ?1", now - MEMPOOL_EXPIRY_SECS)
    return 0
}

# Sum of the fees of every transaction in the pool (an upper bound on the fees any one block can collect).
fn pool_total_fees() {
    bind rows = db_query("SELECT fee_rate, size FROM mempool")
    bind total = 0
    bind i = 0
    while i < len(rows) {
        bind r = list_get(rows, i)
        bind total = total + round(r.fee_rate * r.size)
        bind i = i + 1
    }
    return total
}

# Best fee-rate txs that fit next to the coinbase (pool is conflict-free).
fn pool_select(reserve_bytes) {
    bind rows = db_query("SELECT data, size FROM mempool ORDER BY fee_rate DESC")
    bind budget = MAX_BLOCK_BYTES - reserve_bytes - 1024
    bind out = []
    bind i = 0
    while i < len(rows) {
        bind r = list_get(rows, i)
        if r.size <= budget {
            match dec_tx(r.data) {
                ok(tx) => st_set("pool_sel_tmp", tx),
                bad(e) => st_set("pool_sel_tmp", ()),
            }
            if st_get("pool_sel_tmp", ()) != () {
                bind out = list_push(out, st_get("pool_sel_tmp", ()))
                bind budget = budget - r.size
            }
        }
        bind i = i + 1
    }
    return out
}

← Back to index