Skip to content

Commit 3a7a74e

Browse files
committed
Support pausing and resuming scanning
1 parent c3e658d commit 3a7a74e

File tree

6 files changed

+104
-18
lines changed

6 files changed

+104
-18
lines changed

packages/mobile-app/app/(tabs)/contacts.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
11
import { Button } from "@ironfish/ui";
22
import { View, Text } from "react-native";
3-
import { wallet } from "../../data/wallet/wallet";
43

5-
import { Network } from "../../data/constants";
4+
import { useFacade } from "../../data/facades";
65

76
export default function Contacts() {
7+
const facade = useFacade();
8+
9+
const walletStatus = facade.getWalletStatus.useQuery(undefined, {
10+
refetchInterval: 1000,
11+
});
12+
13+
const pauseSyncing = facade.pauseSyncing.useMutation();
14+
const resumeSyncing = facade.resumeSyncing.useMutation();
15+
816
return (
917
<View>
10-
<Text>Contacts</Text>
18+
{walletStatus.data && (
19+
<>
20+
<Text>{`Scan status: ${walletStatus.data.status}`}</Text>
21+
<Text>{`Latest known block: ${walletStatus.data.latestKnownBlock}`}</Text>
22+
</>
23+
)}
24+
<Text>{}</Text>
25+
<Button
26+
onPress={async () => {
27+
await resumeSyncing.mutateAsync(undefined);
28+
}}
29+
>
30+
Resume Syncing
31+
</Button>
1132
<Button
12-
onPress={() => {
13-
wallet.scan(Network.TESTNET);
33+
onPress={async () => {
34+
await pauseSyncing.mutateAsync(undefined);
1435
}}
1536
>
16-
Request Blocks
37+
Pause Syncing
1738
</Button>
1839
</View>
1940
);

packages/mobile-app/data/blockchain.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ class BlockchainClass {
122122
start: { hash: Uint8Array; sequence: number },
123123
end: { hash: Uint8Array; sequence: number },
124124
onBlock: (block: LightBlock) => unknown,
125+
abort: AbortSignal,
125126
) {
126127
await this.lockRequest(async () => {
127128
const manifest = await WalletServerChunksApi.getChunksManifest(network);
@@ -131,6 +132,8 @@ class BlockchainClass {
131132
let lastHash: null | Uint8Array = null;
132133

133134
const onBlockInner = (block: LightBlock) => {
135+
if (abort.aborted) return;
136+
134137
// TODO: Errors thrown here don't propagate
135138
if (!lastHash && !Uint8ArrayUtils.areEqual(block.hash, start.hash)) {
136139
console.error(
@@ -170,8 +173,13 @@ class BlockchainClass {
170173
chunk.range.start <= end.sequence,
171174
);
172175
for (const chunk of finalizedChunks) {
176+
if (abort.aborted) break;
177+
173178
await WalletServerChunksApi.getChunkBlockAndByteRanges(network, chunk);
179+
180+
if (abort.aborted) break;
174181
readPromise = readPromise.then(async () => {
182+
if (abort.aborted) return;
175183
await WalletServerChunksApi.readChunkBlocks(
176184
network,
177185
chunk,
@@ -187,14 +195,20 @@ class BlockchainClass {
187195
const downloadSize = 100;
188196

189197
for (let i = serverStart; i <= lastSequence; i += downloadSize) {
198+
if (abort.aborted) break;
199+
190200
const endIndex = Math.min(i + downloadSize - 1, lastSequence);
191201
console.log("fetching blocks from wallet server", i, endIndex);
192202
const download = await WalletServerApi.getBlockRange(
193203
network,
194204
i,
195205
endIndex,
196206
);
207+
208+
if (abort.aborted) break;
209+
197210
readPromise = readPromise.then(async () => {
211+
if (abort.aborted) return;
198212
await this.readWalletServerBlocks(download, onBlockInner);
199213
});
200214
}

packages/mobile-app/data/chainProcessor.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,24 @@ import * as Uint8ArrayUtils from "../utils/uint8Array";
1919
*/
2020
export class ChainProcessor {
2121
readonly network: Network;
22+
abort: AbortSignal;
2223
head: Readonly<{ hash: Uint8Array; sequence: number }> | null = null;
2324
onAdd: (block: LightBlock) => unknown;
2425
onRemove: (block: LightBlock) => unknown;
2526

2627
constructor(options: {
2728
network: Network;
29+
abort: AbortSignal;
2830
onAdd: (block: LightBlock) => unknown;
2931
onRemove: (block: LightBlock) => unknown;
3032
}) {
3133
this.network = options.network;
34+
this.abort = options.abort;
3235
this.onAdd = options.onAdd;
3336
this.onRemove = options.onRemove;
3437
}
3538

36-
async update({ signal }: { signal?: AbortSignal } = {}): Promise<{
39+
async update(): Promise<{
3740
hashChanged: boolean;
3841
}> {
3942
const oldHash = this.head;
@@ -65,17 +68,26 @@ export class ChainProcessor {
6568
}
6669

6770
for (const block of result.blocksToRemove) {
71+
if (this.abort.aborted)
72+
return { hashChanged: !oldHash || this.head.hash !== oldHash.hash };
73+
6874
this.onRemove(block);
6975
this.head = {
7076
hash: block.previousBlockHash,
7177
sequence: block.sequence - 1,
7278
};
7379
}
7480

75-
await Blockchain.iterateTo(this.network, this.head, chainHead, (block) => {
76-
this.onAdd(block);
77-
this.head = { hash: block.hash, sequence: block.sequence };
78-
});
81+
await Blockchain.iterateTo(
82+
this.network,
83+
this.head,
84+
chainHead,
85+
(block) => {
86+
this.onAdd(block);
87+
this.head = { hash: block.hash, sequence: block.sequence };
88+
},
89+
this.abort,
90+
);
7991

8092
return { hashChanged: !oldHash || this.head.hash !== oldHash.hash };
8193
}

packages/mobile-app/data/facades/wallet/handlers.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
LanguageUtils,
1919
TransactionStatus,
2020
} from "@ironfish/sdk";
21+
import { WalletServerApi } from "../../api/walletServer";
2122

2223
export const walletHandlers = f.facade<WalletHandlers>({
2324
createAccount: f.handler.mutation(
@@ -276,8 +277,8 @@ export const walletHandlers = f.facade<WalletHandlers>({
276277
},
277278
),
278279
getWalletStatus: f.handler.query(async (): Promise<WalletStatus> => {
279-
// TODO: Implement getWalletStatus
280-
return { status: "PAUSED", latestKnownBlock: 0 };
280+
const block = await WalletServerApi.getLatestBlock(Network.TESTNET);
281+
return { status: wallet.scanState.type, latestKnownBlock: block.sequence };
281282
}),
282283
importAccount: f.handler.mutation(
283284
async ({
@@ -312,7 +313,9 @@ export const walletHandlers = f.facade<WalletHandlers>({
312313
};
313314
},
314315
),
315-
pauseSyncing: f.handler.mutation(async () => {}),
316+
pauseSyncing: f.handler.mutation(async () => {
317+
wallet.pauseScan();
318+
}),
316319
removeAccount: f.handler.mutation(async ({ name }: { name: string }) => {
317320
await wallet.removeAccount(name);
318321
}),
@@ -321,7 +324,9 @@ export const walletHandlers = f.facade<WalletHandlers>({
321324
await wallet.renameAccount(name, newName);
322325
},
323326
),
324-
resumeSyncing: f.handler.mutation(async () => {}),
327+
resumeSyncing: f.handler.mutation(async () => {
328+
wallet.scan(Network.TESTNET);
329+
}),
325330
sendTransaction: f.handler.mutation(
326331
async (args: {
327332
accountName: string;

packages/mobile-app/data/facades/wallet/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export type Burn = {
8484
};
8585

8686
export type WalletStatus = {
87-
status: "SYNCING" | "PAUSED";
87+
status: "SCANNING" | "PAUSED" | "IDLE";
8888
latestKnownBlock: number;
8989
};
9090

packages/mobile-app/data/wallet/wallet.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ import { WalletServerApi } from "../api/walletServer";
1616

1717
type StartedState = { type: "STARTED"; db: WalletDb };
1818
type WalletState = { type: "STOPPED" } | { type: "LOADING" } | StartedState;
19+
type ScanState =
20+
// Not scanning, and a scan can be started
21+
| { type: "IDLE" }
22+
// Not scanning, and a scan cannot be started
23+
| { type: "PAUSED" }
24+
// Scanning
25+
| { type: "SCANNING"; abort: AbortController };
1926

2027
function assertStarted(state: WalletState): asserts state is StartedState {
2128
if (state.type !== "STARTED") {
@@ -25,6 +32,7 @@ function assertStarted(state: WalletState): asserts state is StartedState {
2532

2633
class Wallet {
2734
state: WalletState = { type: "STOPPED" };
35+
scanState: ScanState = { type: "IDLE" };
2836

2937
async start() {
3038
if (this.state.type !== "STOPPED") {
@@ -376,14 +384,18 @@ class Wallet {
376384
async scan(network: Network): Promise<boolean> {
377385
assertStarted(this.state);
378386

387+
if (this.scanState.type === "SCANNING") {
388+
return false;
389+
}
390+
const abort = new AbortController();
391+
this.scanState = { type: "SCANNING", abort };
392+
379393
const cache = new WriteCache(this.state.db, network);
380394

381395
let blockProcess = Promise.resolve();
382396
let performanceTimer = performance.now();
383397
let finished = false;
384398

385-
// todo: lock scanning
386-
387399
const dbAccounts = await this.state.db.getAccounts();
388400
let accounts = dbAccounts.map((account) => {
389401
return {
@@ -410,8 +422,13 @@ class Wallet {
410422

411423
const chainProcessor = new ChainProcessor({
412424
network,
425+
abort: abort.signal,
413426
onAdd: (block) => {
414427
blockProcess = blockProcess.then(async () => {
428+
if (abort.signal.aborted) {
429+
return;
430+
}
431+
415432
assertStarted(this.state);
416433

417434
const prevHash = block.previousBlockHash;
@@ -546,6 +563,10 @@ class Wallet {
546563
},
547564
onRemove: (block) => {
548565
blockProcess = blockProcess.then(() => {
566+
if (abort.signal.aborted) {
567+
return;
568+
}
569+
549570
console.log(`Removing block ${block.sequence}`);
550571

551572
for (const account of accounts) {
@@ -585,11 +606,24 @@ class Wallet {
585606
finished = true;
586607
clearTimeout(saveLoopTimeout);
587608
await saveLoop();
609+
if (this.scanState.abort.signal.aborted) {
610+
this.scanState = this.scanState = this.scanState.abort.signal.aborted
611+
? { type: "PAUSED" }
612+
: { type: "IDLE" };
613+
}
588614
console.log(`finished in ${performance.now() - performanceTimer}ms`);
589615
}
590616

591617
return hashChanged;
592618
}
619+
620+
pauseScan() {
621+
if (this.scanState.type === "SCANNING") {
622+
this.scanState.abort.abort();
623+
} else if (this.scanState.type === "IDLE") {
624+
this.scanState = { type: "PAUSED" };
625+
}
626+
}
593627
}
594628

595629
export const wallet = new Wallet();

0 commit comments

Comments
 (0)