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:
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;