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