precompute speculative signature for payment
This commit is contained in:
parent
6594355704
commit
ca2a46a857
@ -275,8 +275,8 @@ export class CryptoApi {
|
|||||||
return this.doRpc<boolean>("isValidWireFee", 2, type, wf, masterPub);
|
return this.doRpc<boolean>("isValidWireFee", 2, type, wf, masterPub);
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidPaymentSignature(sig: string, contractHash: string, merchantPub: string) {
|
isValidPaymentSignature(sig: string, contractHash: string, merchantPub: string): Promise<boolean> {
|
||||||
return this.doRpc<PayCoinInfo>("isValidPaymentSignature", 1, sig, contractHash, merchantPub);
|
return this.doRpc<boolean>("isValidPaymentSignature", 1, sig, contractHash, merchantPub);
|
||||||
}
|
}
|
||||||
|
|
||||||
signDeposit(contractTerms: ContractTerms,
|
signDeposit(contractTerms: ContractTerms,
|
||||||
|
@ -261,7 +261,11 @@ namespace RpcFunctions {
|
|||||||
*/
|
*/
|
||||||
export function signDeposit(contractTerms: ContractTerms,
|
export function signDeposit(contractTerms: ContractTerms,
|
||||||
cds: CoinWithDenom[]): PayCoinInfo {
|
cds: CoinWithDenom[]): PayCoinInfo {
|
||||||
const ret: PayCoinInfo = [];
|
const ret: PayCoinInfo = {
|
||||||
|
originalCoins: [],
|
||||||
|
updatedCoins: [],
|
||||||
|
sigs: [],
|
||||||
|
};
|
||||||
|
|
||||||
const contractTermsHash = hashString(canonicalJson(contractTerms));
|
const contractTermsHash = hashString(canonicalJson(contractTerms));
|
||||||
|
|
||||||
@ -275,6 +279,7 @@ namespace RpcFunctions {
|
|||||||
const amountRemaining = new native.Amount(total);
|
const amountRemaining = new native.Amount(total);
|
||||||
for (const cd of cds) {
|
for (const cd of cds) {
|
||||||
let coinSpend: Amount;
|
let coinSpend: Amount;
|
||||||
|
const originalCoin = { ...(cd.coin) };
|
||||||
|
|
||||||
if (amountRemaining.value === 0 && amountRemaining.fraction === 0) {
|
if (amountRemaining.value === 0 && amountRemaining.fraction === 0) {
|
||||||
break;
|
break;
|
||||||
@ -324,7 +329,9 @@ namespace RpcFunctions {
|
|||||||
f: coinSpend.toJson(),
|
f: coinSpend.toJson(),
|
||||||
ub_sig: cd.coin.denomSig,
|
ub_sig: cd.coin.denomSig,
|
||||||
};
|
};
|
||||||
ret.push({sig: s, updatedCoin: cd.coin});
|
ret.sigs.push(s);
|
||||||
|
ret.updatedCoins.push(cd.coin);
|
||||||
|
ret.originalCoins.push(originalCoin);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
32
src/query.ts
32
src/query.ts
@ -806,6 +806,38 @@ export class QueryRoot {
|
|||||||
.then(() => promise);
|
.then(() => promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get get objects from a store by their keys.
|
||||||
|
* If no object for a key exists, the resulting position in the array
|
||||||
|
* contains 'undefined'.
|
||||||
|
*/
|
||||||
|
getMany<T>(store: Store<T>, keys: any[]): Promise<T[]> {
|
||||||
|
this.checkFinished();
|
||||||
|
|
||||||
|
const { resolve, promise } = openPromise();
|
||||||
|
const results: T[] = [];
|
||||||
|
|
||||||
|
const doGetMany = (tx: IDBTransaction) => {
|
||||||
|
for (const key of keys) {
|
||||||
|
if (key === void 0) {
|
||||||
|
throw Error("key must not be undefined");
|
||||||
|
}
|
||||||
|
const req = tx.objectStore(store.name).get(key);
|
||||||
|
req.onsuccess = () => {
|
||||||
|
results.push(req.result);
|
||||||
|
if (results.length == keys.length) {
|
||||||
|
resolve(results);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addWork(doGetMany, store.name, false);
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => this.finish())
|
||||||
|
.then(() => promise);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get one object from a store by its key.
|
* Get one object from a store by its key.
|
||||||
*/
|
*/
|
||||||
|
@ -1297,7 +1297,11 @@ export interface ExchangeWireFeesRecord {
|
|||||||
* Coins used for a payment, with signatures authorizing the payment and the
|
* Coins used for a payment, with signatures authorizing the payment and the
|
||||||
* coins with remaining value updated to accomodate for a payment.
|
* coins with remaining value updated to accomodate for a payment.
|
||||||
*/
|
*/
|
||||||
export type PayCoinInfo = Array<{ updatedCoin: CoinRecord, sig: CoinPaySig }>;
|
export interface PayCoinInfo {
|
||||||
|
originalCoins: CoinRecord[];
|
||||||
|
updatedCoins: CoinRecord[];
|
||||||
|
sigs: CoinPaySig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1787,8 +1791,6 @@ export interface PurchaseRecord {
|
|||||||
* Set to 0 if no refund was made on the purchase.
|
* Set to 0 if no refund was made on the purchase.
|
||||||
*/
|
*/
|
||||||
timestamp_refund: number;
|
timestamp_refund: number;
|
||||||
|
|
||||||
userAccepted: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -324,6 +324,13 @@ export interface CoinsReturnRecord {
|
|||||||
wire: any;
|
wire: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SpeculativePayData {
|
||||||
|
payCoinInfo: PayCoinInfo;
|
||||||
|
exchangeUrl: string;
|
||||||
|
proposalId: number;
|
||||||
|
proposal: ProposalRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wallet protocol version spoken with the exchange
|
* Wallet protocol version spoken with the exchange
|
||||||
@ -651,6 +658,7 @@ export class Wallet {
|
|||||||
private processPreCoinConcurrent = 0;
|
private processPreCoinConcurrent = 0;
|
||||||
private processPreCoinThrottle: {[url: string]: number} = {};
|
private processPreCoinThrottle: {[url: string]: number} = {};
|
||||||
private timerGroup: TimerGroup;
|
private timerGroup: TimerGroup;
|
||||||
|
private speculativePayData: SpeculativePayData | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of identifiers for running operations.
|
* Set of identifiers for running operations.
|
||||||
@ -971,7 +979,7 @@ export class Wallet {
|
|||||||
payCoinInfo: PayCoinInfo,
|
payCoinInfo: PayCoinInfo,
|
||||||
chosenExchange: string): Promise<void> {
|
chosenExchange: string): Promise<void> {
|
||||||
const payReq: PayReq = {
|
const payReq: PayReq = {
|
||||||
coins: payCoinInfo.map((x) => x.sig),
|
coins: payCoinInfo.sigs,
|
||||||
exchange: chosenExchange,
|
exchange: chosenExchange,
|
||||||
merchant_pub: proposal.contractTerms.merchant_pub,
|
merchant_pub: proposal.contractTerms.merchant_pub,
|
||||||
order_id: proposal.contractTerms.order_id,
|
order_id: proposal.contractTerms.order_id,
|
||||||
@ -990,7 +998,7 @@ export class Wallet {
|
|||||||
|
|
||||||
await this.q()
|
await this.q()
|
||||||
.put(Stores.purchases, t)
|
.put(Stores.purchases, t)
|
||||||
.putAll(Stores.coins, payCoinInfo.map((pci) => pci.updatedCoin))
|
.putAll(Stores.coins, payCoinInfo.updatedCoins)
|
||||||
.finish();
|
.finish();
|
||||||
this.badge.showNotification();
|
this.badge.showNotification();
|
||||||
this.notifier.notify();
|
this.notifier.notify();
|
||||||
@ -1048,17 +1056,53 @@ export class Wallet {
|
|||||||
console.log("not confirming payment, insufficient coins");
|
console.log("not confirming payment, insufficient coins");
|
||||||
return "insufficient-balance";
|
return "insufficient-balance";
|
||||||
}
|
}
|
||||||
const {exchangeUrl, cds} = res;
|
|
||||||
|
|
||||||
const ds = await this.cryptoApi.signDeposit(proposal.contractTerms, cds);
|
const sd = await this.getSpeculativePayData(proposalId);
|
||||||
await this.recordConfirmPay(proposal, ds, exchangeUrl);
|
if (!sd) {
|
||||||
|
const { exchangeUrl, cds } = res;
|
||||||
|
const payCoinInfo = await this.cryptoApi.signDeposit(proposal.contractTerms, cds);
|
||||||
|
await this.recordConfirmPay(proposal, payCoinInfo, exchangeUrl);
|
||||||
|
} else {
|
||||||
|
await this.recordConfirmPay(sd.proposal, sd.payCoinInfo, sd.exchangeUrl);
|
||||||
|
}
|
||||||
|
|
||||||
return "paid";
|
return "paid";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the speculative pay data, but only if coins have not changed in between.
|
||||||
|
*/
|
||||||
|
async getSpeculativePayData(proposalId: number): Promise<SpeculativePayData | undefined> {
|
||||||
|
const sp = this.speculativePayData;
|
||||||
|
if (!sp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sp.proposalId != proposalId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const coinKeys = sp.payCoinInfo.updatedCoins.map(x => x.coinPub);
|
||||||
|
const coins = await this.q().getMany(Stores.coins, coinKeys);
|
||||||
|
for (let i = 0; i < coins.length; i++) {
|
||||||
|
const specCoin = sp.payCoinInfo.originalCoins[i];
|
||||||
|
const currentCoin = coins[i];
|
||||||
|
|
||||||
|
// Coin does not exist anymore!
|
||||||
|
if (!currentCoin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Amounts.cmp(specCoin.currentAmount, currentCoin.currentAmount) != 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if payment for an offer is possible, or if the offer has already
|
* Check if payment for an offer is possible, or if the offer has already
|
||||||
* been payed for.
|
* been payed for.
|
||||||
|
*
|
||||||
|
* Also speculatively computes the signature for the payment to make the payment
|
||||||
|
* look faster to the user.
|
||||||
*/
|
*/
|
||||||
async checkPay(proposalId: number): Promise<CheckPayResult> {
|
async checkPay(proposalId: number): Promise<CheckPayResult> {
|
||||||
const proposal = await this.q().get(Stores.proposals, proposalId);
|
const proposal = await this.q().get(Stores.proposals, proposalId);
|
||||||
@ -1089,6 +1133,19 @@ export class Wallet {
|
|||||||
console.log("not confirming payment, insufficient coins");
|
console.log("not confirming payment, insufficient coins");
|
||||||
return { status: "insufficient-balance" };
|
return { status: "insufficient-balance" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only create speculative signature if we don't already have one for this proposal
|
||||||
|
if ((!this.speculativePayData) || (this.speculativePayData && this.speculativePayData.proposalId != proposalId)) {
|
||||||
|
const { exchangeUrl, cds } = res;
|
||||||
|
const payCoinInfo = await this.cryptoApi.signDeposit(proposal.contractTerms, cds);
|
||||||
|
this.speculativePayData = {
|
||||||
|
exchangeUrl,
|
||||||
|
payCoinInfo,
|
||||||
|
proposal,
|
||||||
|
proposalId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return { status: "payment-possible", coinSelection: res };
|
return { status: "payment-possible", coinSelection: res };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2673,7 +2730,7 @@ export class Wallet {
|
|||||||
|
|
||||||
console.log("pci", payCoinInfo);
|
console.log("pci", payCoinInfo);
|
||||||
|
|
||||||
const coins = payCoinInfo.map((pci) => ({ coinPaySig: pci.sig }));
|
const coins = payCoinInfo.sigs.map((s) => ({ coinPaySig: s }));
|
||||||
|
|
||||||
const coinsReturnRecord: CoinsReturnRecord = {
|
const coinsReturnRecord: CoinsReturnRecord = {
|
||||||
coins,
|
coins,
|
||||||
@ -2686,7 +2743,7 @@ export class Wallet {
|
|||||||
|
|
||||||
await this.q()
|
await this.q()
|
||||||
.put(Stores.coinsReturns, coinsReturnRecord)
|
.put(Stores.coinsReturns, coinsReturnRecord)
|
||||||
.putAll(Stores.coins, payCoinInfo.map((pci) => pci.updatedCoin))
|
.putAll(Stores.coins, payCoinInfo.updatedCoins)
|
||||||
.finish();
|
.finish();
|
||||||
this.badge.showNotification();
|
this.badge.showNotification();
|
||||||
this.notifier.notify();
|
this.notifier.notify();
|
||||||
|
Loading…
Reference in New Issue
Block a user