-
Notifications
You must be signed in to change notification settings - Fork 280
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: txpool persistence #3672
feat: txpool persistence #3672
Changes from 5 commits
5614ca4
48b1cb8
6dc221c
56912fb
d2be1d4
73f9e5d
c7513b1
2aeabe7
b1e5333
e74ee49
7fe44b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,9 @@ | |
}, | ||
{ | ||
"path": "../world-state" | ||
}, | ||
{ | ||
"path": "../kv-store" | ||
} | ||
], | ||
"include": ["src"] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,14 @@ | ||
import { createDebugLogger } from '@aztec/foundation/log'; | ||
import { L2Block, L2BlockContext, L2BlockDownloader, L2BlockSource, Tx, TxHash } from '@aztec/types'; | ||
import { AztecKVStore, AztecSingleton } from '@aztec/kv-store'; | ||
import { | ||
INITIAL_L2_BLOCK_NUM, | ||
L2Block, | ||
L2BlockContext, | ||
L2BlockDownloader, | ||
L2BlockSource, | ||
Tx, | ||
TxHash, | ||
} from '@aztec/types'; | ||
|
||
import { getP2PConfigEnvVars } from '../config.js'; | ||
import { P2PService } from '../service/service.js'; | ||
|
@@ -102,31 +111,30 @@ export class P2PClient implements P2P { | |
*/ | ||
private runningPromise!: Promise<void>; | ||
|
||
/** | ||
* Store the ID of the latest block the client has synced to. | ||
*/ | ||
private currentL2BlockNum = 0; | ||
|
||
private currentState = P2PClientState.IDLE; | ||
private syncPromise = Promise.resolve(); | ||
private latestBlockNumberAtStart = -1; | ||
private syncResolve?: () => void = undefined; | ||
private synchedBlockNumber: AztecSingleton<number>; | ||
|
||
/** | ||
* In-memory P2P client constructor. | ||
* @param store - The client's instance of the KV store. | ||
* @param l2BlockSource - P2P client's source for fetching existing blocks. | ||
* @param txPool - The client's instance of a transaction pool. Defaults to in-memory implementation. | ||
* @param p2pService - The concrete instance of p2p networking to use. | ||
* @param log - A logger. | ||
*/ | ||
constructor( | ||
store: AztecKVStore, | ||
private l2BlockSource: L2BlockSource, | ||
private txPool: TxPool, | ||
private p2pService: P2PService, | ||
private log = createDebugLogger('aztec:p2p'), | ||
) { | ||
const { p2pBlockCheckIntervalMS: checkInterval, l2QueueSize } = getP2PConfigEnvVars(); | ||
this.blockDownloader = new L2BlockDownloader(l2BlockSource, l2QueueSize, checkInterval); | ||
this.synchedBlockNumber = store.createSingleton('p2p_pool_last_l2_block'); | ||
} | ||
|
||
/** | ||
|
@@ -144,7 +152,7 @@ export class P2PClient implements P2P { | |
// get the current latest block number | ||
this.latestBlockNumberAtStart = await this.l2BlockSource.getBlockNumber(); | ||
|
||
const blockToDownloadFrom = this.currentL2BlockNum + 1; | ||
const blockToDownloadFrom = this.getSyncedBlockNum() + 1; | ||
|
||
// if there are blocks to be retrieved, go to a synching state | ||
if (blockToDownloadFrom <= this.latestBlockNumberAtStart) { | ||
|
@@ -161,6 +169,9 @@ export class P2PClient implements P2P { | |
this.log(`Next block ${blockToDownloadFrom} already beyond latest block at ${this.latestBlockNumberAtStart}`); | ||
} | ||
|
||
// publish any txs in TxPool after its doing initial sync | ||
this.syncPromise = this.syncPromise.then(() => this.publishStoredTxs()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to do this? Transactions are propagated as soon as they are received. Do they need to be re-published when we start up? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use case I was thinking of: we're the only P2P client on the network, we get a bunch of transactions submitted to us but there's no one on the network to transmit them to. We shut down and come back some time later, so we broadcast any transactions stored locally. WDYT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's fine for now. We can re-visit later. |
||
|
||
// start looking for further blocks | ||
const blockProcess = async () => { | ||
while (!this.stopping) { | ||
|
@@ -171,6 +182,7 @@ export class P2PClient implements P2P { | |
this.runningPromise = blockProcess(); | ||
this.blockDownloader.start(blockToDownloadFrom); | ||
this.log(`Started block downloader from block ${blockToDownloadFrom}`); | ||
|
||
return this.syncPromise; | ||
} | ||
|
||
|
@@ -229,7 +241,7 @@ export class P2PClient implements P2P { | |
if (!ready) { | ||
throw new Error('P2P client not ready'); | ||
} | ||
this.txPool.deleteTxs(txHashes); | ||
await this.txPool.deleteTxs(txHashes); | ||
} | ||
|
||
/** | ||
|
@@ -245,7 +257,7 @@ export class P2PClient implements P2P { | |
* @returns Block number of latest L2 Block we've synced with. | ||
*/ | ||
public getSyncedBlockNum() { | ||
return this.currentL2BlockNum; | ||
return this.synchedBlockNumber.get() ?? INITIAL_L2_BLOCK_NUM - 1; | ||
} | ||
|
||
/** | ||
|
@@ -255,7 +267,7 @@ export class P2PClient implements P2P { | |
public getStatus(): Promise<P2PSyncState> { | ||
return Promise.resolve({ | ||
state: this.currentState, | ||
syncedToL2Block: this.currentL2BlockNum, | ||
syncedToL2Block: this.getSyncedBlockNum(), | ||
} as P2PSyncState); | ||
} | ||
|
||
|
@@ -264,14 +276,13 @@ export class P2PClient implements P2P { | |
* @param blocks - A list of existing blocks with txs that the P2P client needs to ensure the tx pool is reconciled with. | ||
* @returns Empty promise. | ||
*/ | ||
private reconcileTxPool(blocks: L2Block[]): Promise<void> { | ||
private async reconcileTxPool(blocks: L2Block[]): Promise<void> { | ||
for (let i = 0; i < blocks.length; i++) { | ||
const blockContext = new L2BlockContext(blocks[i]); | ||
const txHashes = blockContext.getTxHashes(); | ||
this.txPool.deleteTxs(txHashes); | ||
await this.txPool.deleteTxs(txHashes); | ||
this.p2pService.settledTxs(txHashes); | ||
} | ||
return Promise.resolve(); | ||
} | ||
|
||
/** | ||
|
@@ -284,9 +295,11 @@ export class P2PClient implements P2P { | |
return Promise.resolve(); | ||
} | ||
await this.reconcileTxPool(blocks); | ||
this.currentL2BlockNum = blocks[blocks.length - 1].number; | ||
this.log(`Synched to block ${this.currentL2BlockNum}`); | ||
if (this.currentState === P2PClientState.SYNCHING && this.currentL2BlockNum >= this.latestBlockNumberAtStart) { | ||
const lastBlockNum = blocks[blocks.length - 1].number; | ||
await this.synchedBlockNumber.set(lastBlockNum); | ||
this.log(`Synched to block ${lastBlockNum}`); | ||
|
||
if (this.currentState === P2PClientState.SYNCHING && lastBlockNum >= this.latestBlockNumberAtStart) { | ||
this.setCurrentState(P2PClientState.RUNNING); | ||
if (this.syncResolve !== undefined) { | ||
this.syncResolve(); | ||
|
@@ -303,4 +316,16 @@ export class P2PClient implements P2P { | |
this.currentState = newState; | ||
this.log(`Moved to state ${P2PClientState[this.currentState]}`); | ||
} | ||
|
||
private async publishStoredTxs() { | ||
if (!this.isReady()) { | ||
return; | ||
} | ||
|
||
const txs = this.txPool.getAllTxs(); | ||
if (txs.length > 0) { | ||
this.log(`Publishing ${txs.length} previously stored txs`); | ||
await Promise.all(txs.map(tx => this.p2pService.propagateTx(tx))); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { EthAddress } from '@aztec/circuits.js'; | ||
import { AztecLmdbStore } from '@aztec/kv-store'; | ||
|
||
import { AztecKVTxPool } from './aztec_kv_tx_pool.js'; | ||
import { describeTxPool } from './tx_pool_test_suite.js'; | ||
|
||
describe('In-Memory TX pool', () => { | ||
let txPool: AztecKVTxPool; | ||
beforeEach(async () => { | ||
txPool = new AztecKVTxPool(await AztecLmdbStore.create(EthAddress.random())); | ||
}); | ||
|
||
describeTxPool(() => txPool); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🥲 the new TxPool implementation does not guarantee that it returns transactions in insertion order 🥲