anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

commit ad00e002917b616326a198e2516d5dee2e6b4a03
parent 6af6cd9c8e60bd0a581279bd503c838914a28275
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed, 29 Jul 2026 18:42:47 +0200

collect expired rows in a stored procedure and add the stasis-0002 index migration

Diffstat:
Asrc/include/anastasis/anastasis-database/do_gc.h | 43+++++++++++++++++++++++++++++++++++++++++++
Dsrc/include/anastasis/anastasis-database/gc.h | 43-------------------------------------------
Msrc/include/anastasis_database_lib.h | 2+-
Msrc/include/meson.build | 2+-
Msrc/stasis/anastasis-dbinit.c | 2+-
Asrc/stasis/do_gc.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/stasis/do_gc.sql | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/stasis/gc.c | 76----------------------------------------------------------------------------
Msrc/stasis/meson.build | 4+++-
Msrc/stasis/stasis-0001.sql | 9---------
Asrc/stasis/stasis-0002.sql | 34++++++++++++++++++++++++++++++++++
11 files changed, 204 insertions(+), 132 deletions(-)

diff --git a/src/include/anastasis/anastasis-database/do_gc.h b/src/include/anastasis/anastasis-database/do_gc.h @@ -0,0 +1,43 @@ +/* + This file is part of Anastasis + Copyright (C) 2020-2022 Anastasis SARL + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/anastasis/anastasis-database/do_gc.h + * @brief garbage collection for the Anastasis database + * @author Christian Grothoff + */ +#ifndef ANASTASIS_DATABASE_DO_GC_H +#define ANASTASIS_DATABASE_DO_GC_H + +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_db_lib.h> + +/** + * Function called to perform "garbage collection" on the + * database, expiring records we no longer require. Deletes + * all user records that are not paid up (and by cascade deletes + * the associated recovery documents). Also deletes expired + * truth and financial records older than @a expire_pending_payments. + * + * @param expire_backups backups older than the given time stamp should be garbage collected + * @param expire_pending_payments payments still pending from since before + * this value should be garbage collected + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +ANASTASIS_DB_do_gc (struct GNUNET_TIME_Absolute expire_backups, + struct GNUNET_TIME_Absolute expire_pending_payments); + +#endif diff --git a/src/include/anastasis/anastasis-database/gc.h b/src/include/anastasis/anastasis-database/gc.h @@ -1,43 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2020-2022 Anastasis SARL - - Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file include/anastasis/anastasis-database/gc.h - * @brief garbage collection for the Anastasis database - * @author Christian Grothoff - */ -#ifndef ANASTASIS_DATABASE_GC_H -#define ANASTASIS_DATABASE_GC_H - -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_db_lib.h> - -/** - * Function called to perform "garbage collection" on the - * database, expiring records we no longer require. Deletes - * all user records that are not paid up (and by cascade deletes - * the associated recovery documents). Also deletes expired - * truth and financial records older than @a expire_pending_payments. - * - * @param expire_backups backups older than the given time stamp should be garbage collected - * @param expire_pending_payments payments still pending from since before - * this value should be garbage collected - * @return transaction status - */ -enum GNUNET_DB_QueryStatus -ANASTASIS_DB_gc (struct GNUNET_TIME_Absolute expire_backups, - struct GNUNET_TIME_Absolute expire_pending_payments); - -#endif diff --git a/src/include/anastasis_database_lib.h b/src/include/anastasis_database_lib.h @@ -31,7 +31,7 @@ #include "anastasis/anastasis-database/event.h" #include "anastasis/anastasis-database/create_tables.h" #include "anastasis/anastasis-database/drop_tables.h" -#include "anastasis/anastasis-database/gc.h" +#include "anastasis/anastasis-database/do_gc.h" #include "anastasis/anastasis-database/do_insert_recovery_document.h" #include "anastasis/anastasis-database/iterate_recovery_meta_data.h" #include "anastasis/anastasis-database/get_recovery_document.h" diff --git a/src/include/meson.build b/src/include/meson.build @@ -25,7 +25,7 @@ install_data( 'anastasis/anastasis-database/event.h', 'anastasis/anastasis-database/create_tables.h', 'anastasis/anastasis-database/drop_tables.h', - 'anastasis/anastasis-database/gc.h', + 'anastasis/anastasis-database/do_gc.h', 'anastasis/anastasis-database/do_insert_recovery_document.h', 'anastasis/anastasis-database/iterate_recovery_meta_data.h', 'anastasis/anastasis-database/get_recovery_document.h', diff --git a/src/stasis/anastasis-dbinit.c b/src/stasis/anastasis-dbinit.c @@ -94,7 +94,7 @@ run (void *cls, GNUNET_TIME_relative_multiply ( GNUNET_TIME_UNIT_YEARS, 10)); - if (0 > ANASTASIS_DB_gc ( + if (0 > ANASTASIS_DB_do_gc ( expire_backups, expire_payments)) { diff --git a/src/stasis/do_gc.c b/src/stasis/do_gc.c @@ -0,0 +1,50 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2021, 2022, 2026 Anastasis SARL + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file stasis/do_gc.c + * @brief garbage collection for the Anastasis database + * @author Christian Grothoff + */ +#include "platform.h" +#include "anastasis-db_pg.h" +#include "anastasis/anastasis-database/do_gc.h" +#include "anastasis/anastasis-database/preflight.h" + + +enum GNUNET_DB_QueryStatus +ANASTASIS_DB_do_gc (struct GNUNET_TIME_Absolute expire_backups, + struct GNUNET_TIME_Absolute expire_pending_payments) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_absolute_time (&expire_backups), + GNUNET_PQ_query_param_absolute_time (&expire_pending_payments), + GNUNET_PQ_query_param_end + }; + + GNUNET_break (GNUNET_OK == + ANASTASIS_DB_preflight ()); + /* Every delete lives in the stored procedure: the rows referencing + anastasis_user have to go before the account rows themselves, and doing + that in one statement makes the whole collection atomic. */ + PREPARE ("do_gc", + "SELECT anastasis_do_gc ($1, $2);"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "do_gc", + params); +} + + +/* end of do_gc.c */ diff --git a/src/stasis/do_gc.sql b/src/stasis/do_gc.sql @@ -0,0 +1,71 @@ +-- +-- This file is part of Anastasis +-- Copyright (C) 2026 Anastasis SARL +-- +-- ANASTASIS is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- ANASTASIS is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- ANASTASIS; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS anastasis_do_gc; +CREATE FUNCTION anastasis_do_gc ( + IN in_expire_backups INT8, -- accounts and truths expired before this are removed + IN in_expire_pending_payments INT8) -- unpaid payment records older than this are removed +RETURNS void +LANGUAGE plpgsql +AS $$ +BEGIN + +-- The children of anastasis_user reference it with NO ACTION, so they have to +-- go first; deleting the account rows on their own raises +-- foreign_key_violation for every user that ever uploaded a document, which +-- used to abort the whole collection and reclaim nothing. +DELETE FROM anastasis_recoverydocument + WHERE user_id IN + (SELECT user_id + FROM anastasis_user + WHERE expiration_date < in_expire_backups); + +DELETE FROM anastasis_recdoc_payment + WHERE user_id IN + (SELECT user_id + FROM anastasis_user + WHERE expiration_date < in_expire_backups); + +DELETE FROM anastasis_user + WHERE expiration_date < in_expire_backups; + +-- Expired truths hold encrypted key shares, so they must not be kept past the +-- date they were paid for. anastasis_truth has no foreign key to the account. +DELETE FROM anastasis_challengecode + WHERE expiration_date < in_expire_backups; + +DELETE FROM anastasis_truth + WHERE expiration < in_expire_backups; + +DELETE FROM anastasis_truth_payment + WHERE expiration < in_expire_backups; + +-- Payment records that never resulted in a payment (or were refunded) are +-- only useful while the client may still come back with the receipt. +DELETE FROM anastasis_recdoc_payment + WHERE paid=FALSE + AND creation_date < in_expire_pending_payments; + +-- anastasis_challenge_payment has no expiration column, so the +-- pending-payment horizon doubles as its retention policy. +DELETE FROM anastasis_challenge_payment + WHERE (paid=FALSE OR refunded) + AND creation_date < in_expire_pending_payments; + +END $$; + +COMMENT ON FUNCTION anastasis_do_gc + IS 'Removes expired accounts with their recovery documents and payments, expired truths with their challenge codes and payments, and payment records that were never paid or were refunded. Runs as a single statement, so it either collects everything or nothing.'; diff --git a/src/stasis/gc.c b/src/stasis/gc.c @@ -1,76 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2020, 2021, 2022 Anastasis SARL - - Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file stasis/gc.c - * @brief garbage collection for the Anastasis database - * @author Christian Grothoff - */ -#include "platform.h" -#include "anastasis-db_pg.h" -#include "anastasis/anastasis-database/gc.h" -#include "anastasis/anastasis-database/preflight.h" -#include "anastasis/anastasis-database/transaction.h" - - -enum GNUNET_DB_QueryStatus -ANASTASIS_DB_gc (struct GNUNET_TIME_Absolute expire_backups, - struct GNUNET_TIME_Absolute expire_pending_payments) -{ - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_absolute_time (&expire_backups), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_QueryParam params2[] = { - GNUNET_PQ_query_param_absolute_time (&expire_pending_payments), - GNUNET_PQ_query_param_end - }; - enum GNUNET_DB_QueryStatus qs; - - GNUNET_break (GNUNET_OK == - ANASTASIS_DB_preflight ()); -#if 0 - /* FIXME: should we do this? */ - PREPARE ("gc_challenge_pending_payments", - "DELETE FROM anastasis_challenge_payment " - "WHERE" - " (paid=FALSE" - " OR" - " refunded)" - " AND" - " creation_date < $1;"), -#endif - PREPARE ("gc_accounts", - "DELETE FROM anastasis_user " - "WHERE" - " expiration_date < $1;"); - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "gc_accounts", - params); - if (qs < 0) - return qs; - PREPARE ("gc_recdoc_pending_payments", - "DELETE FROM anastasis_recdoc_payment " - "WHERE" - " paid=FALSE" - " AND" - " creation_date < $1;"); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "gc_recdoc_pending_payments", - params2); -} - - -/* end of gc.c */ diff --git a/src/stasis/meson.build b/src/stasis/meson.build @@ -6,6 +6,7 @@ sqldir = get_option('datadir') / 'anastasis' / 'sql' install_data( 'versioning.sql', 'stasis-0001.sql', + 'stasis-0002.sql', 'drop.sql', install_dir: sqldir, ) @@ -15,6 +16,7 @@ install_data( # list in the same order as libanastasisdb_SOURCES. procedures_sql = [ 'procedures-prelude.sql', + 'do_gc.sql', 'do_update_account_lifetime.sql', 'commit.sql', ] @@ -36,7 +38,7 @@ libanastasisdb_SOURCES = [ 'preflight.c', 'create_tables.c', 'drop_tables.c', - 'gc.c', + 'do_gc.c', 'gc_challenge_codes.c', 'get_challenge_payment.c', 'get_recdoc_payment.c', diff --git a/src/stasis/stasis-0001.sql b/src/stasis/stasis-0001.sql @@ -197,15 +197,6 @@ CREATE INDEX IF NOT EXISTS anastasis_challengecode_uuid_index COMMENT ON INDEX anastasis_challengecode_uuid_index IS 'for challenge lookup'; --- GC deletes WHERE expiration_date < $1 with no truth_uuid predicate, so the --- index must lead with expiration_date (a (truth_uuid, expiration_date) index --- cannot serve it, and duplicating the lookup index above is pure overhead). -CREATE INDEX IF NOT EXISTS anastasis_challengecode_expiration_index - ON anastasis_challengecode - (expiration_date); -COMMENT ON INDEX anastasis_challengecode_expiration_index - IS 'for challenge garbage collection'; - CREATE TABLE IF NOT EXISTS anastasis_auth_iban_in (auth_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE diff --git a/src/stasis/stasis-0002.sql b/src/stasis/stasis-0002.sql @@ -0,0 +1,34 @@ +-- +-- This file is part of Anastasis +-- Copyright (C) 2026 Anastasis SARL +-- +-- ANASTASIS is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- ANASTASIS is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- ANASTASIS; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +-- Everything in one big transaction +BEGIN; + +-- Check patch versioning is in place. +SELECT _v.register_patch('stasis-0002', NULL, NULL); + +SET search_path TO anastasis; + +-- Garbage collection deletes WHERE expiration_date < $1 with no truth_uuid +-- predicate, so the index must lead with expiration_date; the existing +-- (truth_uuid, expiration_date) lookup index cannot serve that scan. +CREATE INDEX IF NOT EXISTS anastasis_challengecode_expiration_index + ON anastasis_challengecode + (expiration_date); +COMMENT ON INDEX anastasis_challengecode_expiration_index + IS 'for challenge garbage collection'; + +COMMIT;