Skip to content

Commit

Permalink
fix(prover): Decouple core/ prover database management (#1029)
Browse files Browse the repository at this point in the history
This is PR 1 out of many PRs to come. In first step, prover database
needs to be managed by code. The changes adds a new DAL/ migration for
prover that initializes the database with all the needed tables (as
observed in production).

Additional changes are made to `zk` to not break local development. Note
that from this point forward, development will be done on 2 databases
(as opposed to 1 database model). Alongside these changes, stage env
will be moved to dual database model.

This has been long overdue (8+ months) after core and prover has been
split from infra side during an outage. This is the continuation (and
with following PRs, finalization) of said effort.

Note that whilst there's no automated proper testing of the typescript
code modified, I've reran and tested working condition locally manually
multiple times. I expect no breakage, but can't guarantee it.
  • Loading branch information
EmilLuta authored Feb 13, 2024
1 parent c9a02cc commit 37674fd
Show file tree
Hide file tree
Showing 13 changed files with 549 additions and 56 deletions.
2 changes: 2 additions & 0 deletions etc/env/base/private.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
# Values provided here are valid for the development infrastructure only.

database_url="postgres://postgres@localhost/zksync_local"
database_prover_url="postgres://postgres@localhost/prover_local"
test_database_url="postgres://postgres@localhost:5433/zksync_local_test"
test_database_prover_url="postgres://postgres@localhost:5433/prover_local_test"

[eth_sender.sender]
# Set in env file for development, production, staging and testnet.
Expand Down
2 changes: 2 additions & 0 deletions etc/env/docker.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
database_url = "postgres://postgres@postgres/zksync_local"
database_prover_url = "postgres://postgres@postgres/prover_local"
test_database_url = "postgres://postgres@host:5433/zksync_local_test"
test_database_prover_url="postgres://postgres@host:5433/prover_local_test"

# for loadtest
l1_rpc_address = "http://geth:8545"
Expand Down
243 changes: 194 additions & 49 deletions infrastructure/zk/src/database.ts
Original file line number Diff line number Diff line change
@@ -1,101 +1,246 @@
import { Command } from 'commander';
import * as utils from './utils';

export async function reset() {
export async function reset(opts: DbOpts) {
await utils.confirmAction();
await wait();
await drop();
await setup();
await wait(opts);
await drop(opts);
await setup(opts);
}

export async function resetTest() {
process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
await utils.confirmAction();
enum DalPath {
CoreDal = 'core/lib/dal',
ProverDal = 'prover/prover_dal'
}

export interface DbOpts {
server: boolean;
prover: boolean;
}

function getDals(opts: DbOpts): Map<DalPath, string> {
let dals = new Map<DalPath, string>();
if (!opts.prover && !opts.server) {
dals.set(DalPath.CoreDal, process.env.DATABASE_URL!);
dals.set(DalPath.ProverDal, process.env.DATABASE_PROVER_URL!);
}
if (opts.prover) {
dals.set(DalPath.ProverDal, process.env.DATABASE_PROVER_URL!);
}
if (opts.server) {
dals.set(DalPath.CoreDal, process.env.DATABASE_URL!);
}
return dals;
}

function getTestDals(opts: DbOpts): Map<DalPath, string> {
let dals = new Map<DalPath, string>();
if (!opts.prover && !opts.server) {
dals.set(DalPath.CoreDal, process.env.TEST_DATABASE_URL!);
dals.set(DalPath.ProverDal, process.env.TEST_DATABASE_PROVER_URL!);
}
if (opts.prover) {
dals.set(DalPath.ProverDal, process.env.TEST_DATABASE_PROVER_URL!);
}
if (opts.server) {
dals.set(DalPath.CoreDal, process.env.TEST_DATABASE_URL!);
}
return dals;
}

async function resetTestDal(dalPath: DalPath, dbUrl: string) {
console.log('recreating postgres container for unit tests');
await utils.spawn('docker compose -f docker-compose-unit-tests.yml down');
await utils.spawn('docker compose -f docker-compose-unit-tests.yml up -d');
await wait(100);
await waitForDal(dbUrl, 100);
console.log('setting up a database template');
await setup();
await setupForDal(dalPath, dbUrl);
console.log('disallowing connections to the template');
await utils.spawn(
`psql "${process.env.DATABASE_URL}" -c "update pg_database set datallowconn = false where datname = current_database()"`
`psql "${dbUrl}" -c "update pg_database set datallowconn = false where datname = current_database()"`
);
}

export async function drop() {
export async function resetTest(opts: DbOpts) {
await utils.confirmAction();
console.log('Dropping DB...');
await utils.spawn('cargo sqlx database drop -y');
let dals = getTestDals(opts);
for (const [dalPath, dbUrl] of dals.entries()) {
await resetTestDal(dalPath, dbUrl);
}
}

async function dropForDal(dalPath: DalPath, dbUrl: string) {
console.log(`Dropping DB for dal ${dalPath}...`);
await utils.spawn(`cargo sqlx database drop -y --database-url ${dbUrl}`);
}

export async function migrate() {
export async function drop(opts: DbOpts) {
await utils.confirmAction();
console.log('Running migrations...');
await utils.spawn('cd core/lib/dal && cargo sqlx database create && cargo sqlx migrate run');
let dals = getDals(opts);
for (const [dalPath, dbUrl] of dals.entries()) {
await dropForDal(dalPath, dbUrl);
}
}

export async function generateMigration(name: String) {
console.log('Generating migration... ');
process.chdir('core/lib/dal');
await utils.exec(`cargo sqlx migrate add -r ${name}`);
async function migrateForDal(dalPath: DalPath, dbUrl: string) {
console.log(`Running migrations for ${dalPath}...`);
await utils.spawn(
`cd ${dalPath} && cargo sqlx database create --database-url ${dbUrl} && cargo sqlx migrate run --database-url ${dbUrl}`
);
}

export async function migrate(opts: DbOpts) {
await utils.confirmAction();
let dals = getDals(opts);
for (const [dalPath, dbUrl] of dals.entries()) {
await migrateForDal(dalPath, dbUrl);
}
}

async function generateMigrationForDal(dalPath: DalPath, dbUrl: string, name: String) {
console.log(`Generating migration for ${dalPath}...`);
await utils.spawn(`cd ${dalPath} && cargo sqlx migrate add -r ${name}`);
}

export enum DbType {
Core,
Prover
}

export async function generateMigration(dbType: DbType, name: string) {
console.log('Generating migration... ');
if (dbType === DbType.Core) {
await generateMigrationForDal(DalPath.CoreDal, process.env.DATABASE_URL!, name);
} else if (dbType === DbType.Prover) {
await generateMigrationForDal(DalPath.ProverDal, process.env.DATABASE_PROVER_URL!, name);
}
process.chdir(process.env.ZKSYNC_HOME as string);
}

export async function setup() {
process.chdir('core/lib/dal');
export async function setupForDal(dalPath: DalPath, dbUrl: string) {
process.chdir(dalPath);
const localDbUrl = 'postgres://postgres@localhost';
if (process.env.DATABASE_URL!.startsWith(localDbUrl)) {
console.log(`Using localhost database:`);
console.log(`DATABASE_URL = ${process.env.DATABASE_URL}`);
if (dbUrl.startsWith(localDbUrl)) {
console.log(`Using localhost database -- ${dbUrl}`);
} else {
// Remote database, we can't show the contents.
console.log(`WARNING! Using prod db!`);
}
await utils.spawn(`cargo sqlx database create --database-url ${dbUrl}`);
await utils.spawn(`cargo sqlx migrate run --database-url ${dbUrl}`);
if (dbUrl.startsWith(localDbUrl)) {
await utils.spawn(
`cargo sqlx prepare --check --database-url ${dbUrl} -- --tests || cargo sqlx prepare --database-url ${dbUrl} -- --tests`
);
}

process.chdir(process.env.ZKSYNC_HOME as string);
}

export async function setup(opts: DbOpts) {
if (process.env.TEMPLATE_DATABASE_URL !== undefined) {
process.chdir(DalPath.CoreDal);

// Dump and restore from template database (simulate backup)
console.log(`Template DB URL provided. Creating a DB via dump from ${process.env.TEMPLATE_DATABASE_URL}`);
await utils.spawn('cargo sqlx database drop -y');
await utils.spawn('cargo sqlx database create');
await utils.spawn(
`pg_dump ${process.env.TEMPLATE_DATABASE_URL} -F c | pg_restore -d ${process.env.DATABASE_URL}`
);
} else {
// Create an empty database.
await utils.spawn('cargo sqlx database create');
await utils.spawn('cargo sqlx migrate run');
if (process.env.DATABASE_URL!.startsWith(localDbUrl)) {
await utils.spawn('cargo sqlx prepare --check -- --tests || cargo sqlx prepare -- --tests');
}
}

process.chdir(process.env.ZKSYNC_HOME as string);
process.chdir(process.env.ZKSYNC_HOME as string);

return;
}
let dals = getDals(opts);
for (const [dalPath, dbUrl] of dals.entries()) {
await setupForDal(dalPath, dbUrl);
}
}

export async function wait(tries: number = 4) {
async function waitForDal(dbUrl: string, tries: number) {
for (let i = 0; i < tries; i++) {
const result = await utils.allowFail(utils.exec(`pg_isready -d "${process.env.DATABASE_URL}"`));
const result = await utils.allowFail(utils.exec(`pg_isready -d "${dbUrl}"`));
if (result !== null) return; // null means failure
console.log(`waiting for postgres ${process.env.DATABASE_URL}`);
console.log(`waiting for postgres ${dbUrl}`);
await utils.sleep(1);
}
await utils.exec(`pg_isready -d "${process.env.DATABASE_URL}"`);
await utils.exec(`pg_isready -d "${dbUrl}"`);
}

export async function wait(opts: DbOpts, tries: number = 4) {
let dals = getDals(opts);
for (const dbUrl of dals.values()) {
await waitForDal(dbUrl, tries);
}
}

export async function checkSqlxData() {
process.chdir('core/lib/dal');
await utils.spawn('cargo sqlx prepare --check -- --tests');
async function checkSqlxDataForDal(dalPath: DalPath, dbUrl: string) {
process.chdir(dalPath);
await utils.spawn(`cargo sqlx prepare --check --database-url ${dbUrl} -- --tests`);
process.chdir(process.env.ZKSYNC_HOME as string);
}

export async function checkSqlxData(opts: DbOpts) {
let dals = getDals(opts);
for (const [dalPath, dbUrl] of dals.entries()) {
await checkSqlxDataForDal(dalPath, dbUrl);
}
}

interface DbGenerateMigrationOpts {
prover: string | undefined;
server: string | undefined;
}

export const command = new Command('db').description('database management');

command.command('drop').description('drop the database').action(drop);
command.command('migrate').description('run migrations').action(migrate);
command.command('new-migration <name>').description('generate a new migration').action(generateMigration);
command.command('setup').description('initialize the database and perform migrations').action(setup);
command.command('wait').description('wait for database to get ready for interaction').action(wait);
command.command('reset').description('reinitialize the database').action(reset);
command.command('reset-test').description('reinitialize the database for test').action(resetTest);
command.command('check-sqlx-data').description('check sqlx-data.json is up to date').action(checkSqlxData);
command.command('drop').description('drop the database').option('-p, --prover').option('-s, --server').action(drop);
command.command('migrate').description('run migrations').option('-p, --prover').option('-s, --server').action(migrate);
command
.command('new-migration')
.description('generate a new migration for a specific database')
.option('-p, --prover <name>')
.option('-s, --server <name>')
.action((opts: DbGenerateMigrationOpts) => {
if ((!opts.prover && !opts.server) || (opts.prover && opts.server)) {
throw new Error(
'[aborted] please specify a single database to generate migration for (i.e. to generate a migration for server `zk db new-migration --server name_of_migration`'
);
}
if (opts.prover) {
return generateMigration(DbType.Prover, opts.prover);
}
return generateMigration(DbType.Core, opts.server!);
});
command
.command('setup')
.description('initialize the database and perform migrations')
.option('-p, --prover')
.option('-s, --server')
.action(setup);
command
.command('wait')
.description('wait for database to get ready for interaction')
.option('-p, --prover')
.option('-s, --server')
.action(wait);
command
.command('reset')
.description('reinitialize the database')
.option('-p, --prover')
.option('-s, --server')
.action(reset);
command
.command('reset-test')
.description('reinitialize the database for test')
.option('-p, --prover')
.option('-s, --server')
.action(resetTest);
command
.command('check-sqlx-data')
.description('check sqlx-data.json is up to date')
.option('-p, --prover')
.option('-s, --server')
.action(checkSqlxData);
2 changes: 1 addition & 1 deletion infrastructure/zk/src/hyperchain_wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ async function setHyperchainMetadata() {
feeReceiverAddress = richWallets[3].address;

await up('docker-compose-zkstack-common.yml');
await announced('Ensuring databases are up', db.wait());
await announced('Ensuring databases are up', db.wait({ server: true, prover: false }));
}

await initializeTestERC20s();
Expand Down
8 changes: 4 additions & 4 deletions infrastructure/zk/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export async function init(initArgs: InitArgs = DEFAULT_ARGS) {

await announced('Compiling JS packages', run.yarn());
await announced('Compile l2 contracts', compiler.compileAll());
await announced('Drop postgres db', db.drop());
await announced('Setup postgres db', db.setup());
await announced('Drop postgres db', db.drop({ server: true, prover: true }));
await announced('Setup postgres db', db.setup({ server: true, prover: true }));
await announced('Clean rocksdb', clean('db'));
await announced('Clean backups', clean('backups'));
await announced('Building contracts', contract.build());
Expand Down Expand Up @@ -67,8 +67,8 @@ export async function reinit() {
await announced('Setting up containers', up());
await announced('Compiling JS packages', run.yarn());
await announced('Compile l2 contracts', compiler.compileAll());
await announced('Drop postgres db', db.drop());
await announced('Setup postgres db', db.setup());
await announced('Drop postgres db', db.drop({ server: true, prover: true }));
await announced('Setup postgres db', db.setup({ server: true, prover: true }));
await announced('Clean rocksdb', clean('db'));
await announced('Clean backups', clean('backups'));
await announced('Building contracts', contract.build());
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/zk/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export async function externalNode(reinit: boolean = false, enableConsensus: boo
// On --reinit we want to reset RocksDB and Postgres before we start.
if (reinit) {
await utils.confirmAction();
await db.reset();
await db.reset({ server: true, prover: false });
clean(path.dirname(process.env.EN_MERKLE_TREE_PATH!));
}

Expand Down
2 changes: 1 addition & 1 deletion infrastructure/zk/src/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export async function prover() {
await utils.spawn('cargo test --release --workspace --locked');
}
export async function rust(options: string[]) {
await db.resetTest();
await db.resetTest({ server: true, prover: true });

let result = await utils.exec('cargo install --list');
let test_runner = 'cargo nextest run';
Expand Down
4 changes: 4 additions & 0 deletions prover/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions prover/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
# lib
"prover_fri_utils",
"prover_fri_types",
"prover_dal",

# binaries
"witness_generator",
Expand Down
8 changes: 8 additions & 0 deletions prover/prover_dal/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "prover_dal"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
Loading

0 comments on commit 37674fd

Please sign in to comment.