Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions src/libs/blockchain/mempool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@
import { isForkActive } from "@/forks"
import { GCRMain } from "@/model/entities/GCRv2/GCR_Main"

/**
* System relay transaction types: node-generated txs that carry no
* balance edits and no `nonce` GCR edit, so they never advance the
* sender's account.nonce. Their `content.nonce` is monotonic-for-
* uniqueness (see L2PSBatchAggregator.getNextBatchNonce), NOT a
* sequential per-account counter, so the value-transfer nonce TOCTOU
* check in `addTransaction` must not apply to them. Admission is still
* gated on the tx originating from THIS node's own identity (see
* `addTransaction`) so an arbitrary signer cannot label a tx with one
* of these types to bypass the per-account nonce throttle.
*/
const SYSTEM_RELAY_TX_TYPES = new Set<string>(["l2psBatch"])

export default class Mempool {
public static repo: Repository<MempoolTx> = null
public static async init() {
Expand Down Expand Up @@ -141,7 +154,7 @@
return await this.repo.findOne({ where: { hash: ILike(hash) } })
}

public static async addTransaction(

Check failure on line 157 in src/libs/blockchain/mempool.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 17 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZ7XBPG2eb9OVCgZ8bWd&open=AZ7XBPG2eb9OVCgZ8bWd&pullRequest=945
transaction: Transaction & { reference_block: number },
blockRef?: number,
) {
Expand Down Expand Up @@ -191,8 +204,9 @@
// re-check fails, return error without inserting.
//
// Skip when not native, when no sender (genesis path), when
// fork inactive, or when the tx doesn't carry a nonce —
// those paths preserve the legacy behaviour bit-identically.
// fork inactive, when the tx doesn't carry a nonce, or when the
// tx is a system relay type (see below) — those paths preserve
// the legacy behaviour bit-identically.
const senderFromRaw = transaction.content?.from
const senderFrom =
typeof senderFromRaw === "string"
Expand All @@ -201,9 +215,37 @@
const txNonce = transaction.content?.nonce
const blockHeight = getSharedState.lastBlockNumber ?? 0

// System relay transactions (SYSTEM_RELAY_TX_TYPES, e.g.
// `l2psBatch`) carry a node-generated monotonic-for-uniqueness
// nonce, not a sequential per-account counter, and never advance
// the sender's account.nonce (no `nonce` GCR edit). The
// sequential `account.nonce + 1 + pendingCount` check below is
// built for value-transfer txs and would reject every batch (the
// timestamp nonce never equals account.nonce+1), trapping the
// L2PSBatchAggregator in a permanent retry loop.
//
// The exemption is gated on the tx originating from THIS node's
// OWN identity. The aggregator only ever submits batch txs from
// the node's own keypair, via a direct local addTransaction call;
// legitimate batch txs reach other nodes inside a block, not via
// mempool admission. Gating on own-identity means an arbitrary
// signer (or a remote peer) cannot self-label a tx `l2psBatch`
// to skip the per-account nonce throttle and flood the mempool —
// their `from` won't match this node's identity and they stay on
// the enforced path. Replay safety for the node's own batches
// comes from the in-mempool hash dedup above, not the nonce.
const ownIdentityHex =
getSharedState.publicKeyHex?.toLowerCase() ?? null
const isOwnSystemRelayTx =
typeof transaction.content?.type === "string" &&
SYSTEM_RELAY_TX_TYPES.has(transaction.content.type) &&
ownIdentityHex !== null &&
senderFrom === ownIdentityHex

if (
senderFrom &&
typeof txNonce === "number" &&
!isOwnSystemRelayTx &&
isForkActive("nonceEnforcement", blockHeight)
) {
try {
Expand Down
Loading