on master help sign in
fix: handle infinite loop from networking reset and consider transaction pending until keyfile available
072c6ebMatt Condon (shrugs)
src/components/DownloadKeyfileButton.js+7−7
@@ -1,19 +1,19 @@
1import React from 'react';
1import React from 'react';
2import { B } from 'indigo-react';
2import { B } from 'indigo-react';
3
3
4−import useKeyfileGenerator from 'lib/useKeyfileGenerator';
5−
6import { DownloadButton } from 'components/Buttons';
4import { DownloadButton } from 'components/Buttons';
7
5
8export default function DownloadKeyfileButton({
6export default function DownloadKeyfileButton({
9− networkSeed,
7+ // useKeyfileGenerator.bind
8+ generating,
9+ available,
10+ downloaded,
11+ download,
12+
13+ // from caller
10 className,
14 className,
11 ...rest
15 ...rest
12}) {
16}) {
13− const { generating, available, downloaded, download } = useKeyfileGenerator(
14− networkSeed
15− );
16−
17 const showHelp = !generating && !available;
17 const showHelp = !generating && !available;
18
18
19 return (
19 return (
src/indigo-react/components/IconButton.js+1−1
@@ -10,7 +10,7 @@ export default function IconButton({
10 disabled = false,
10 disabled = false,
11 secondary = false,
11 secondary = false,
12 solid = false,
12 solid = false,
13− flush = false, // whether or not the icon should include horizontal padding
13+ flush = false, // if true, remove horizontal padding
14 ...rest
14 ...rest
15}) {
15}) {
16 return (
16 return (
src/indigo-react/components/Input.js+2−2
@@ -4,7 +4,7 @@ import cn from 'classnames';
4import Flex from './Flex';
4import Flex from './Flex';
5import { ErrorText } from './Typography';
5import { ErrorText } from './Typography';
6
6
7−export default function Input({
7+export default React.memo(function Input({
8 // visuals
8 // visuals
9 name,
9 name,
10 label,
10 label,
@@ -115,4 +115,4 @@ export default function Input({
115 )}
115 )}
116 </Flex>
116 </Flex>
117 );
117 );
118−}
118+});
src/lib/useEthereumTransaction.js+4−6
@@ -16,6 +16,7 @@ import {
16import * as need from './need';
16import * as need from './need';
17import useLifecycle from './useLifecycle';
17import useLifecycle from './useLifecycle';
18import { ensureFundsFor } from './tank';
18import { ensureFundsFor } from './tank';
19+import useDeepEqualReference from './useDeepEqualReference';
19
20
20const STATE = {
21const STATE = {
21 NONE: 'NONE',
22 NONE: 'NONE',
@@ -199,7 +200,7 @@ export default function useEthereumTransaction(
199 })();
200 })();
200 });
201 });
201
202
202− const values = {
203+ const values = useDeepEqualReference({
203 isDefaultState,
204 isDefaultState,
204 initializing,
205 initializing,
205 construct,
206 construct,
@@ -223,10 +224,7 @@ export default function useEthereumTransaction(
223 nonce,
224 nonce,
224 chainId,
225 chainId,
225 needFunds,
226 needFunds,
226− };
227+ });
227
228
228− return {
229− ...values,
230− bind: values,
229+ return { ...values, bind: values };
231− };
232}
230}
src/lib/useKeyfileGenerator.js+11−3
@@ -5,6 +5,7 @@ import * as ob from 'urbit-ob';
5
5
6import { usePointCache } from 'store/pointCache';
6import { usePointCache } from 'store/pointCache';
7import { useWallet } from 'store/wallet';
7import { useWallet } from 'store/wallet';
8+import { usePointCursor } from 'store/pointCursor';
8
9
9import * as need from 'lib/need';
10import * as need from 'lib/need';
10
11
@@ -15,7 +16,6 @@ import {
15 compileNetworkingKey,
16 compileNetworkingKey,
16} from './keys';
17} from './keys';
17import useCurrentPermissions from './useCurrentPermissions';
18import useCurrentPermissions from './useCurrentPermissions';
18−import { usePointCursor } from 'store/pointCursor';
19
19
20export default function useKeyfileGenerator(manualNetworkSeed) {
20export default function useKeyfileGenerator(manualNetworkSeed) {
21 const { getDetails } = usePointCache();
21 const { getDetails } = usePointCache();
@@ -34,7 +34,11 @@ export default function useKeyfileGenerator(manualNetworkSeed) {
34
34
35 const hasNetworkingKeys = networkRevision > 0;
35 const hasNetworkingKeys = networkRevision > 0;
36 const available =
36 const available =
37− (isOwner || isManagementProxy) && hasNetworkingKeys && keyfile;
37+ (isOwner || isManagementProxy) && hasNetworkingKeys && !!keyfile;
38+
39+ console.log(
40+ `keyfile available (${available}) for revision ${networkRevision}`
41+ );
38
42
39 const generate = useCallback(async () => {
43 const generate = useCallback(async () => {
40 console.log('generating for network revision', networkRevision);
44 console.log('generating for network revision', networkRevision);
@@ -51,6 +55,7 @@ export default function useKeyfileGenerator(manualNetworkSeed) {
51
55
52 if (Nothing.hasInstance(networkSeed)) {
56 if (Nothing.hasInstance(networkSeed)) {
53 setGenerating(false);
57 setGenerating(false);
58+ console.log('manual network seed not provided and key not derivable');
54 return;
59 return;
55 }
60 }
56
61
@@ -60,6 +65,7 @@ export default function useKeyfileGenerator(manualNetworkSeed) {
60
65
61 if (!keysMatchChain(pair, _details)) {
66 if (!keysMatchChain(pair, _details)) {
62 setGenerating(false);
67 setGenerating(false);
68+ console.log('derived network keys do not match on-chain details');
63 return;
69 return;
64 }
70 }
65
71
@@ -90,10 +96,12 @@ export default function useKeyfileGenerator(manualNetworkSeed) {
90 generate();
96 generate();
91 }, [generate]);
97 }, [generate]);
92
98
93− return {
99+ const values = {
94 generating,
100 generating,
95 available,
101 available,
96 downloaded,
102 downloaded,
97 download,
103 download,
98 };
104 };
105+
106+ return { ...values, bind: values };
99}
107}
src/lib/useSetState.js+3−3
@@ -1,15 +1,15 @@
1−import { useCallback, useState } from 'react';
1+import { useCallback, useRef, useState } from 'react';
2
2
3export default function useSetState(initialState = {}) {
3export default function useSetState(initialState = {}) {
4+ const initialStateRef = useRef(initialState);
4 const [state, _setState] = useState(initialState);
5 const [state, _setState] = useState(initialState);
5 const setState = useCallback(
6 const setState = useCallback(
6 items => _setState(state => ({ ...state, ...items })),
7 items => _setState(state => ({ ...state, ...items })),
7 [_setState]
8 [_setState]
8 );
9 );
9
10
10− const reset = useCallback(() => _setState(initialState), [
11+ const reset = useCallback(() => _setState(initialStateRef.current), [
11 _setState,
12 _setState,
12− initialState,
13 ]);
13 ]);
14
14
15 return [state, setState, reset];
15 return [state, setState, reset];
src/views/Admin/AdminHome.js+8−4
@@ -2,20 +2,23 @@ import React, { useCallback } from 'react';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3import { Grid, B } from 'indigo-react';
3import { Grid, B } from 'indigo-react';
4
4
5−import { ForwardButton } from 'components/Buttons';
6−
7import { useWallet } from 'store/wallet';
5import { useWallet } from 'store/wallet';
8
6
9−import ViewHeader from 'components/ViewHeader';
10import { useLocalRouter } from 'lib/LocalRouter';
7import { useLocalRouter } from 'lib/LocalRouter';
8+import useCurrentPermissions from 'lib/useCurrentPermissions';
9+import useKeyfileGenerator from 'lib/useKeyfileGenerator';
10+
11+import { ForwardButton } from 'components/Buttons';
12+import ViewHeader from 'components/ViewHeader';
11import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
13import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
12import MiniBackButton from 'components/MiniBackButton';
14import MiniBackButton from 'components/MiniBackButton';
13−import useCurrentPermissions from 'lib/useCurrentPermissions';
14
15
15export default function AdminHome() {
16export default function AdminHome() {
16 const { push, names, pop } = useLocalRouter();
17 const { push, names, pop } = useLocalRouter();
17 const { urbitWallet } = useWallet();
18 const { urbitWallet } = useWallet();
18
19
20+ const { bind: keyfileBind } = useKeyfileGenerator();
21+
19 const { isOwner, canTransfer, isTransferProxySet } = useCurrentPermissions();
22 const { isOwner, canTransfer, isTransferProxySet } = useCurrentPermissions();
20
23
21 const goRedownload = useCallback(() => push(names.REDOWNLOAD), [push, names]);
24 const goRedownload = useCallback(() => push(names.REDOWNLOAD), [push, names]);
@@ -57,6 +60,7 @@ export default function AdminHome() {
57 <Grid.Item
60 <Grid.Item
58 full
61 full
59 as={DownloadKeyfileButton}
62 as={DownloadKeyfileButton}
63+ {...keyfileBind}
60 detail="Download your Arvo Keyfile"
64 detail="Download your Arvo Keyfile"
61 />
65 />
62 <Grid.Divider />
66 <Grid.Divider />
src/views/Admin/AdminNetworkingKeys.js+25−18
@@ -1,4 +1,4 @@
1−import React, { useCallback, useEffect, useState } from 'react';
1+import React, { useCallback, useEffect, useRef, useState } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3import {
3import {
4 Grid,
4 Grid,
@@ -39,6 +39,7 @@ import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
39import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
39import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
40import NoticeBox from 'components/NoticeBox';
40import NoticeBox from 'components/NoticeBox';
41import { useHexInput, useCheckboxInput } from 'lib/useInputs';
41import { useHexInput, useCheckboxInput } from 'lib/useInputs';
42+import useKeyfileGenerator from 'lib/useKeyfileGenerator';
42
43
43const chainKeyProp = name => d =>
44const chainKeyProp = name => d =>
44 d[name] === CURVE_ZERO_ADDR ? Nothing() : Just(d[name]);
45 d[name] === CURVE_ZERO_ADDR ? Nothing() : Just(d[name]);
@@ -61,19 +62,29 @@ function useSetKeys() {
61 const _point = need.point(pointCursor);
62 const _point = need.point(pointCursor);
62 const _contracts = need.contracts(contracts);
63 const _contracts = need.contracts(contracts);
63
64
65+ const randomSeed = useRef();
66+
64 // NOTE: nd = 'nondeterministic' (can also be a 'manual' seed)
67 // NOTE: nd = 'nondeterministic' (can also be a 'manual' seed)
65 const [ndNetworkSeed, setNdNetworkSeed] = useState();
68 const [ndNetworkSeed, setNdNetworkSeed] = useState();
66
69
70+ const {
71+ available: keyfileAvailable,
72+ bind: keyfileBind,
73+ } = useKeyfileGenerator(ndNetworkSeed);
74+
67 const {
75 const {
68 isDefaultState,
76 isDefaultState,
69 construct: _construct,
77 construct: _construct,
70 unconstruct,
78 unconstruct,
71 broadcasting,
79 broadcasting,
72− confirmed,
80+ confirmed: _confirmed,
73 inputsLocked,
81 inputsLocked,
74 bind,
82 bind,
75 } = useEthereumTransaction(GAS_LIMITS.CONFIGURE_KEYS);
83 } = useEthereumTransaction(GAS_LIMITS.CONFIGURE_KEYS);
76
84
85+ // only treat the transaction as confirmed once we also have keys to download
86+ const confirmed = _confirmed && keyfileAvailable;
87+
77 // sync point details after success
88 // sync point details after success
78 useEffect(() => {
89 useEffect(() => {
79 if (confirmed) {
90 if (confirmed) {
@@ -98,14 +109,14 @@ function useSetKeys() {
98 revision: networkRevision + 1,
109 revision: networkRevision + 1,
99 });
110 });
100
111
101− return networkSeed.matchWith({
112+ if (Just.hasInstance(networkSeed)) {
102− Nothing: () => {
113+ return networkSeed.value;
103− const ndSeed = randomHex(64);
114+ }
115+
116+ randomSeed.current = randomSeed.current || randomHex(64);
104− setNdNetworkSeed(ndSeed);
117+ setNdNetworkSeed(randomSeed.current);
105− return ndSeed;
118+
106− },
119+ return randomSeed.current;
107− Just: p => p.value,
108− });
109 }
120 }
110 },
121 },
111 [_point, authMnemonic, getDetails, urbitWallet, wallet]
122 [_point, authMnemonic, getDetails, urbitWallet, wallet]
@@ -139,6 +150,7 @@ function useSetKeys() {
139 ndNetworkSeed,
150 ndNetworkSeed,
140 inputsLocked,
151 inputsLocked,
141 bind,
152 bind,
153+ keyfileBind,
142 };
154 };
143}
155}
144
156
@@ -162,8 +174,8 @@ export default function AdminNetworkingKeys() {
162 broadcasting,
174 broadcasting,
163 confirmed,
175 confirmed,
164 inputsLocked,
176 inputsLocked,
165− ndNetworkSeed,
166 bind,
177 bind,
178+ keyfileBind,
167 } = useSetKeys();
179 } = useSetKeys();
168
180
169 const [showNetworkSeedInput, { data: showNetworkSeed }] = useCheckboxInput({
181 const [showNetworkSeedInput, { data: showNetworkSeed }] = useCheckboxInput({
@@ -205,10 +217,10 @@ export default function AdminNetworkingKeys() {
205 }
217 }
206 }, [
218 }, [
207 construct,
219 construct,
220+ unconstruct,
208 isDiscontinuity,
221 isDiscontinuity,
209 networkSeed,
222 networkSeed,
210 showNetworkSeed,
223 showNetworkSeed,
211− unconstruct,
212 validDiscontinuity,
224 validDiscontinuity,
213 validNetworkSeed,
225 validNetworkSeed,
214 ]);
226 ]);
@@ -334,12 +346,7 @@ export default function AdminNetworkingKeys() {
334 <Grid.Item full as={NoticeBox} className="mb3">
346 <Grid.Item full as={NoticeBox} className="mb3">
335 You need this keyfile to authenticate with Arvo.
347 You need this keyfile to authenticate with Arvo.
336 </Grid.Item>
348 </Grid.Item>
337− <Grid.Item
338− full
339− as={DownloadKeyfileButton}
349+ <Grid.Item full as={DownloadKeyfileButton} solid {...keyfileBind} />
340− solid
341− networkSeed={ndNetworkSeed}
342− />
343 </>
350 </>
344 )}
351 )}
345
352