[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-wallet-core] branch master updated: wallet-core: allow peer-push
From: |
gnunet |
Subject: |
[taler-wallet-core] branch master updated: wallet-core: allow peer-push with coins locked behind refresh |
Date: |
Thu, 04 Apr 2024 15:43:14 +0200 |
This is an automated email from the git hooks/post-receive script.
dold pushed a commit to branch master
in repository wallet-core.
The following commit(s) were added to refs/heads/master by this push:
new e7a966f75 wallet-core: allow peer-push with coins locked behind refresh
e7a966f75 is described below
commit e7a966f755e78bf9ec200f29e49706045d1e1a54
Author: Florian Dold <florian@dold.me>
AuthorDate: Thu Apr 4 15:43:12 2024 +0200
wallet-core: allow peer-push with coins locked behind refresh
---
.../test-wallet-blocked-pay-merchant.ts | 2 +-
...ant.ts => test-wallet-blocked-pay-peer-push.ts} | 59 ++++---
.../src/integrationtests/testrunner.ts | 2 +
packages/taler-wallet-core/src/coinSelection.ts | 9 +-
packages/taler-wallet-core/src/db.ts | 2 +-
packages/taler-wallet-core/src/pay-peer-common.ts | 14 +-
.../taler-wallet-core/src/pay-peer-pull-debit.ts | 3 -
.../taler-wallet-core/src/pay-peer-push-debit.ts | 177 +++++++++++++++------
8 files changed, 171 insertions(+), 97 deletions(-)
diff --git
a/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-merchant.ts
b/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-merchant.ts
index a74f4fd1d..15167d133 100644
---
a/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-merchant.ts
+++
b/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-merchant.ts
@@ -45,7 +45,7 @@ const coinCommon = {
};
/**
- * Run test for refreshe after a payment.
+ * Run test for paying a merchant with balance locked behind a pending refresh.
*/
export async function runWalletBlockedPayMerchantTest(t: GlobalTestState) {
// Set up test environment
diff --git
a/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-merchant.ts
b/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-peer-push.ts
similarity index 69%
copy from
packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-merchant.ts
copy to
packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-peer-push.ts
index a74f4fd1d..7427f2b07 100644
---
a/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-merchant.ts
+++
b/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-peer-push.ts
@@ -18,8 +18,12 @@
* Imports.
*/
import {
- MerchantApiClient,
- PreparePayResultType,
+ AbsoluteTime,
+ AmountString,
+ Duration,
+ NotificationType,
+ TransactionMajorState,
+ TransactionMinorState,
j2s,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
@@ -45,9 +49,9 @@ const coinCommon = {
};
/**
- * Run test for refreshe after a payment.
+ * Run test for a peer push payment with balance locked behind a pending
refresh.
*/
-export async function runWalletBlockedPayMerchantTest(t: GlobalTestState) {
+export async function runWalletBlockedPayPeerPushTest(t: GlobalTestState) {
// Set up test environment
const coinConfigList: CoinConfig[] = [
@@ -105,38 +109,41 @@ export async function runWalletBlockedPayMerchantTest(t:
GlobalTestState) {
},
});
- const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl());
-
- const orderResp = await merchantClient.createOrder({
- order: {
- summary: "My Payment",
- amount: "TESTKUDOS:18",
- },
- });
-
- let orderStatus = await merchantClient.queryPrivateOrderStatus({
- orderId: orderResp.order_id,
+ const checkResp = await w1.call(WalletApiOperation.CheckPeerPushDebit, {
+ amount: "TESTKUDOS:18" as AmountString,
});
- t.assertTrue(orderStatus.order_status === "unpaid");
+ console.log(`check resp ${j2s(checkResp)}`);
- // Make wallet pay for the order
+ const readyCond = w1.waitForNotificationCond((n) => {
+ return (
+ n.type === NotificationType.TransactionStateTransition &&
+ n.transactionId.startsWith("txn:peer-push-debit:") &&
+ n.newTxState.major === TransactionMajorState.Pending &&
+ n.newTxState.minor === TransactionMinorState.Ready
+ );
+ });
- const preparePayResult = await w1.call(WalletApiOperation.PreparePayForUri, {
- talerPayUri: orderStatus.taler_pay_uri,
+ const confirmResp = await w1.call(WalletApiOperation.InitiatePeerPushDebit, {
+ partialContractTerms: {
+ summary: "hi!",
+ amount: "TESTKUDOS:18" as AmountString,
+ purse_expiration: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ hours: 1 }),
+ ),
+ ),
+ },
});
- console.log(`prepare pay result: ${j2s(preparePayResult)}`);
-
- t.assertTrue(
- preparePayResult.status === PreparePayResultType.PaymentPossible,
- );
+ console.log(`confirm resp ${j2s(confirmResp)}`);
await w1.call(WalletApiOperation.ApplyDevExperiment, {
devExperimentUri: "taler://dev-experiment/stop-block-refresh",
});
- await w1.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+ await readyCond;
}
-runWalletBlockedPayMerchantTest.suites = ["wallet"];
+runWalletBlockedPayPeerPushTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts
b/packages/taler-harness/src/integrationtests/testrunner.ts
index 2934e36e3..acc9f5e29 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -92,6 +92,7 @@ import { runWalletBalanceZeroTest } from
"./test-wallet-balance-zero.js";
import { runWalletBalanceTest } from "./test-wallet-balance.js";
import { runWalletBlockedDepositTest } from "./test-wallet-blocked-deposit.js";
import { runWalletBlockedPayMerchantTest } from
"./test-wallet-blocked-pay-merchant.js";
+import { runWalletBlockedPayPeerPushTest } from
"./test-wallet-blocked-pay-peer-push.js";
import { runWalletCliTerminationTest } from "./test-wallet-cli-termination.js";
import { runWalletConfigTest } from "./test-wallet-config.js";
import { runWalletCryptoWorkerTest } from "./test-wallet-cryptoworker.js";
@@ -216,6 +217,7 @@ const allTests: TestMainFunction[] = [
runWalletDenomExpireTest,
runWalletBlockedDepositTest,
runWalletBlockedPayMerchantTest,
+ runWalletBlockedPayPeerPushTest,
];
export interface TestRunSpec {
diff --git a/packages/taler-wallet-core/src/coinSelection.ts
b/packages/taler-wallet-core/src/coinSelection.ts
index bce51fd91..0027241c4 100644
--- a/packages/taler-wallet-core/src/coinSelection.ts
+++ b/packages/taler-wallet-core/src/coinSelection.ts
@@ -1001,13 +1001,6 @@ export interface PeerCoinSelectionRequest {
* selection instead of selecting completely new coins.
*/
repair?: PreviousPayCoins;
-
- /**
- * If set to true, the coin selection will also use coins that are not
- * materially available yet, but that are expected to become available
- * as the output of a refresh operation.
- */
- includePendingCoins: boolean;
}
export async function computeCoinSelMaxExpirationDate(
@@ -1191,7 +1184,7 @@ export async function selectPeerCoins(
false,
);
- if (!avRes && req.includePendingCoins) {
+ if (!avRes) {
// Try to see if we can do a prospective selection
const prospectiveAvRes = await internalSelectPeerCoins(
wex,
diff --git a/packages/taler-wallet-core/src/db.ts
b/packages/taler-wallet-core/src/db.ts
index a675fa8dd..6db85d8f6 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1892,7 +1892,7 @@ export interface PeerPushDebitRecord {
totalCost: AmountString;
- coinSel: DbPeerPushPaymentCoinSelection;
+ coinSel?: DbPeerPushPaymentCoinSelection;
contractTermsHash: HashCodeString;
diff --git a/packages/taler-wallet-core/src/pay-peer-common.ts
b/packages/taler-wallet-core/src/pay-peer-common.ts
index 599010c1d..6ad8ecb70 100644
--- a/packages/taler-wallet-core/src/pay-peer-common.ts
+++ b/packages/taler-wallet-core/src/pay-peer-common.ts
@@ -22,7 +22,7 @@ import {
AmountString,
Amounts,
Codec,
- SelectedCoin,
+ SelectedProspectiveCoin,
TalerProtocolTimestamp,
buildCodecForObject,
checkDbInvariant,
@@ -74,21 +74,17 @@ export async function queryCoinInfosForSelection(
export async function getTotalPeerPaymentCost(
wex: WalletExecutionContext,
- pcs: SelectedCoin[],
+ pcs: SelectedProspectiveCoin[],
): Promise<AmountJson> {
const currency = Amounts.currencyOf(pcs[0].contribution);
return wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
const costs: AmountJson[] = [];
for (let i = 0; i < pcs.length; i++) {
- const coin = await tx.coins.get(pcs[i].coinPub);
- if (!coin) {
- throw Error("can't calculate payment cost, coin not found");
- }
const denomInfo = await getDenomInfo(
wex,
tx,
- coin.exchangeBaseUrl,
- coin.denomPubHash,
+ pcs[i].exchangeBaseUrl,
+ pcs[i].denomPubHash,
);
if (!denomInfo) {
throw Error(
@@ -98,7 +94,7 @@ export async function getTotalPeerPaymentCost(
const allDenoms = await getCandidateWithdrawalDenomsTx(
wex,
tx,
- coin.exchangeBaseUrl,
+ pcs[i].exchangeBaseUrl,
currency,
);
const amountLeft = Amounts.sub(
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
index 2cc241187..9bfa14ca2 100644
--- a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
@@ -373,7 +373,6 @@ async function handlePurseCreationConflict(
const coinSelRes = await selectPeerCoins(ws, {
instructedAmount,
repair,
- includePendingCoins: false,
});
switch (coinSelRes.type) {
@@ -600,7 +599,6 @@ export async function confirmPeerPullDebit(
const coinSelRes = await selectPeerCoins(wex, {
instructedAmount,
- includePendingCoins: false,
});
if (logger.shouldLogTrace()) {
logger.trace(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
@@ -785,7 +783,6 @@ export async function preparePeerPullDebit(
const coinSelRes = await selectPeerCoins(wex, {
instructedAmount,
- includePendingCoins: true,
});
if (logger.shouldLogTrace()) {
logger.trace(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts
b/packages/taler-wallet-core/src/pay-peer-push-debit.ts
index 51b865b99..b6771be89 100644
--- a/packages/taler-wallet-core/src/pay-peer-push-debit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-push-debit.ts
@@ -26,6 +26,7 @@ import {
Logger,
NotificationType,
RefreshReason,
+ SelectedProspectiveCoin,
TalerError,
TalerErrorCode,
TalerPreciseTimestamp,
@@ -38,6 +39,7 @@ import {
TransactionState,
TransactionType,
assertUnreachable,
+ checkDbInvariant,
checkLogicInvariant,
encodeCrock,
getRandomBytes,
@@ -345,8 +347,8 @@ export async function checkPeerPushDebit(
);
const coinSelRes = await selectPeerCoins(wex, {
instructedAmount,
- includePendingCoins: true,
});
+ let coins: SelectedProspectiveCoin[] | undefined = undefined;
switch (coinSelRes.type) {
case "failure":
throw TalerError.fromDetail(
@@ -356,17 +358,16 @@ export async function checkPeerPushDebit(
},
);
case "prospective":
- throw Error("not supported");
+ coins = coinSelRes.result.prospectiveCoins;
+ break;
case "success":
+ coins = coinSelRes.result.coins;
break;
default:
assertUnreachable(coinSelRes);
}
- logger.trace(`selected peer coins (len=${coinSelRes.result.coins.length})`);
- const totalAmount = await getTotalPeerPaymentCost(
- wex,
- coinSelRes.result.coins,
- );
+ logger.trace(`selected peer coins (len=${coins.length})`);
+ const totalAmount = await getTotalPeerPaymentCost(wex, coins);
logger.trace("computed total peer payment cost");
return {
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
@@ -401,6 +402,8 @@ async function handlePurseCreationConflict(
const instructedAmount = Amounts.parseOrThrow(peerPushInitiation.amount);
const sel = peerPushInitiation.coinSel;
+ checkDbInvariant(!!sel);
+
const repair: PreviousPayCoins = [];
for (let i = 0; i < sel.coinPubs.length; i++) {
@@ -415,7 +418,6 @@ async function handlePurseCreationConflict(
const coinSelRes = await selectPeerCoins(wex, {
instructedAmount,
repair,
- includePendingCoins: false,
});
switch (coinSelRes.type) {
@@ -479,6 +481,75 @@ async function processPeerPushDebitCreateReserve(
);
}
+ if (!peerPushInitiation.coinSel) {
+ const coinSelRes = await selectPeerCoins(wex, {
+ instructedAmount: Amounts.parseOrThrow(peerPushInitiation.amount),
+ });
+
+ switch (coinSelRes.type) {
+ case "failure":
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
+ {
+ insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
+ },
+ );
+ case "prospective":
+ throw Error("insufficient funds (blocked on refresh)");
+ case "success":
+ break;
+ default:
+ assertUnreachable(coinSelRes);
+ }
+ const transitionDone = await wex.db.runReadWriteTx(
+ [
+ "exchanges",
+ "contractTerms",
+ "coins",
+ "coinAvailability",
+ "denominations",
+ "refreshGroups",
+ "refreshSessions",
+ "peerPushDebit",
+ ],
+ async (tx) => {
+ const ppi = await tx.peerPushDebit.get(pursePub);
+ if (!ppi) {
+ return false;
+ }
+ if (ppi.coinSel) {
+ return false;
+ }
+
+ ppi.coinSel = {
+ coinPubs: coinSelRes.result.coins.map((x) => x.coinPub),
+ contributions: coinSelRes.result.coins.map((x) => x.contribution),
+ };
+ // FIXME: Instead of directly doing a spendCoin here,
+ // we might want to mark the coins as used and spend them
+ // after we've been able to create the purse.
+ await spendCoins(wex, tx, {
+ allocationId: constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub,
+ }),
+ coinPubs: coinSelRes.result.coins.map((x) => x.coinPub),
+ contributions: coinSelRes.result.coins.map((x) =>
+ Amounts.parseOrThrow(x.contribution),
+ ),
+ refreshReason: RefreshReason.PayPeerPush,
+ });
+
+ await tx.peerPushDebit.put(ppi);
+ return true;
+ },
+ );
+ if (transitionDone) {
+ return TaskRunResult.progress();
+ }
+ return TaskRunResult.backoff();
+ }
+
const purseSigResp = await wex.cryptoApi.signPurseCreation({
hContractTerms,
mergePub: peerPushInitiation.mergePub,
@@ -625,6 +696,10 @@ async function processPeerPushDebitAbortingDeletePurse(
const oldTxState = computePeerPushDebitTransactionState(ppiRec);
const coinPubs: CoinRefreshRequest[] = [];
+ if (!ppiRec.coinSel) {
+ return undefined;
+ }
+
for (let i = 0; i < ppiRec.coinSel.coinPubs.length; i++) {
coinPubs.push({
amount: ppiRec.coinSel.contributions[i],
@@ -859,23 +934,26 @@ async function processPeerPushDebitReady(
const oldTxState = computePeerPushDebitTransactionState(ppiRec);
const coinPubs: CoinRefreshRequest[] = [];
- for (let i = 0; i < ppiRec.coinSel.coinPubs.length; i++) {
- coinPubs.push({
- amount: ppiRec.coinSel.contributions[i],
- coinPub: ppiRec.coinSel.coinPubs[i],
- });
+ if (ppiRec.coinSel) {
+ for (let i = 0; i < ppiRec.coinSel.coinPubs.length; i++) {
+ coinPubs.push({
+ amount: ppiRec.coinSel.contributions[i],
+ coinPub: ppiRec.coinSel.coinPubs[i],
+ });
+ }
+
+ const refresh = await createRefreshGroup(
+ wex,
+ tx,
+ currency,
+ coinPubs,
+ RefreshReason.AbortPeerPushDebit,
+ transactionId,
+ );
+
+ ppiRec.abortRefreshGroupId = refresh.refreshGroupId;
}
-
- const refresh = await createRefreshGroup(
- wex,
- tx,
- currency,
- coinPubs,
- RefreshReason.AbortPeerPushDebit,
- transactionId,
- );
ppiRec.status = PeerPushDebitStatus.AbortingRefreshExpired;
- ppiRec.abortRefreshGroupId = refresh.refreshGroupId;
await tx.peerPushDebit.put(ppiRec);
const newTxState = computePeerPushDebitTransactionState(ppiRec);
return {
@@ -954,12 +1032,12 @@ export async function initiatePeerPushDebit(
const contractKeyPair = await wex.cryptoApi.createEddsaKeypair({});
- // FIXME: Check first if possible with pending coins, in that case defer
coin selection
const coinSelRes = await selectPeerCoins(wex, {
instructedAmount,
- includePendingCoins: false,
});
+ let coins: SelectedProspectiveCoin[] | undefined = undefined;
+
switch (coinSelRes.type) {
case "failure":
throw TalerError.fromDetail(
@@ -969,8 +1047,10 @@ export async function initiatePeerPushDebit(
},
);
case "prospective":
- throw Error("blocked on pending refresh");
+ coins = coinSelRes.result.prospectiveCoins;
+ break;
case "success":
+ coins = coinSelRes.result.coins;
break;
default:
assertUnreachable(coinSelRes);
@@ -981,10 +1061,7 @@ export async function initiatePeerPushDebit(
logger.info(`selected p2p coins (push):`);
logger.trace(`${j2s(coinSelRes)}`);
- const totalAmount = await getTotalPeerPaymentCost(
- wex,
- coinSelRes.result.coins,
- );
+ const totalAmount = await getTotalPeerPaymentCost(wex, coins);
logger.info(`computed total peer payment cost`);
@@ -1008,21 +1085,6 @@ export async function initiatePeerPushDebit(
"peerPushDebit",
],
async (tx) => {
- // FIXME: Instead of directly doing a spendCoin here,
- // we might want to mark the coins as used and spend them
- // after we've been able to create the purse.
- await spendCoins(wex, tx, {
- allocationId: constructTransactionIdentifier({
- tag: TransactionType.PeerPushDebit,
- pursePub: pursePair.pub,
- }),
- coinPubs: sel.coins.map((x) => x.coinPub),
- contributions: sel.coins.map((x) =>
- Amounts.parseOrThrow(x.contribution),
- ),
- refreshReason: RefreshReason.PayPeerPush,
- });
-
const ppi: PeerPushDebitRecord = {
amount: Amounts.stringify(instructedAmount),
contractPriv: contractKeyPair.priv,
@@ -1037,13 +1099,30 @@ export async function initiatePeerPushDebit(
timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
status: PeerPushDebitStatus.PendingCreatePurse,
contractEncNonce,
- coinSel: {
- coinPubs: sel.coins.map((x) => x.coinPub),
- contributions: sel.coins.map((x) => x.contribution),
- },
totalCost: Amounts.stringify(totalAmount),
};
+ if (coinSelRes.type === "success") {
+ ppi.coinSel = {
+ coinPubs: coinSelRes.result.coins.map((x) => x.coinPub),
+ contributions: coinSelRes.result.coins.map((x) => x.contribution),
+ };
+ // FIXME: Instead of directly doing a spendCoin here,
+ // we might want to mark the coins as used and spend them
+ // after we've been able to create the purse.
+ await spendCoins(wex, tx, {
+ allocationId: constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub: pursePair.pub,
+ }),
+ coinPubs: coinSelRes.result.coins.map((x) => x.coinPub),
+ contributions: coinSelRes.result.coins.map((x) =>
+ Amounts.parseOrThrow(x.contribution),
+ ),
+ refreshReason: RefreshReason.PayPeerPush,
+ });
+ }
+
await tx.peerPushDebit.add(ppi);
await tx.contractTerms.put({
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-wallet-core] branch master updated: wallet-core: allow peer-push with coins locked behind refresh,
gnunet <=