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 {
AmountJson,
AmountLike,
Amounts,
AmountString,
buildSigPS,
bytesToString,
Codec,
codecForAny,
decodeCrock,
Duration,
eddsaSign,
encodeCrock,
getDurationRemaining,
getRandomBytes,
getTimestampNow,
hash,
j2s,
Logger,
@ -1051,27 +1057,62 @@ async function nextFromAuthenticationsEditing(
async function updateUploadFees(
state: ReducerStateBackup,
): Promise<ReducerStateBackup | ReducerStateError> {
for (const prov of state.policy_providers ?? []) {
const info = state.authentication_providers![prov.provider_url];
if (!("currency" in info)) {
continue;
const expiration = state.expiration;
if (!expiration) {
return { ...state };
}
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(
state: ReducerStateBackup,
args: ActionArgEnterSecret,
): Promise<ReducerStateBackup | ReducerStateError> {
return {
return updateUploadFees({
...state,
expiration: args.expiration,
core_secret: {
mime: args.secret.mime ?? "text/plain",
value: args.secret.value,
},
};
});
}
async function nextFromChallengeSelecting(
@ -1102,11 +1143,10 @@ async function updateSecretExpiration(
state: ReducerStateBackup,
args: ActionArgsUpdateExpiration,
): Promise<ReducerStateBackup | ReducerStateError> {
// FIXME: implement!
return {
return updateUploadFees({
...state,
expiration: args.expiration,
};
});
}
const backupTransitions: Record<

View File

@ -66,6 +66,7 @@ export interface ReducerStateBackup {
selected_country?: string;
secret_name?: string;
policies?: Policy[];
/**
* Policy providers are providers that we checked to be functional
* and that are actually used in policies.
@ -82,7 +83,7 @@ export interface ReducerStateBackup {
expiration?: Timestamp;
upload_fees?: AmountString[];
upload_fees?: { fee: AmountString }[];
}
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)) {
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 };
}
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 {
if (t1.t_ms === "never") {
return { t_ms: t2.t_ms };