lnurlpay: replace realm with async-storage.

also stop checking pending transactions, insted delete every lnurlpay
thing older than 30 days (a check is done in 10% of the times the wallet
is turned on).
This commit is contained in:
fiatjaf
2020-05-29 01:14:27 -03:00
parent 9fe0ad1ad6
commit 096901e019
6 changed files with 75 additions and 199 deletions
+1 -1
View File
@@ -11,6 +11,7 @@
"prettier": "prettier --check --write '**/*.ts*'"
},
"dependencies": {
"@react-native-community/async-storage": "^1.10.1",
"@react-native-community/masked-view": "^0.1.6",
"@tradle/react-native-http": "^2.0.1",
"assert": "^1.5.0",
@@ -64,7 +65,6 @@
"react-navigation": "^4.0.10",
"react-navigation-stack": "^2.0.16",
"readable-stream": "^1.0.33",
"realm": "^3.6.0",
"rn-fetch-blob": "^0.12.0",
"sha.js": "^2.4.11",
"stream-browserify": "^1.0.0",
+56 -177
View File
@@ -1,24 +1,16 @@
import { action } from 'mobx';
import RNFetchBlob from 'rn-fetch-blob';
import Realm from 'realm';
import { when } from 'mobx';
import { LNURLPaySuccessAction } from 'js-lnurl';
import NodeInfoStore from './../stores/NodeInfoStore';
import SettingsStore from './../stores/SettingsStore';
import Payment from './../models/Payment';
import AsyncStorage from '@react-native-community/async-storage';
export interface LnurlPayTransaction {
paymentHash: string;
pending: boolean;
domain: string;
lnurl: string;
metadata: LnurlPayMetadata;
metadata_hash: string;
successAction: LnurlPaySuccessAction;
}
time: number;
interface LnurlPayMetadata {
descriptionHash: string;
metadata: string;
metadata: string; // only after an independent load from AsyncStorage.
}
interface LnurlPaySuccessAction {
@@ -30,150 +22,57 @@ interface LnurlPaySuccessAction {
ciphertext: string;
}
const LnurlPayTransactionSchema = {
name: 'LnurlPayTransaction',
primaryKey: 'paymentHash',
properties: {
paymentHash: 'string',
pending: { type: 'bool', default: true },
domain: { type: 'string', indexed: true },
lnurl: 'string',
metadata: 'LnurlPayMetadata',
successAction: 'LnurlPaySuccessAction'
}
};
const LnurlPayMetadataSchema = {
name: 'LnurlPayMetadata',
primaryKey: 'descriptionHash',
properties: {
descriptionHash: 'string',
metadata: 'string'
}
};
const LnurlPaySuccessActionSchema = {
name: 'LnurlPaySuccessAction',
properties: {
tag: { type: 'string', default: 'noop' },
description: 'string?',
url: 'string?',
message: 'string?',
iv: 'string?',
ciphertext: 'string?'
}
};
interface LnurlPayMetadataEntry {
metadata: string;
last_stored: number;
}
export default class LnurlPayStore {
paymentHash: string | null;
domain: string | null;
successAction: LNURLPaySuccessAction | null;
realm: any;
settingsStore: SettingsStore;
nodeInfoStore: NodeInfoStore;
constructor(settingsStore: SettingsStore, nodeInfoStore: NodeInfoStore) {
this.settingsStore = settingsStore;
this.nodeInfoStore = nodeInfoStore;
this.realm = new Realm({
path: `lnurl-${nodeInfoStore.nodeInfo.identity_pubkey}.realm`,
schema: [
LnurlPayTransactionSchema,
LnurlPaySuccessActionSchema,
LnurlPayMetadataSchema
]
});
when(
() =>
!!this.settingsStore.host &&
!!this.settingsStore.port &&
!!this.settingsStore.macaroonHex,
() => this.checkPending()
);
constructor() {
if (Math.random() < 0.1) {
setTimeout(() => {
this.deleteOld();
}, 100000);
}
}
checkPending = () => {
const { host, port, macaroonHex, sslVerification } = this.settingsStore;
// remove all pending stored lnurl-pay transactions if we can't find them on lnd
// and remove their pending status if we find them as completed
let pending = this.realm
.objects('LnurlPayTransaction')
.filtered('pending == true');
if (pending.length === 0) {
// only if there's a pending tx on realm we'll do this expensive query
return;
}
const url = `https://${host}${
port ? ':' + port : ''
}/v1/payments?include_incomplete=true`;
const headers = {
'Grpc-Metadata-macaroon': macaroonHex
};
RNFetchBlob.config({
trusty: !sslVerification || true
})
.fetch('get', url)
.then((response: any) => {
const status = response.info().status;
if (status == 200) {
const data = response.json();
let { payments } = data;
for (let i = 0; i < pending.length; i++) {
this.resolvePendingHash(
payments,
pending[i].paymentHash
);
}
} else {
const error = response.json();
const { message } = error;
console.log(
`error checking pending lnurl-pay transactions: ${err.message}`
);
}
})
.catch(err => {
console.log(
`error checking pending lnurl-pay transactions: ${err.toString()}`
);
});
};
resolvePendingHash = (payments: Payment[], pendingHash: string) => {
for (let j = 0; j < payments.length; j++) {
let payment: Payment = payments[j];
if (payment.payment_hash === pendingHash) {
// a match!
switch (payment.status) {
case 'SUCCEEDED':
this.acknowledge(pendingHash);
return;
case 'FAILED':
this.clear(pendingHash);
return;
default:
// leave it as is
return;
deleteOld = () => {
// delete all lnurlpay keys older than 30 days
const daysago30 = new Date().getTime() + 1000 * 60 * 60 * 24 * 30;
const allKeys = await AsyncStorage.getAllKeys();
var toRemove = [];
for (let i = 0; i < allKeys.length; i++) {
let key = allKeys[i];
if (key.slice(0, 9) === 'lnurlpay:') {
let item = JSON.parse(await AsyncStorage.getItem(key));
if (
(item.last_stored && item.last_stored < daysago30) ||
(item.time && item.time < daysago30)
) {
toRemove.push(key);
}
}
}
// if we got here it's because there is no match / the payment is not on lnd
this.clear(pendingHash);
AsyncStorage.multiRemove(toRemove);
};
@action
public load = (paymentHash: string): LnurlPayTransaction => {
return this.realm.objectForPrimaryKey(
'LnurlPayTransaction',
paymentHash
);
let lnurlpaytx = await AsyncStorage.getItem('lnurlpay:' + paymentHash);
if (lnurlpaytx) {
lnurlpaytx = JSON.parse(lnurlpaytx);
let metadata = await AsyncStorage.getItem(lnurlpaytx.metadata_hash);
if (metadata) {
lnurlpaytx.metadata = JSON.parse(metadata);
}
}
return lnurlpaytx;
};
@action
@@ -181,51 +80,31 @@ export default class LnurlPayStore {
paymentHash: string,
domain: string,
lnurl: string,
metadata: any,
metadata: string,
descriptionHash: string,
successAction: LNURLPaySuccessAction
) => {
this.realm.write(() => {
this.realm.create(
'LnurlPayTransaction',
{
const now = new Date().getTime();
AsyncStorage.multiSet([
[
'lnurlpay:' + paymentHash,
JSON.stringify({
paymentHash,
domain,
lnurl,
metadata,
successAction
},
true
);
});
successAction,
time: now
})
],
[
'lnurlpay:' + descriptionHash,
JSON.stringify({ metadata, last_stored: now })
]
]);
this.paymentHash = paymentHash;
this.successAction = successAction;
this.domain = domain;
};
@action
public acknowledge = (paymentHash: string) => {
this.realm.write(() => {
this.realm.create(
'LnurlPayTransaction',
{
paymentHash,
pending: false
},
true
);
});
};
@action
public clear = (paymentHash: string) => {
this.realm.write(() => {
this.realm.delete(
this.realm.objectForPrimaryKey(
'LnurlPayTransaction',
paymentHash
)
);
});
};
}
+3 -7
View File
@@ -21,13 +21,9 @@ export default class LnurlPayHistorical extends React.Component<
LnurlPayHistoricalProps,
LnurlPayHistoricalState
> {
constructor(props: any) {
super(props);
this.state = {
showLnurlSuccess: false
};
}
state = {
showLnurlSuccess: false
};
render() {
const { navigation, lnurlpaytx, preimage, SettingsStore } = this.props;
+2 -4
View File
@@ -119,10 +119,8 @@ export default class LnurlPay extends React.Component<
payment_hash,
domain,
lnurl.lnurlText,
{
metadata: lnurl.metadata,
descriptionHash: description_hash
},
lnurl.metadata,
description_hash,
successAction
);
navigation.navigate('PaymentRequest');
+13 -1
View File
@@ -26,6 +26,17 @@ interface PaymentProps {
@inject('UnitsStore', 'SettingsStore', 'LnurlPayStore')
@observer
export default class PaymentView extends React.Component<PaymentProps> {
state = {
lnurlpaytx: null
};
async componentDidMount() {
let lnurlpaytx = await LnurlPayStore.load(payment_hash);
if (lnurlpaytx) {
this.setState({ lnurlpaytx });
}
}
render() {
const {
navigation,
@@ -46,7 +57,6 @@ export default class PaymentView extends React.Component<PaymentProps> {
enhancedPath
} = payment;
const date = getCreationTime;
const lnurlpaytx = LnurlPayStore.load(payment_hash);
const BackButton = () => (
<Icon
@@ -64,6 +74,8 @@ export default class PaymentView extends React.Component<PaymentProps> {
? PrivacyUtils.hideValue(getAmount(getFee), 3, true)
: getAmount(getFee);
const lnurlpaytx = this.state.lnurlpaytx;
return (
<ScrollView
style={
-9
View File
@@ -29,20 +29,11 @@ export default class SendingLightning extends React.Component<
{}
> {
componentDidMount = () => {
const { TransactionsStore, LnurlPayStore } = this.props;
const {
payment_route,
payment_hash,
payment_error
} = TransactionsStore;
const { acknowledge, clear } = LnurlPayStore;
when(() => payment_route).then(() => {
acknowledge(payment_hash);
});
when(() => payment_error).then(() => {
clear(payment_hash);
});
};
getBackgroundColor() {