anastasis-core: compute upload fees

This commit is contained in:
Florian Dold 2021-11-02 17:02:04 +01:00
parent 83b63d1cc0
commit fdc36b4fb7
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
4 changed files with 68 additions and 12 deletions

View File

@ -1,13 +1,19 @@
import { import {
AmountJson,
AmountLike,
Amounts,
AmountString, AmountString,
buildSigPS, buildSigPS,
bytesToString, bytesToString,
Codec, Codec,
codecForAny, codecForAny,
decodeCrock, decodeCrock,
Duration,
eddsaSign, eddsaSign,
encodeCrock, encodeCrock,
getDurationRemaining,
getRandomBytes, getRandomBytes,
getTimestampNow,
hash, hash,
j2s, j2s,
Logger, Logger,
@ -1051,27 +1057,62 @@ async function nextFromAuthenticationsEditing(
async function updateUploadFees( async function updateUploadFees(
state: ReducerStateBackup, state: ReducerStateBackup,
): Promise<ReducerStateBackup | ReducerStateError> { ): Promise<ReducerStateBackup | ReducerStateError> {
for (const prov of state.policy_providers ?? []) { const expiration = state.expiration;
const info = state.authentication_providers![prov.provider_url]; if (!expiration) {
if (!("currency" in info)) { return { ...state };
continue; }
logger.info("updating upload fees");
const feePerCurrency: Record<string, AmountJson> = {};
const coveredProviders = new Set<string>();
const addFee = (x: AmountLike) => {
x = Amounts.jsonifyAmount(x);
feePerCurrency[x.currency] = Amounts.add(
feePerCurrency[x.currency] ?? Amounts.getZero(x.currency),
x,
).amount;
};
const years = Duration.toIntegerYears(Duration.getRemaining(expiration));
logger.info(`computing fees for ${years} years`);
for (const x of state.policies ?? []) {
for (const m of x.methods) {
const prov = state.authentication_providers![
m.provider
] as AuthenticationProviderStatusOk;
const authMethod = state.authentication_methods![m.authentication_method];
if (!coveredProviders.has(m.provider)) {
const annualFee = Amounts.mult(prov.annual_fee, years).amount;
logger.info(`adding annual fee ${Amounts.stringify(annualFee)}`);
addFee(annualFee);
coveredProviders.add(m.provider);
}
for (const pm of prov.methods) {
if (pm.type === authMethod.type) {
addFee(pm.usage_fee);
break;
} }
} }
return { ...state, upload_fees: [] }; }
}
return {
...state,
upload_fees: Object.values(feePerCurrency).map((x) => ({
fee: Amounts.stringify(x),
})),
};
} }
async function enterSecret( async function enterSecret(
state: ReducerStateBackup, state: ReducerStateBackup,
args: ActionArgEnterSecret, args: ActionArgEnterSecret,
): Promise<ReducerStateBackup | ReducerStateError> { ): Promise<ReducerStateBackup | ReducerStateError> {
return { return updateUploadFees({
...state, ...state,
expiration: args.expiration, expiration: args.expiration,
core_secret: { core_secret: {
mime: args.secret.mime ?? "text/plain", mime: args.secret.mime ?? "text/plain",
value: args.secret.value, value: args.secret.value,
}, },
}; });
} }
async function nextFromChallengeSelecting( async function nextFromChallengeSelecting(
@ -1102,11 +1143,10 @@ async function updateSecretExpiration(
state: ReducerStateBackup, state: ReducerStateBackup,
args: ActionArgsUpdateExpiration, args: ActionArgsUpdateExpiration,
): Promise<ReducerStateBackup | ReducerStateError> { ): Promise<ReducerStateBackup | ReducerStateError> {
// FIXME: implement! return updateUploadFees({
return {
...state, ...state,
expiration: args.expiration, expiration: args.expiration,
}; });
} }
const backupTransitions: Record< const backupTransitions: Record<

View File

@ -66,6 +66,7 @@ export interface ReducerStateBackup {
selected_country?: string; selected_country?: string;
secret_name?: string; secret_name?: string;
policies?: Policy[]; policies?: Policy[];
/** /**
* Policy providers are providers that we checked to be functional * Policy providers are providers that we checked to be functional
* and that are actually used in policies. * and that are actually used in policies.
@ -82,7 +83,7 @@ export interface ReducerStateBackup {
expiration?: Timestamp; expiration?: Timestamp;
upload_fees?: AmountString[]; upload_fees?: { fee: AmountString }[];
} }
export interface AuthMethod { export interface AuthMethod {

View File

@ -349,7 +349,8 @@ export class Amounts {
} }
} }
static mult(a: AmountJson, n: number): Result { static mult(a: AmountLike, n: number): Result {
a = this.jsonifyAmount(a);
if (!Number.isInteger(n)) { if (!Number.isInteger(n)) {
throw Error("amount can only be multipied by an integer"); throw Error("amount can only be multipied by an integer");
} }

View File

@ -69,6 +69,20 @@ export function getDurationRemaining(
return { d_ms: deadline.t_ms - now.t_ms }; return { d_ms: deadline.t_ms - now.t_ms };
} }
export namespace Duration {
export const getRemaining = getDurationRemaining;
export function toIntegerYears(d: Duration): number {
if (typeof d.d_ms !== "number") {
throw Error("infinite duration");
}
return Math.ceil(d.d_ms / 1000 / 60 / 60 / 24 / 365);
}
}
export namespace Timestamp {
export const min = timestampMin;
}
export function timestampMin(t1: Timestamp, t2: Timestamp): Timestamp { export function timestampMin(t1: Timestamp, t2: Timestamp): Timestamp {
if (t1.t_ms === "never") { if (t1.t_ms === "never") {
return { t_ms: t2.t_ms }; return { t_ms: t2.t_ms };