· 5 years ago · Dec 14, 2020, 08:54 PM
1diff --git a/electron-react/package-lock.json b/electron-react/package-lock.json
2index 26fd577a..0ac0f34a 100644
3--- a/electron-react/package-lock.json
4+++ b/electron-react/package-lock.json
5@@ -6002,6 +6002,14 @@
6 "delayed-stream": "~1.0.0"
7 }
8 },
9+ "electron-osx-sign": {
10+ "version": "github:electron/electron-osx-sign#6ba45b2deec3f4f3629010645f92e6506df133ee",
11+ "from": "github:electron/electron-osx-sign#master",
12+ "dev": true,
13+ "requires": {
14+ "common-sequence": "^2.0.0"
15+ }
16+ },
17 "common-sequence": {
18 "version": "2.0.0",
19 "resolved": "https://registry.npmjs.org/common-sequence/-/common-sequence-2.0.0.tgz",
20diff --git a/electron-react/src/components/core/Log/Log.tsx b/electron-react/src/components/core/Log/Log.tsx
21index e30073f6..9eef69bd 100644
22--- a/electron-react/src/components/core/Log/Log.tsx
23+++ b/electron-react/src/components/core/Log/Log.tsx
24@@ -6,8 +6,7 @@ const StyledPaper = styled(Paper)`
25 background-color: #272c34;
26 color: white;
27 width: 100%;
28- min-height: 200px;
29- max-height: 400px;
30+ height: 40vh;
31 padding: ${({ theme }) => `${theme.spacing(1)}px ${theme.spacing(2)}px`};
32 overflow: auto;
33
34diff --git a/electron-react/src/components/core/RadioGroup/RadioGroup.tsx b/electron-react/src/components/core/RadioGroup/RadioGroup.tsx
35index 05615466..c7fa6705 100644
36--- a/electron-react/src/components/core/RadioGroup/RadioGroup.tsx
37+++ b/electron-react/src/components/core/RadioGroup/RadioGroup.tsx
38@@ -1,4 +1,4 @@
39-import React, { ChangeEvent, ReactElement, ReactNode } from 'react';
40+import React, { ChangeEvent, ReactElement, ReactNode, forwardRef } from 'react';
41 import { Controller, ControllerProps, useFormContext } from 'react-hook-form';
42 import { RadioGroup as MaterialRadioGroup, RadioGroupProps } from '@material-ui/core';
43
44@@ -32,7 +32,7 @@ type Props = RadioGroupProps & {
45 boolean?: boolean,
46 };
47
48-function ParseBoolean(props: RadioGroupProps) {
49+const ParseBoolean = forwardRef((props: RadioGroupProps, ref) => {
50 const { onChange, ...rest } = props;
51 const { name } = rest;
52 const { setValue } = useFormContext();
53@@ -47,8 +47,8 @@ function ParseBoolean(props: RadioGroupProps) {
54 }
55 }
56
57- return <MaterialRadioGroup onChange={handleChange} {...rest} />;
58-};
59+ return <MaterialRadioGroup onChange={handleChange} ref={ref} {...rest} />;
60+});
61
62 export default function RadioGroup(props: Props) {
63 const { name, boolean, ...rest } = props;
64diff --git a/electron-react/src/components/plot/add/PlotAdd.tsx b/electron-react/src/components/plot/add/PlotAdd.tsx
65index 5100c052..fb1c8973 100644
66--- a/electron-react/src/components/plot/add/PlotAdd.tsx
67+++ b/electron-react/src/components/plot/add/PlotAdd.tsx
68@@ -51,10 +51,16 @@ export default function PlotAdd() {
69 }, [plotSize, setValue]);
70
71 const handleSubmit: SubmitHandler<FormData> = (data) => {
72+ const { delay } = data;
73+
74 dispatch(plotQueueAdd(fingerprint ? {
75 ...data,
76 fingerprint,
77- } : data));
78+ delay: delay * 60,
79+ } : {
80+ ...data,
81+ delay: delay * 60,
82+ }));
83
84 history.push('/dashboard/plot');
85 }
86diff --git a/electron-react/src/components/plot/add/PlotAddNumberOfPlots.tsx b/electron-react/src/components/plot/add/PlotAddNumberOfPlots.tsx
87index b9ca39b3..334f9ccd 100644
88--- a/electron-react/src/components/plot/add/PlotAddNumberOfPlots.tsx
89+++ b/electron-react/src/components/plot/add/PlotAddNumberOfPlots.tsx
90@@ -62,7 +62,6 @@ export default function PlotAddNumberOfPlots() {
91 control={<Radio />}
92 label="Plot in Parallel"
93 value
94- disabled
95 />
96 <FormControlLabel
97 value={false}
98diff --git a/electron-react/src/components/plot/overview/PlotOverview.tsx b/electron-react/src/components/plot/overview/PlotOverview.tsx
99index ff9bd4ea..0f752289 100644
100--- a/electron-react/src/components/plot/overview/PlotOverview.tsx
101+++ b/electron-react/src/components/plot/overview/PlotOverview.tsx
102@@ -13,7 +13,9 @@ export default function PlotOverview() {
103 return (
104 <Flex flexDirection="column" gap={3}>
105 {loading && (
106- <Loading />
107+ <Flex alignItems="center" justifyContent="center">
108+ <Loading />
109+ </Flex>
110 )}
111
112 {!loading && (
113diff --git a/electron-react/src/components/plot/overview/PlotOverviewPlots.tsx b/electron-react/src/components/plot/overview/PlotOverviewPlots.tsx
114index e530d60f..ed4f5ec7 100644
115--- a/electron-react/src/components/plot/overview/PlotOverviewPlots.tsx
116+++ b/electron-react/src/components/plot/overview/PlotOverviewPlots.tsx
117@@ -5,6 +5,7 @@ import { Card, Flex, Table, FormatBytes } from '@chia/core';
118 import { TableCell, TableRow } from '@material-ui/core';
119 import Typography from '@material-ui/core/Typography';
120 import type Plot from '../../../types/Plot';
121+import PlotStatusEnum from '../../../constants/PlotStatus';
122 import PlotStatus from '../PlotStatus';
123 import PlotAction from '../PlotAction';
124 import PlotHeader from '../PlotHeader';
125@@ -69,6 +70,8 @@ export default function PlotOverviewPlots() {
126 return null;
127 }
128
129+ const queuePlots = queue?.filter(item => [PlotStatusEnum.SUBMITTED, PlotStatusEnum.RUNNING].includes(item.state));
130+
131 return (
132 <>
133 <PlotHeader />
134@@ -100,9 +103,8 @@ export default function PlotOverviewPlots() {
135 </Flex>
136
137 <Table cols={cols} rows={plots} pages>
138- {queue ? queue.map((item) => {
139+ {queuePlots ? queuePlots.map((item) => {
140 const { id } = item;
141-
142 return (
143 <StyledTableRowQueue key={id}>
144 <TableCell>
145diff --git a/electron-react/src/components/plot/queue/PlotQueueActions.tsx b/electron-react/src/components/plot/queue/PlotQueueActions.tsx
146index 78095318..45369bee 100644
147--- a/electron-react/src/components/plot/queue/PlotQueueActions.tsx
148+++ b/electron-react/src/components/plot/queue/PlotQueueActions.tsx
149@@ -10,7 +10,7 @@ import {
150 import useOpenDialog from '../../../hooks/useOpenDialog';
151 import type PlotQueueItem from '../../../types/PlotQueueItem';
152 import PlotStatus from '../../../constants/PlotStatus';
153-import { plotQueueDelete } from '../../../modules/plotQueue';
154+import { stopPlotting } from '../../../modules/plotter_messages';
155 import PlotQueueLogDialog from './PlotQueueLogDialog';
156
157 type Props = {
158@@ -21,7 +21,7 @@ export default function PlotQueueAction(props: Props) {
159 const {
160 queueItem: {
161 id,
162- status,
163+ state,
164 }
165 } = props;
166
167@@ -43,7 +43,7 @@ export default function PlotQueueAction(props: Props) {
168
169 // @ts-ignore
170 if (canDelete) {
171- dispatch(plotQueueDelete(id));
172+ dispatch(stopPlotting(id));
173 }
174 }
175
176@@ -57,7 +57,7 @@ export default function PlotQueueAction(props: Props) {
177 <More>
178 {({ onClose }) => (
179 <Box>
180- {status === PlotStatus.IN_PROGRESS && (
181+ {state === PlotStatus.RUNNING && (
182 <MenuItem onClick={() => { onClose(); handleViewLog(); }}>
183 <ListItemIcon>
184 <InfoIcon fontSize="small" />
185diff --git a/electron-react/src/components/plot/queue/PlotQueueIndicator.tsx b/electron-react/src/components/plot/queue/PlotQueueIndicator.tsx
186index 4629ada0..35b7b58b 100644
187--- a/electron-react/src/components/plot/queue/PlotQueueIndicator.tsx
188+++ b/electron-react/src/components/plot/queue/PlotQueueIndicator.tsx
189@@ -9,20 +9,25 @@ type Props = {
190 };
191
192 export default function PlotQueueIndicator(props: Props) {
193- const { queueItem: { status } } = props;
194+ const { queueItem: { state } } = props;
195
196 return (
197 <Indicator color="#979797">
198- {status === PlotStatusEnum.IN_PROGRESS && (
199+ {state === PlotStatusEnum.RUNNING && (
200 <Trans id="PlotQueueIndicator.plotting">
201 Plotting
202 </Trans>
203 )}
204- {status === PlotStatusEnum.WAITING && (
205+ {state === PlotStatusEnum.SUBMITTED && (
206 <Trans id="PlotQueueIndicator.queued">
207 Queued
208 </Trans>
209 )}
210+ {state === PlotStatusEnum.ERROR && (
211+ <Trans id="PlotQueueIndicator.error">
212+ Error
213+ </Trans>
214+ )}
215 </Indicator>
216 );
217 }
218\ No newline at end of file
219diff --git a/electron-react/src/components/plot/queue/PlotQueueLogDialog.tsx b/electron-react/src/components/plot/queue/PlotQueueLogDialog.tsx
220index b07d92e8..cd28b807 100644
221--- a/electron-react/src/components/plot/queue/PlotQueueLogDialog.tsx
222+++ b/electron-react/src/components/plot/queue/PlotQueueLogDialog.tsx
223@@ -5,7 +5,7 @@ import { Log } from '@chia/core';
224 import type { RootState } from '../../../modules/rootReducer';
225
226 type Props = {
227- id: number;
228+ id: string;
229 open: boolean;
230 onClose: () => void;
231 };
232@@ -16,8 +16,8 @@ export default function PlotQueueLogDialog(props: Props) {
233 const [log, setLog] = useState<string>('Loading...');
234
235 useEffect(() => {
236- if (queueItem) {
237- setLog(queueItem?.log);
238+ if (queueItem && queueItem.log) {
239+ setLog(queueItem.log.trim());
240 }
241 }, [queueItem]);
242
243diff --git a/electron-react/src/components/plot/queue/PlotQueueSize.tsx b/electron-react/src/components/plot/queue/PlotQueueSize.tsx
244index c8958460..61266eb3 100644
245--- a/electron-react/src/components/plot/queue/PlotQueueSize.tsx
246+++ b/electron-react/src/components/plot/queue/PlotQueueSize.tsx
247@@ -7,15 +7,15 @@ type Props = {
248 };
249
250 export default function PlotQueueSize(props: Props) {
251- const { queueItem: { config: { plotSize } } } = props;
252- const item = plotSizes.find((item) => item.value === plotSize);
253+ const { queueItem: { size } } = props;
254+ const item = plotSizes.find((item) => item.value === size);
255 if (!item) {
256 return null;
257 }
258
259 return (
260 <>
261- {`K-${plotSize}, ${item.label}`}
262+ {`K-${size}, ${item.label}`}
263 </>
264 );
265 }
266diff --git a/electron-react/src/constants/PlotStatus.ts b/electron-react/src/constants/PlotStatus.ts
267index cf0c5044..f1a73b8e 100644
268--- a/electron-react/src/constants/PlotStatus.ts
269+++ b/electron-react/src/constants/PlotStatus.ts
270@@ -1,7 +1,8 @@
271 enum PlotStatus {
272- WAITING,
273- IN_PROGRESS,
274- DONE,
275+ SUBMITTED = 'SUBMITTED',
276+ RUNNING = 'RUNNING',
277+ ERROR = 'ERROR',
278+ FINISHED = 'FINISHED',
279 }
280
281 export default PlotStatus;
282diff --git a/electron-react/src/middleware/middleware.js b/electron-react/src/middleware/middleware.js
283index 69009e92..f38987c6 100644
284--- a/electron-react/src/middleware/middleware.js
285+++ b/electron-react/src/middleware/middleware.js
286@@ -2,7 +2,6 @@ import * as actions from '../modules/websocket';
287 import {
288 registerService,
289 startService,
290- isServiceRunning,
291 startServiceTest,
292 } from '../modules/daemon_messages';
293 import { handle_message } from './middleware_api';
294@@ -36,10 +35,9 @@ const socketMiddleware = () => {
295 const onOpen = (store) => (event) => {
296 connected = true;
297 store.dispatch(actions.wsConnected(event.target.url));
298- const register_action = registerService();
299- store.dispatch(register_action);
300+ store.dispatch(registerService('wallet_ui'));
301+ store.dispatch(registerService(service_plotter));
302
303- store.dispatch(isServiceRunning(service_plotter));
304 store.dispatch(startServiceTest(service_wallet));
305
306 if (config.local_test) {
307diff --git a/electron-react/src/middleware/middleware_api.jsx b/electron-react/src/middleware/middleware_api.jsx
308index 33dfb77c..f1e7315a 100644
309--- a/electron-react/src/middleware/middleware_api.jsx
310+++ b/electron-react/src/middleware/middleware_api.jsx
311@@ -1,7 +1,5 @@
312 import React from 'react';
313-// import { push } from 'connected-react-router';
314 import { AlertDialog } from '@chia/core';
315-import isElectron from 'is-electron';
316 import {
317 get_address,
318 format_message,
319@@ -43,14 +41,9 @@ import {
320 pingHarvester,
321 refreshPlots,
322 } from '../modules/harvesterMessages';
323-import {
324- addProgress,
325- resetProgress,
326- plottingStopped,
327- plottingStarted,
328-} from '../modules/plotter_messages';
329+import { plottingStopped } from '../modules/plotter_messages';
330
331-import { plotQueueProcess } from '../modules/plotQueue';
332+import { plotQueueUpdate } from '../modules/plotQueue';
333 import { startService, isServiceRunning } from '../modules/daemon_messages';
334 import { get_all_trades } from '../modules/trade_messages';
335 import {
336@@ -103,42 +96,6 @@ async function ping_harvester(store) {
337 }
338 }
339
340-let global_tail = null;
341-
342-async function track_progress(store, location) {
343- if (!isElectron()) {
344- return;
345- }
346- const { Tail } = window.require('tail');
347-
348- const { dispatch } = store;
349- const options = { fromBeginning: true, follow: true, useWatchFile: true };
350- if (!location) {
351- return;
352- }
353- dispatch(plottingStarted());
354- try {
355- dispatch(resetProgress());
356- if (global_tail) {
357- global_tail.unwatch();
358- }
359- global_tail = new Tail(location, options);
360- global_tail.on('line', async (data) => {
361- await dispatch(addProgress(data));
362- if (data.includes('Renamed final file')) {
363- await dispatch(refreshPlots());
364- await dispatch(plottingStopped());
365- dispatch(plotQueueProcess());
366- }
367- });
368- global_tail.on('error', (err) => {
369- dispatch(addProgress(err));
370- });
371- } catch (error) {
372- console.log(error);
373- }
374-}
375-
376 export const refreshAllState = (dispatch) => {
377 dispatch(format_message('get_wallets', {}));
378 const start_farmer = startService(service_farmer);
379@@ -156,7 +113,6 @@ export const refreshAllState = (dispatch) => {
380 dispatch(getFarmerConnections());
381 dispatch(getPlots());
382 dispatch(getPlotDirectories());
383- dispatch(isServiceRunning(service_plotter));
384 dispatch(get_all_trades());
385 };
386
387@@ -195,7 +151,6 @@ export const handle_message = async (store, payload) => {
388 /*
389 if (payload.data.success) {
390 console.log('redirect to / after get_public_keys');
391- console.log(new Error('why ???'));
392 store.dispatch(push('/'));
393 }
394 */
395@@ -216,8 +171,6 @@ export const handle_message = async (store, payload) => {
396 </AlertDialog>,
397 ),
398 );
399- } else if (payload.command === 'get_plots') {
400- store.dispatch(plotQueueProcess());
401 } else if (payload.command === 'delete_plot') {
402 store.dispatch(refreshPlots());
403 } else if (payload.command === 'refresh_plots') {
404@@ -247,20 +200,36 @@ export const handle_message = async (store, payload) => {
405 }
406 }
407 }
408+ } else if (payload.command === 'register_service') {
409+ const { service, queue } = payload.data;
410+ if (service === service_plotter) {
411+ store.dispatch(plotQueueUpdate(queue));
412+ }
413 } else if (payload.command === 'state_changed') {
414+ const { origin } = payload;
415 const { state } = payload.data;
416- if (state === 'coin_added' || state === 'coin_removed') {
417- var { wallet_id } = payload.data;
418- store.dispatch(get_balance_for_wallet(wallet_id));
419- store.dispatch(get_transactions(wallet_id));
420- } else if (state === 'sync_changed') {
421- store.dispatch(get_sync_status());
422- } else if (state === 'new_block') {
423- store.dispatch(get_height_info());
424- } else if (state === 'pending_transaction') {
425- wallet_id = payload.data.wallet_id;
426- store.dispatch(get_balance_for_wallet(wallet_id));
427- store.dispatch(get_transactions(wallet_id));
428+ if (origin === service_plotter) {
429+ const { queue } = payload.data;
430+ await store.dispatch(plotQueueUpdate(queue));
431+
432+ // updated state of the plots
433+ if (state === 'state') {
434+ store.dispatch(refreshPlots());
435+ }
436+ } else {
437+ if (state === 'coin_added' || state === 'coin_removed') {
438+ var { wallet_id } = payload.data;
439+ store.dispatch(get_balance_for_wallet(wallet_id));
440+ store.dispatch(get_transactions(wallet_id));
441+ } else if (state === 'sync_changed') {
442+ store.dispatch(get_sync_status());
443+ } else if (state === 'new_block') {
444+ store.dispatch(get_height_info());
445+ } else if (state === 'pending_transaction') {
446+ wallet_id = payload.data.wallet_id;
447+ store.dispatch(get_balance_for_wallet(wallet_id));
448+ store.dispatch(get_transactions(wallet_id));
449+ }
450 }
451 } else if (payload.command === 'cc_set_name') {
452 if (payload.data.success) {
453@@ -278,32 +247,28 @@ export const handle_message = async (store, payload) => {
454 if (payload.data.success) {
455 store.dispatch(offerParsed(payload.data.discrepancies));
456 }
457- } else if (payload.command === 'start_plotting') {
458- if (payload.data.success) {
459- track_progress(store, payload.data.out_file);
460- }
461 } else if (payload.command === 'start_service') {
462 const { service } = payload.data;
463 if (payload.data.success) {
464 if (service === service_wallet) {
465 ping_wallet(store);
466- } else if (service === service_full_node) {
467- ping_full_node(store);
468- } else if (service === service_simulator) {
469+ } else if (
470+ service === service_full_node ||
471+ service === service_simulator
472+ ) {
473 ping_full_node(store);
474 } else if (service === service_farmer) {
475 ping_farmer(store);
476 } else if (service === service_harvester) {
477 ping_harvester(store);
478- } else if (service === service_plotter) {
479- track_progress(store, payload.data.out_file);
480 }
481 } else if (payload.data.error.includes('already running')) {
482 if (service === service_wallet) {
483 ping_wallet(store);
484- } else if (service === service_full_node) {
485- ping_full_node(store);
486- } else if (service === service_simulator) {
487+ } else if (
488+ service === service_full_node ||
489+ service === service_simulator
490+ ) {
491 ping_full_node(store);
492 } else if (service === service_farmer) {
493 ping_farmer(store);
494@@ -312,21 +277,10 @@ export const handle_message = async (store, payload) => {
495 } else if (service === service_plotter) {
496 }
497 }
498- } else if (payload.command === 'is_running') {
499- if (payload.data.success) {
500- const service = payload.data.service_name;
501- const { is_running } = payload.data;
502- if (service === service_plotter) {
503- if (is_running) {
504- track_progress(store, payload.data.out_file);
505- }
506- }
507- }
508 } else if (payload.command === 'stop_service') {
509 if (payload.data.success) {
510 if (payload.data.service_name === service_plotter) {
511 await store.dispatch(plottingStopped());
512- store.dispatch(plotQueueProcess());
513 }
514 }
515 }
516diff --git a/electron-react/src/modules/daemon_messages.js b/electron-react/src/modules/daemon_messages.js
517index 8a407a9b..9dfe2561 100644
518--- a/electron-react/src/modules/daemon_messages.js
519+++ b/electron-react/src/modules/daemon_messages.js
520@@ -5,17 +5,17 @@ export const daemonMessage = () => ({
521 },
522 });
523
524-export const registerService = () => {
525+export const registerService = (service) => {
526 const action = daemonMessage();
527 action.message.command = 'register_service';
528- action.message.data = { service: 'wallet_ui' };
529+ action.message.data = { service };
530 return action;
531 };
532
533-export const startService = (service_name) => {
534+export const startService = (service) => {
535 const action = daemonMessage();
536 action.message.command = 'start_service';
537- action.message.data = { service: service_name };
538+ action.message.data = { service };
539 return action;
540 };
541
542diff --git a/electron-react/src/modules/plotQueue.ts b/electron-react/src/modules/plotQueue.ts
543index 5c306a3b..bf256b48 100644
544--- a/electron-react/src/modules/plotQueue.ts
545+++ b/electron-react/src/modules/plotQueue.ts
546@@ -1,128 +1,64 @@
547 import { Action } from 'redux';
548-import { last } from 'lodash';
549-import { ThunkAction, ThunkDispatch } from 'redux-thunk';
550+import { ThunkAction } from 'redux-thunk';
551 import type { RootState } from './rootReducer';
552-import PlotStatus from '../constants/PlotStatus';
553 import type PlotAdd from '../types/PlotAdd';
554 import type PlotQueueItem from '../types/PlotQueueItem';
555 import { startPlotting } from './plotter_messages';
556+import PlotStatus from '../constants/PlotStatus';
557 import { stopService } from './daemon_messages';
558 import { service_plotter } from '../util/service_names';
559
560-function plotNow(
561- dispatch: ThunkDispatch<PlotQueueState, undefined, any>,
562+export function plotQueueAdd(
563 config: PlotAdd,
564-) {
565- const {
566- plotSize,
567- plotCount,
568- workspaceLocation,
569- workspaceLocation2,
570- finalLocation,
571- maxRam,
572- numBuckets,
573- numThreads,
574- stripeSize,
575- fingerprint,
576- } = config;
577-
578- return dispatch(
579- startPlotting(
580+): ThunkAction<any, RootState, unknown, Action<Object>> {
581+ return (dispatch) => {
582+ const {
583 plotSize,
584 plotCount,
585 workspaceLocation,
586- workspaceLocation2 || workspaceLocation,
587+ workspaceLocation2,
588 finalLocation,
589 maxRam,
590 numBuckets,
591 numThreads,
592 stripeSize,
593 fingerprint,
594- ),
595- );
596+ parallel,
597+ delay,
598+ } = config;
599+
600+ return dispatch(
601+ startPlotting(
602+ plotSize,
603+ plotCount,
604+ workspaceLocation,
605+ workspaceLocation2 || workspaceLocation,
606+ finalLocation,
607+ maxRam,
608+ numBuckets,
609+ numThreads,
610+ stripeSize,
611+ fingerprint,
612+ parallel,
613+ delay,
614+ ),
615+ );
616+ };
617 }
618
619-export function plotQueueProcess(): ThunkAction<
620- any,
621- RootState,
622- unknown,
623- Action<Object>
624-> {
625- return async (dispatch, getState) => {
626- const {
627- plot_queue: { queue },
628- plot_control: { plotting_in_proggress },
629- } = getState();
630-
631- if (plotting_in_proggress) {
632- return;
633- }
634-
635- const newQueue = queue.filter(
636- (item) => item.status !== PlotStatus.IN_PROGRESS,
637- );
638- if (queue.length === newQueue.length) {
639- return;
640- }
641-
642- const [first, ...rest] = newQueue;
643- if (first) {
644- await plotNow(dispatch, first.config);
645-
646- dispatch({
647- type: 'PLOT_QUEUE_REPLACE',
648- queue: [
649- {
650- ...first,
651- status: PlotStatus.IN_PROGRESS,
652- },
653- ...rest,
654- ],
655- });
656- return;
657- }
658-
659+export function plotQueueUpdate(
660+ queue: PlotQueueItem[],
661+): ThunkAction<any, RootState, unknown, Action<Object>> {
662+ return (dispatch) => {
663 dispatch({
664- type: 'PLOT_QUEUE_REPLACE',
665- queue: newQueue,
666+ type: 'PLOT_QUEUE_UPDATE',
667+ queue,
668 });
669 };
670 }
671
672-export function plotQueueAdd(
673- config: PlotAdd,
674-): ThunkAction<any, RootState, unknown, Action<Object>> {
675- return async (dispatch, getState) => {
676- const {
677- plot_queue: { queue },
678- plot_control: { plotting_in_proggress },
679- } = getState();
680-
681- const lastId = last(queue)?.id ?? 1;
682-
683- if (plotting_in_proggress) {
684- // add config into the queue
685- dispatch({
686- type: 'PLOT_QUEUE_ADD',
687- id: lastId + 1,
688- config,
689- status: PlotStatus.WAITING,
690- });
691- } else {
692- await plotNow(dispatch, config);
693-
694- dispatch({
695- type: 'PLOT_QUEUE_ADD',
696- id: lastId + 1,
697- config,
698- status: PlotStatus.IN_PROGRESS,
699- });
700- }
701- };
702-}
703-
704 export function plotQueueDelete(
705- id: number,
706+ id: string,
707 ): ThunkAction<any, RootState, unknown, Action<Object>> {
708 return (dispatch, getState) => {
709 const {
710@@ -134,13 +70,8 @@ export function plotQueueDelete(
711 return;
712 }
713
714- if (queueItem.status === PlotStatus.IN_PROGRESS) {
715- dispatch(stopService(service_plotter));
716- } else {
717- dispatch({
718- type: 'PLOT_QUEUE_REPLACE',
719- queue: queue.filter((item) => item.id !== id),
720- });
721+ if (queueItem.state === PlotStatus.RUNNING) {
722+ dispatch(stopService(service_plotter)); // TODO replace with stopPlotting(id)
723 }
724 };
725 }
726@@ -157,63 +88,14 @@ export default function plotQueueReducer(
727 state = { ...initialState },
728 action: any,
729 ): PlotQueueState {
730- const { queue } = state;
731- const { id, status } = action;
732-
733 switch (action.type) {
734- case 'PLOT_QUEUE_ADD':
735- const { config } = action;
736+ case 'PLOT_QUEUE_UPDATE':
737+ const { queue } = action;
738
739 return {
740 ...state,
741- queue: [
742- ...queue,
743- {
744- id,
745- config,
746- status,
747- added: new Date().getTime(),
748- log: '',
749- },
750- ],
751+ queue,
752 };
753- case 'PLOT_QUEUE_CHANGE_STATUS':
754- return {
755- ...state,
756- queue: queue.map((item) => {
757- if (id !== item.id) {
758- return item;
759- }
760-
761- return {
762- ...item,
763- status,
764- };
765- }),
766- };
767-
768- case 'PLOT_QUEUE_REPLACE':
769- return {
770- ...state,
771- queue: action.queue,
772- };
773- case 'PLOTTER_CONTROL':
774- if (action.command === 'add_progress') {
775- return {
776- ...state,
777- queue: queue.map((item) => {
778- if (item.status !== PlotStatus.IN_PROGRESS) {
779- return item;
780- }
781-
782- return {
783- ...item,
784- log: `${item.log}\n${action.progress}`,
785- };
786- }),
787- };
788- }
789- return state;
790 default:
791 return state;
792 }
793diff --git a/electron-react/src/modules/plotter_messages.js b/electron-react/src/modules/plotter_messages.js
794index 5cefdea1..f83f1e35 100644
795--- a/electron-react/src/modules/plotter_messages.js
796+++ b/electron-react/src/modules/plotter_messages.js
797@@ -5,7 +5,31 @@ export const plotControl = () => ({
798 type: 'PLOTTER_CONTROL',
799 });
800
801-export const startPlotting = (k, n, t, t2, d, b, u, r, s, a) => {
802+export const stopPlotting = (id) => {
803+ const action = daemonMessage();
804+ action.message.command = 'stop_plotting';
805+ action.message.data = {
806+ service: service_plotter,
807+ id,
808+ };
809+
810+ return action;
811+};
812+
813+export const startPlotting = (
814+ k,
815+ n,
816+ t,
817+ t2,
818+ d,
819+ b,
820+ u,
821+ r,
822+ s,
823+ a,
824+ parallel,
825+ delay,
826+) => {
827 const action = daemonMessage();
828 action.message.command = 'start_plotting';
829
830@@ -20,6 +44,8 @@ export const startPlotting = (k, n, t, t2, d, b, u, r, s, a) => {
831 u,
832 r,
833 s,
834+ parallel,
835+ delay,
836 };
837
838 if (a) {
839diff --git a/electron-react/src/types/PlotQueueItem.ts b/electron-react/src/types/PlotQueueItem.ts
840index 65d939a9..018d9c92 100644
841--- a/electron-react/src/types/PlotQueueItem.ts
842+++ b/electron-react/src/types/PlotQueueItem.ts
843@@ -1,12 +1,13 @@
844 import PlotStatus from '../constants/PlotStatus';
845-import type PlotAdd from './PlotAdd';
846
847 type PlotQueueItem = {
848- id: number;
849- config: PlotAdd;
850- status: PlotStatus;
851- added: number; // timestamp when added
852- log: string;
853+ id: string;
854+ size: number;
855+ parallel: boolean;
856+ delay: number;
857+ state: PlotStatus;
858+ error?: string;
859+ log?: string;
860 };
861
862 export default PlotQueueItem;
863diff --git a/requirements-dev.txt b/requirements-dev.txt
864index e5d3a768..fc5129da 100644
865--- a/requirements-dev.txt
866+++ b/requirements-dev.txt
867@@ -36,10 +36,10 @@ aiohttp~=3.7.1
868 blspy~=0.2.4
869 cryptography~=3.2
870 PyYAML~=5.3.1
871-cbor2~=5.2.0
872-clvm~=0.6
873-colorlog~=4.6.2
874-keyring~=21.5.0
875+cbor2~=5.1.2
876+clvm~=0.5.3
877+colorlog~=4.4.0
878+keyring~=21.4.0
879 bitstring~=3.1.7
880 chiabip158~=0.16
881 chiavdf~=0.13.0b2
882@@ -48,7 +48,7 @@ chiapos~=0.12.33
883 websockets~=8.1
884 aiter~=0.13.20191203
885 aiosqlite~=0.15.0
886-sortedcontainers~=2.3.0
887+sortedcontainers~=2.2.2
888 py~=1.9.0
889 pip~=20.2.4
890 wheel~=0.35.1
891diff --git a/setup.py b/setup.py
892index 8eaf30d7..23aa2504 100644
893--- a/setup.py
894+++ b/setup.py
895@@ -6,7 +6,7 @@ dependencies = [
896 "blspy==0.2.9", # Signature library
897 "chiavdf==0.13.0b2", # timelord and vdf verification
898 "chiabip158==0.17", # bip158-style wallet filters
899- "chiapos==0.12.39", # proof of space
900+ "chiapos==0.12.40", # proof of space
901 "clvm==0.6",
902 "clvm_tools==0.1.9",
903 "aiohttp==3.7.3", # HTTP server for full node rpc
904diff --git a/src/cmds/plots.py b/src/cmds/plots.py
905index 6e4969e9..f6ec2c49 100644
906--- a/src/cmds/plots.py
907+++ b/src/cmds/plots.py
908@@ -26,7 +26,8 @@ def help_message():
909 + " -a [fingerprint] -f [farmer public key] -p [pool public key]"
910 + " -t [tmp dir] -2 [tmp dir 2] -d [final dir] (creates plots)"
911 )
912- print("-i [plotid] [-m memo] are available for debugging")
913+ print("-e disables bitfield plotting")
914+ print("-i [plotid] -m [memo] are available for debugging")
915 print("chia plots check -n [num checks] -g [string] (checks plots)")
916 print(" Default: check all plots in every directory")
917 print(" -g: checks plots with file or directory name containing [string]")
918@@ -99,6 +100,13 @@ def make_parser(parser):
919 type=str,
920 default=None,
921 )
922+ parser.add_argument(
923+ "-e",
924+ "--nobitfield",
925+ help="Disable bitfield",
926+ default=False,
927+ action="store_true",
928+ )
929 parser.add_argument(
930 "command",
931 help=f"Command can be any one of {command_list}",
932diff --git a/src/consensus/block_body_validation.py b/src/consensus/block_body_validation.py
933index d1048ec9..9ad443e9 100644
934--- a/src/consensus/block_body_validation.py
935+++ b/src/consensus/block_body_validation.py
936@@ -40,7 +40,7 @@ async def validate_block_body(
937 peak: Optional[SubBlockRecord],
938 block: Union[FullBlock, UnfinishedBlock],
939 sub_height: uint32,
940- height: Optional[uint32],
941+ height: uint32,
942 ) -> Optional[Err]:
943 """
944 This assumes the header block has been completely validated.
945@@ -49,11 +49,7 @@ async def validate_block_body(
946 """
947 if isinstance(block, FullBlock):
948 assert sub_height == block.sub_block_height
949- if height is not None:
950- assert height == block.height
951- assert block.is_block()
952- else:
953- assert not block.is_block()
954+ assert height == block.height
955
956 # 1. For non block sub-blocks, foliage block, transaction filter, transactions info, and generator must be empty
957 # If it is a sub block but not a block, there is no body to validate. Check that all fields are None
958@@ -93,12 +89,32 @@ async def validate_block_body(
959 return Err.INVALID_TRANSACTIONS_GENERATOR_ROOT
960
961 # 7. The reward claims must be valid for the previous sub-blocks, and current block fees
962+ pool_coin = create_pool_coin(
963+ sub_height,
964+ block.foliage_sub_block.foliage_sub_block_data.pool_target.puzzle_hash,
965+ calculate_pool_reward(height),
966+ )
967+ farmer_coin = create_farmer_coin(
968+ sub_height,
969+ block.foliage_sub_block.foliage_sub_block_data.farmer_reward_puzzle_hash,
970+ calculate_base_farmer_reward(height) + block.transactions_info.fees,
971+ )
972+ expected_reward_coins.add(pool_coin)
973+ expected_reward_coins.add(farmer_coin)
974+
975 if sub_height > 0:
976- # Add reward claims for all sub-blocks from the prev prev block, until the prev block (including the latter)
977+ # Add reward claims for all sub-blocks since the last block
978 curr_sb = sub_blocks[block.prev_header_hash]
979
980 while not curr_sb.is_block:
981- # Finds the prev block
982+ expected_reward_coins.add(
983+ create_pool_coin(curr_sb.sub_block_height, curr_sb.pool_puzzle_hash, calculate_pool_reward(curr_sb.sub_block_height))
984+ )
985+ expected_reward_coins.add(
986+ create_farmer_coin(
987+ curr_sb.sub_block_height, curr_sb.farmer_puzzle_hash, calculate_base_farmer_reward(curr_sb.sub_block_height)
988+ )
989+ )
990 curr_sb = sub_blocks[curr_sb.prev_hash]
991 assert curr_sb.header_hash == block.foliage_block.prev_block_hash
992
993@@ -112,7 +128,7 @@ async def validate_block_body(
994
995 assert curr_sb.fees is not None
996 pool_coin = create_pool_coin(
997- curr_sb.sub_block_height,
998+ curr_sb.height,
999 curr_sb.pool_puzzle_hash,
1000 calculate_pool_reward(curr_height),
1001 )
1002@@ -126,7 +142,7 @@ async def validate_block_body(
1003 expected_reward_coins.add(farmer_coin)
1004
1005 # For the second block in the chain, don't go back further
1006- if curr_sb.sub_block_height > 0:
1007+ if curr_sb.height > 0:
1008 curr_sb = sub_blocks[curr_sb.prev_hash]
1009 curr_height = curr_sb.height
1010 while not curr_sb.is_block:
1011@@ -242,11 +258,11 @@ async def validate_block_body(
1012 for c_name in removals_in_curr:
1013 removals_since_fork.add(c_name)
1014 for c in additions_in_curr:
1015- additions_since_fork[c.name()] = (c, curr.sub_block_height)
1016+ additions_since_fork[c.name()] = (c, curr.height)
1017
1018 for coinbase_coin in curr.get_included_reward_coins():
1019- coinbases_since_fork[coinbase_coin.name()] = curr.sub_block_height
1020- if curr.sub_block_height == 0:
1021+ coinbases_since_fork[coinbase_coin.name()] = curr.height
1022+ if curr.height == 0:
1023 break
1024 curr = await block_store.get_full_block(curr.prev_header_hash)
1025 assert curr is not None
1026@@ -316,7 +332,7 @@ async def validate_block_body(
1027 if ConditionOpcode.ASSERT_FEE in npc.condition_dict:
1028 fee_list: List[ConditionVarPair] = npc.condition_dict[ConditionOpcode.ASSERT_FEE]
1029 for cvp in fee_list:
1030- fee = int_from_bytes(cvp.vars[0])
1031+ fee = int_from_bytes(cvp.var1)
1032 assert_fee_sum = assert_fee_sum + fee
1033
1034 # 17. Check that the assert fee sum <= fees
1035diff --git a/src/consensus/block_creation.py b/src/consensus/block_creation.py
1036index 79f88ac5..d3ab6330 100644
1037--- a/src/consensus/block_creation.py
1038+++ b/src/consensus/block_creation.py
1039@@ -97,7 +97,6 @@ def create_foliage(
1040 sub_block_height = uint32(prev_sub_block.sub_block_height + 1)
1041
1042 if prev_block is None:
1043- sub_height: uint32 = uint32(0)
1044 height: uint32 = uint32(0)
1045 else:
1046 sub_height = uint32(prev_block.sub_block_height + 1)
1047@@ -173,7 +172,7 @@ def create_foliage(
1048 assert curr.header_hash == prev_block.header_hash
1049 reward_claims_incorporated += [pool_coin, farmer_coin]
1050
1051- if curr.sub_block_height > 0:
1052+ if curr.height > 0:
1053 curr = sub_blocks[curr.prev_hash]
1054 # Prev block is not genesis
1055 while not curr.is_block:
1056@@ -252,7 +251,7 @@ def create_foliage(
1057 additions_root,
1058 removals_root,
1059 transactions_info.get_hash(),
1060- height,
1061+ height
1062 )
1063 assert foliage_block is not None
1064 foliage_block_hash: Optional[bytes32] = foliage_block.get_hash()
1065diff --git a/src/consensus/blockchain.py b/src/consensus/blockchain.py
1066index 7448f42b..269f62e3 100644
1067--- a/src/consensus/blockchain.py
1068+++ b/src/consensus/blockchain.py
1069@@ -197,14 +197,8 @@ class Blockchain:
1070 assert required_iters is not None
1071
1072 error_code = await validate_block_body(
1073- self.constants,
1074- self.sub_blocks,
1075- self.block_store,
1076- self.coin_store,
1077- self.get_peak(),
1078- block,
1079- block.sub_block_height,
1080- block.height if block.is_block() else None,
1081+ self.constants, self.sub_blocks, self.block_store, self.coin_store, self.get_peak(),
1082+ block, block.sub_block_height, block.height
1083 )
1084
1085 if error_code is not None:
1086@@ -282,7 +276,7 @@ class Blockchain:
1087 assert fetched_block is not None
1088 assert fetched_sub_block is not None
1089 blocks_to_add.append((fetched_block, fetched_sub_block))
1090- if fetched_block.sub_block_height == 0:
1091+ if fetched_block.height == 0:
1092 # Doing a full reorg, starting at height 0
1093 break
1094 curr = fetched_sub_block.prev_hash
1095@@ -292,9 +286,7 @@ class Blockchain:
1096 if fetched_sub_block.is_block:
1097 await self.coin_store.new_block(fetched_block)
1098 if fetched_sub_block.sub_epoch_summary_included is not None:
1099- self.sub_epoch_summaries[
1100- fetched_sub_block.sub_block_height
1101- ] = fetched_sub_block.sub_epoch_summary_included
1102+ self.sub_epoch_summaries[fetched_sub_block.sub_block_height] = fetched_sub_block.sub_epoch_summary_included
1103
1104 # Changes the peak to be the new peak
1105 await self.block_store.set_peak(sub_block.header_hash)
1106@@ -420,9 +412,7 @@ class Blockchain:
1107 # return results
1108 return []
1109
1110- async def validate_unfinished_block(
1111- self, block: UnfinishedBlock, skip_overflow_ss_validation=True
1112- ) -> Tuple[Optional[uint64], Optional[Err]]:
1113+ async def validate_unfinished_block(self, block: UnfinishedBlock) -> Tuple[Optional[uint64], Optional[Err]]:
1114 if (
1115 block.prev_header_hash not in self.sub_blocks
1116 and not block.prev_header_hash == self.constants.GENESIS_PREV_HASH
1117@@ -445,7 +435,7 @@ class Blockchain:
1118 self.sub_height_to_hash,
1119 unfinished_header_block,
1120 False,
1121- skip_overflow_ss_validation,
1122+ True,
1123 )
1124
1125 if error is not None:
1126diff --git a/src/consensus/blockchain_check_conditions.py b/src/consensus/blockchain_check_conditions.py
1127index 3d267edd..8a4d913d 100644
1128--- a/src/consensus/blockchain_check_conditions.py
1129+++ b/src/consensus/blockchain_check_conditions.py
1130@@ -73,13 +73,12 @@ def blockchain_assert_time_exceeds(condition: ConditionVarPair, timestamp):
1131 return Err.ASSERT_TIME_EXCEEDS_FAILED
1132 return None
1133
1134-
1135 def blockchain_assert_relative_time_exceeds(condition: ConditionVarPair, unspent: CoinRecord, timestamp):
1136 """
1137 Checks if time since unspent creation in millis exceeds the time specified in condition
1138 """
1139 try:
1140- expected_mili_time = int_from_bytes(condition.vars[0])
1141+ expected_mili_time = int_from_bytes(condition.var1)
1142 except ValueError:
1143 return Err.INVALID_CONDITION
1144
1145@@ -88,13 +87,12 @@ def blockchain_assert_relative_time_exceeds(condition: ConditionVarPair, unspent
1146 return Err.ASSERT_RELATIVE_TIME_EXCEEDS_FAILED
1147 return None
1148
1149-
1150 def blockchain_check_conditions_dict(
1151 unspent: CoinRecord,
1152 removed: Dict[bytes32, CoinRecord],
1153 conditions_dict: Dict[ConditionOpcode, List[ConditionVarPair]],
1154 height: uint32,
1155- timestamp: uint64,
1156+ timestamp: uint64
1157 ) -> Optional[Err]:
1158 """
1159 Check all conditions against current state.
1160diff --git a/src/consensus/difficulty_adjustment.py b/src/consensus/difficulty_adjustment.py
1161index 71099bab..5982f5c8 100644
1162--- a/src/consensus/difficulty_adjustment.py
1163+++ b/src/consensus/difficulty_adjustment.py
1164@@ -87,6 +87,7 @@ def _get_last_block_in_previous_epoch(
1165 f"Height at {prev_sb.sub_block_height + 1} should not create a new epoch, it is far past the epoch barrier"
1166 )
1167
1168+ height_prev_epoch_surpass: uint32 = height_epoch_surpass - constants.EPOCH_SUB_BLOCKS
1169 if height_prev_epoch_surpass == 0:
1170 # The genesis block is an edge case, where we measure from the first block in epoch (height 0), as opposed to
1171 # the last sub-block in the previous epoch, which would be height -1
1172@@ -102,12 +103,11 @@ def _get_last_block_in_previous_epoch(
1173 sub_height_to_hash,
1174 sub_blocks,
1175 prev_sb,
1176- uint32(height_prev_epoch_surpass - constants.MAX_SUB_SLOT_SUB_BLOCKS - 1),
1177- uint32(2 * constants.MAX_SUB_SLOT_SUB_BLOCKS + 1),
1178+ uint32(height_prev_epoch_surpass - constants.MAX_SLOT_SUB_BLOCKS - 1),
1179+ uint32(2 * constants.MAX_SLOT_SUB_BLOCKS + 1),
1180 )
1181-
1182 # This is the last sb in the slot at which we surpass the height. The last block in epoch will be before this.
1183- fetched_index: int = constants.MAX_SUB_SLOT_SUB_BLOCKS
1184+ fetched_index: int = constants.MAX_SLOT_SUB_BLOCKS
1185 last_sb_in_slot: SubBlockRecord = fetched_blocks[fetched_index]
1186 fetched_index += 1
1187 assert last_sb_in_slot.sub_block_height == height_prev_epoch_surpass - 1
1188@@ -116,17 +116,17 @@ def _get_last_block_in_previous_epoch(
1189
1190 # Wait until the slot finishes with a challenge chain infusion at start of slot
1191 # Note that there are no overflow blocks at the start of new epochs
1192- while curr_b.sub_epoch_summary_included is None:
1193- last_sb_in_slot = curr_b
1194- curr_b = fetched_blocks[fetched_index]
1195+ while not curr.is_challenge_sub_block(constants):
1196+ last_sb_in_slot = curr
1197+ curr = fetched_blocks[fetched_index]
1198 fetched_index += 1
1199
1200 # Backtrack to find the last block before the signage point
1201- curr_b = sub_blocks[last_sb_in_slot.prev_hash]
1202- while curr_b.total_iters > last_sb_in_slot.sp_total_iters(constants) or not curr_b.is_block:
1203- curr_b = sub_blocks[curr_b.prev_hash]
1204+ curr = sub_blocks[last_sb_in_slot.prev_hash]
1205+ while curr.total_iters > last_sb_in_slot.sp_total_iters(constants) or not curr.is_block:
1206+ curr = sub_blocks[curr.prev_hash]
1207
1208- return curr_b
1209+ return curr
1210
1211
1212 def can_finish_sub_and_full_epoch(
1213diff --git a/src/daemon/server.py b/src/daemon/server.py
1214index 2e4c686c..ad5ab8a0 100644
1215--- a/src/daemon/server.py
1216+++ b/src/daemon/server.py
1217@@ -6,10 +6,23 @@ import signal
1218 import subprocess
1219 import sys
1220 import traceback
1221+from enum import Enum
1222+import uuid
1223+import time
1224 from typing import Dict, Any, List, Tuple, Optional
1225 from sys import platform
1226-
1227+from concurrent.futures import ThreadPoolExecutor
1228 from websockets import serve, ConnectionClosedOK, WebSocketException
1229+from src.cmds.init import chia_init
1230+from src.daemon.windows_signal import kill
1231+from src.util.ws_message import format_response, create_payload
1232+from src.util.json_util import dict_to_json_str
1233+from src.util.config import load_config
1234+from src.util.logging import initialize_logging
1235+from src.util.path import mkdir
1236+from src.util.service_groups import validate_service
1237+
1238+io_pool_exc = ThreadPoolExecutor()
1239
1240 try:
1241 from aiohttp import web
1242@@ -17,12 +30,6 @@ except ModuleNotFoundError:
1243 print("Error: Make sure to run . ./activate from the project folder before starting Chia.")
1244 quit()
1245
1246-
1247-from src.cmds.init import chia_init
1248-from src.daemon.windows_signal import kill
1249-from src.util.ws_message import format_response
1250-from src.util.json_util import dict_to_json_str
1251-
1252 try:
1253 import fcntl
1254
1255@@ -30,13 +37,18 @@ try:
1256 except ImportError:
1257 has_fcntl = False
1258
1259-from src.util.config import load_config
1260-from src.util.logging import initialize_logging
1261-from src.util.path import mkdir
1262-from src.util.service_groups import validate_service
1263-
1264 log = logging.getLogger(__name__)
1265
1266+service_plotter = "chia plots create"
1267+
1268+
1269+class PlotState(str, Enum):
1270+ SUBMITTED = "SUBMITTED"
1271+ RUNNING = "RUNNING"
1272+ ERROR = "ERROR"
1273+ FINISHED = "FINISHED"
1274+
1275+
1276 # determine if application is a script file or frozen exe
1277 if getattr(sys, "frozen", False):
1278 name_map = {
1279@@ -79,6 +91,7 @@ class WebSocketServer:
1280 self.root_path = root_path
1281 self.log = log
1282 self.services: Dict = dict()
1283+ self.plots_queue: List[Dict] = []
1284 self.connections: Dict[str, List[Any]] = dict() # service_name : [WebSocket]
1285 self.remote_address_map: Dict[str, str] = dict() # remote_address: service_name
1286 self.ping_job = None
1287@@ -226,6 +239,8 @@ class WebSocketServer:
1288 response = await self.start_service(data)
1289 elif command == "start_plotting":
1290 response = await self.start_plotting(data)
1291+ elif command == "stop_plotting":
1292+ response = await self.stop_plotting(data)
1293 elif command == "stop_service":
1294 response = await self.stop_service(data)
1295 elif command == "is_running":
1296@@ -241,8 +256,92 @@ class WebSocketServer:
1297 full_response = format_response(message, response)
1298 return full_response, [websocket]
1299
1300- async def start_plotting(self, request):
1301+ def plot_queue_to_payload(self, plot_queue_item):
1302+ error = plot_queue_item.get("error")
1303+ has_error = error is not None
1304+
1305+ return {
1306+ "id": plot_queue_item["id"],
1307+ "size": plot_queue_item["size"],
1308+ "parallel": plot_queue_item["parallel"],
1309+ "delay": plot_queue_item["delay"],
1310+ "state": plot_queue_item["state"],
1311+ "error": str(error) if has_error else None,
1312+ "log": plot_queue_item.get("log"),
1313+ }
1314+
1315+ def extract_plot_queue(self):
1316+ data = []
1317+ for item in self.plots_queue:
1318+ data.append(WebSocketServer.plot_queue_to_payload(self, item))
1319+ return data
1320+
1321+ async def _state_changed(self, service: str, state: str):
1322+ if service not in self.connections:
1323+ return
1324+
1325+ message = None
1326+ websockets = self.connections[service]
1327+
1328+ if service == service_plotter:
1329+ message = {
1330+ "state": state,
1331+ "queue": self.extract_plot_queue(),
1332+ }
1333+
1334+ if message is None:
1335+ return
1336+
1337+ response = create_payload("state_changed", message, service, "wallet_ui")
1338+
1339+ for websocket in websockets:
1340+ try:
1341+ await websocket.send(response)
1342+ except Exception as e:
1343+ tb = traceback.format_exc()
1344+ self.log.error(
1345+ f"Unexpected exception trying to send to websocket: {e} {tb}"
1346+ )
1347+ websockets.remove(websocket)
1348+ await websocket.close()
1349+
1350+ def state_changed(self, service: str, state: str):
1351+ asyncio.create_task(self._state_changed(service, state))
1352+
1353+ async def _watch_file_changes(self, id: str, loop):
1354+ config = self._get_plots_queue_item(id)
1355+
1356+ if config is None:
1357+ raise Exception(f"Plot queue config with ID {id} is not defined")
1358+
1359+ words = ["Renamed final file"]
1360+ file_path = config["out_file"]
1361+ fp = open(file_path, "r")
1362+ while True:
1363+ new = await loop.run_in_executor(io_pool_exc, fp.readline)
1364+
1365+ config["log"] = new if config["log"] is None else config["log"] + new
1366+ self.state_changed(service_plotter, "log_changed")
1367+
1368+ if new:
1369+ for word in words:
1370+ if word in new:
1371+ yield (word, new)
1372+ else:
1373+ time.sleep(0.5)
1374+
1375+ async def _track_plotting_progress(self, id: str, loop):
1376+ config = self._get_plots_queue_item(id)
1377+
1378+ if config is None:
1379+ raise Exception(f"Plot queue config with ID {id} is not defined")
1380+
1381+ async for hit_word, hit_sentence in self._watch_file_changes(id, loop):
1382+ break
1383+
1384+ def _build_plotting_command_args(self, request):
1385 service_name = request["service"]
1386+
1387 k = request["k"]
1388 n = request["n"]
1389 t = request["t"]
1390@@ -269,9 +368,6 @@ class WebSocketServer:
1391 if a is not None:
1392 command_args.append(f"-a={a}")
1393
1394- error = None
1395- success = False
1396-
1397 if service_name in self.services:
1398 service = self.services[service_name]
1399 r = service is not None and service.poll() is None
1400@@ -291,13 +387,37 @@ class WebSocketServer:
1401 error = "start failed"
1402
1403 response = {
1404- "success": success,
1405- "service": service_name,
1406- "out_file": f"{plotter_log_path(self.root_path).absolute()}",
1407- "error": error,
1408+ "success": True,
1409+ "service_name": service_name,
1410+ "plot_id": str(id),
1411 }
1412+
1413 return response
1414
1415+ async def stop_plotting(self, request):
1416+ id = request["id"]
1417+ config = self._get_plots_queue_item(id)
1418+ if config is None:
1419+ return {"success": False}
1420+
1421+ id = config["id"]
1422+ state = config["state"]
1423+ process = config["process"]
1424+
1425+ try:
1426+ if process is not None and state == PlotState.RUNNING:
1427+ await kill_process(process, self.root_path, service_plotter, id)
1428+ self.plots_queue.remove(config)
1429+ self.state_changed(service_plotter, "removed")
1430+ return {"success": True}
1431+ except Exception as e:
1432+ log.error(f"Error during killing the plot process: {e}")
1433+ config["state"] = PlotState.ERROR
1434+ config["error"] = str(e)
1435+ self.state_changed(service_plotter, "state")
1436+ pass
1437+ return {"success": False}
1438+
1439 async def start_service(self, request):
1440 service_command = request["service"]
1441 error = None
1442@@ -341,22 +461,27 @@ class WebSocketServer:
1443
1444 async def is_running(self, request):
1445 service_name = request["service"]
1446- process = self.services.get(service_name)
1447- r = process is not None and process.poll() is None
1448- if service_name == "chia plots create":
1449+
1450+ if service_name == service_plotter:
1451+ processes = self.services.get(service_name)
1452+ is_running = processes is not None and len(processes) > 0
1453 response = {
1454 "success": True,
1455 "service_name": service_name,
1456- "is_running": r,
1457- "out_file": f"{plotter_log_path(self.root_path).absolute()}",
1458+ "is_running": is_running,
1459 }
1460 else:
1461- response = {"success": True, "service_name": service_name, "is_running": r}
1462+ process = self.services.get(service_name)
1463+ is_running = process is not None and process.poll() is None
1464+ response = {
1465+ "success": True,
1466+ "service_name": service_name,
1467+ "is_running": is_running,
1468+ }
1469
1470 return response
1471
1472 async def exit(self):
1473-
1474 jobs = []
1475 for k in self.services.keys():
1476 jobs.append(kill_service(self.root_path, self.services, k))
1477@@ -377,10 +502,19 @@ class WebSocketServer:
1478 if service not in self.connections:
1479 self.connections[service] = []
1480 self.connections[service].append(websocket)
1481- self.remote_address_map[websocket.remote_address[1]] = service
1482- if self.ping_job is None:
1483- self.ping_job = asyncio.create_task(self.ping_task())
1484- response = {"success": True}
1485+
1486+ response = {"success": False}
1487+ if service == service_plotter:
1488+ response = {
1489+ "success": True,
1490+ "service": service,
1491+ "queue": self.extract_plot_queue(),
1492+ }
1493+ else:
1494+ self.remote_address_map[websocket.remote_address[1]] = service
1495+ if self.ping_job is None:
1496+ self.ping_job = asyncio.create_task(self.ping_task())
1497+ response = {"success": True}
1498 self.log.info(f"registered for service {service}")
1499 return response
1500
1501@@ -393,19 +527,19 @@ def daemon_launch_lock_path(root_path):
1502 return root_path / "run" / "start-daemon.launching"
1503
1504
1505-def pid_path_for_service(root_path, service):
1506+def pid_path_for_service(root_path, service, id=""):
1507 """
1508 Generate a path for a PID file for the given service name.
1509 """
1510 pid_name = service.replace(" ", "-").replace("/", "-")
1511- return root_path / "run" / f"{pid_name}.pid"
1512+ return root_path / "run" / f"{pid_name}{id}.pid"
1513
1514
1515-def plotter_log_path(root_path):
1516- return root_path / "plotter" / "plotter_log.txt"
1517+def plotter_log_path(root_path, id):
1518+ return root_path / "plotter" / f"plotter_log_{id}.txt"
1519
1520
1521-def launch_plotter(root_path, service_name, service_array):
1522+def launch_plotter(root_path, service_name, service_array, id):
1523 # we need to pass on the possibly altered CHIA_ROOT
1524 os.environ["CHIA_ROOT"] = str(root_path)
1525 service_executable = executable_for_service(service_array[0])
1526@@ -417,7 +551,7 @@ def launch_plotter(root_path, service_name, service_array):
1527 startupinfo = subprocess.STARTUPINFO() # type: ignore
1528 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore
1529
1530- plotter_path = plotter_log_path(root_path)
1531+ plotter_path = plotter_log_path(root_path, id)
1532
1533 if plotter_path.parent.exists():
1534 if plotter_path.exists():
1535@@ -428,7 +562,7 @@ def launch_plotter(root_path, service_name, service_array):
1536 log.info(f"Service array: {service_array}")
1537 process = subprocess.Popen(service_array, shell=False, stdout=outfile, startupinfo=startupinfo)
1538
1539- pid_path = pid_path_for_service(root_path, service_name)
1540+ pid_path = pid_path_for_service(root_path, service_name, id)
1541 try:
1542 mkdir(pid_path.parent)
1543 with open(pid_path, "w") as f:
1544@@ -479,12 +613,10 @@ def launch_service(root_path, service_command):
1545 return process, pid_path
1546
1547
1548-async def kill_service(root_path, services, service_name, delay_before_kill=15) -> bool:
1549- process = services.get(service_name)
1550- if process is None:
1551- return False
1552- del services[service_name]
1553- pid_path = pid_path_for_service(root_path, service_name)
1554+async def kill_process(
1555+ process, root_path, service_name, id, delay_before_kill=15
1556+) -> bool:
1557+ pid_path = pid_path_for_service(root_path, service_name, id)
1558
1559 if platform == "win32" or platform == "cygwin":
1560 log.info("sending CTRL_BREAK_EVENT signal to %s", service_name)
1561@@ -517,6 +649,16 @@ async def kill_service(root_path, services, service_name, delay_before_kill=15)
1562 return True
1563
1564
1565+async def kill_service(root_path, services, service_name, delay_before_kill=15) -> bool:
1566+ process = services.get(service_name)
1567+ if process is None:
1568+ return False
1569+ del services[service_name]
1570+
1571+ result = await kill_process(process, root_path, service_name, "", delay_before_kill)
1572+ return result
1573+
1574+
1575 def is_running(services, service_name):
1576 process = services.get(service_name)
1577 return process is not None and process.poll() is None
1578diff --git a/src/full_node/block_store.py b/src/full_node/block_store.py
1579index 7173a2fd..0c025c7e 100644
1580--- a/src/full_node/block_store.py
1581+++ b/src/full_node/block_store.py
1582@@ -104,7 +104,7 @@ class BlockStore:
1583 return None
1584
1585 async def get_sub_block_records(
1586- self,
1587+ self,
1588 ) -> Tuple[Dict[bytes32, SubBlockRecord], Optional[bytes32]]:
1589 """
1590 Returns a dictionary with all sub blocks, as well as the header hash of the peak,
1591diff --git a/src/full_node/full_node.py b/src/full_node/full_node.py
1592index 940998a8..aa7006e6 100644
1593--- a/src/full_node/full_node.py
1594+++ b/src/full_node/full_node.py
1595@@ -142,7 +142,8 @@ class FullNode:
1596 self.full_node_peers = FullNodePeers(
1597 self.server,
1598 self.root_path,
1599- self.config["target_peer_count"] - self.config["target_outbound_peer_count"],
1600+ self.config["target_peer_count"]
1601+ - self.config["target_outbound_peer_count"],
1602 self.config["target_outbound_peer_count"],
1603 self.config["peer_db_path"],
1604 self.config["introducer_peer"],
1605@@ -508,6 +509,7 @@ class FullNode:
1606 if added == ReceiveBlockResult.ALREADY_HAVE_BLOCK:
1607 return
1608 elif added == ReceiveBlockResult.INVALID_BLOCK:
1609+ self.log.error(f"Block {header_hash} at height {sub_block.sub_block_height} is invalid with code {error_code}.")
1610 assert error_code is not None
1611 self.log.error(
1612 f"Block {header_hash} at height {sub_block.sub_block_height} is invalid with code {error_code}."
1613@@ -640,10 +642,7 @@ class FullNode:
1614 await self.server.send_to_all([msg], NodeType.WALLET)
1615
1616 elif added == ReceiveBlockResult.ADDED_AS_ORPHAN:
1617- self.log.warning(
1618- f"Received orphan block of height {sub_block.sub_block_height} rh "
1619- f"{sub_block.reward_chain_sub_block.get_hash()}"
1620- )
1621+ self.log.info(f"Received orphan block of height {sub_block.sub_block_height}")
1622 else:
1623 # Should never reach here, all the cases are covered
1624 raise RuntimeError(f"Invalid result from receive_block {added}")
1625@@ -789,7 +788,7 @@ class FullNode:
1626 sub_slot_iters, difficulty = get_sub_slot_iters_and_difficulty(
1627 self.constants,
1628 block,
1629- self.blockchain.sub_height_to_hash,
1630+ self.blockchain.height_to_hash,
1631 prev_sb,
1632 self.blockchain.sub_blocks,
1633 )
1634diff --git a/src/full_node/full_node_api.py b/src/full_node/full_node_api.py
1635index 0673985d..72ae9141 100644
1636--- a/src/full_node/full_node_api.py
1637+++ b/src/full_node/full_node_api.py
1638@@ -192,7 +192,12 @@ class FullNodeAPI:
1639 @api_request
1640 async def respond_proof_of_weight(self, response: full_node_protocol.RespondProofOfWeight) -> Optional[Message]:
1641 self.log.info(f"got weight proof response {response.wp}")
1642- return await self.full_node.weight_proof_handler.validate_weight_proof(response)
1643+ cache = await init_block_block_cache_mock(
1644+ self.full_node.blockchain, uint32(0), self.full_node.blockchain.peak_height
1645+ )
1646+ wpf = WeightProofHandler(self.full_node.constants, cache)
1647+ wpf.set_block_cache(cache)
1648+ return await wpf.validate_weight_proof(response.wp)
1649
1650 @api_request
1651 async def request_sub_block(self, request: full_node_protocol.RequestSubBlock) -> Optional[Message]:
1652diff --git a/src/full_node/mempool_check_conditions.py b/src/full_node/mempool_check_conditions.py
1653index bdb375f7..63d08a06 100644
1654--- a/src/full_node/mempool_check_conditions.py
1655+++ b/src/full_node/mempool_check_conditions.py
1656@@ -11,10 +11,11 @@ from src.util.errors import Err
1657 import time
1658
1659 from src.util.ints import uint64, uint32
1660-from src.wallet.puzzles.generator_loader import GENERATOR_MOD
1661
1662
1663-def mempool_assert_coin_consumed(condition: ConditionVarPair, spend_bundle: SpendBundle) -> Optional[Err]:
1664+def mempool_assert_coin_consumed(
1665+ condition: ConditionVarPair, spend_bundle: SpendBundle
1666+) -> Optional[Err]:
1667 """
1668 Checks coin consumed conditions
1669 Returns None if conditions are met, if not returns the reason why it failed
1670@@ -26,7 +27,9 @@ def mempool_assert_coin_consumed(condition: ConditionVarPair, spend_bundle: Spen
1671 return None
1672
1673
1674-def mempool_assert_my_coin_id(condition: ConditionVarPair, unspent: CoinRecord) -> Optional[Err]:
1675+def mempool_assert_my_coin_id(
1676+ condition: ConditionVarPair, unspent: CoinRecord
1677+) -> Optional[Err]:
1678 """
1679 Checks if CoinID matches the id from the condition
1680 """
1681@@ -35,7 +38,9 @@ def mempool_assert_my_coin_id(condition: ConditionVarPair, unspent: CoinRecord)
1682 return None
1683
1684
1685-def mempool_assert_block_index_exceeds(condition: ConditionVarPair, peak_height: uint32) -> Optional[Err]:
1686+def mempool_assert_block_index_exceeds(
1687+ condition: ConditionVarPair, peak_height: uint32
1688+) -> Optional[Err]:
1689 """
1690 Checks if the next block index exceeds the block index from the condition
1691 """
1692@@ -50,7 +55,7 @@ def mempool_assert_block_index_exceeds(condition: ConditionVarPair, peak_height:
1693
1694
1695 def mempool_assert_block_age_exceeds(
1696- condition: ConditionVarPair, unspent: CoinRecord, peak_height: uint32
1697+ condition: ConditionVarPair, unspent: CoinRecord, peak_height: uint32
1698 ) -> Optional[Err]:
1699 """
1700 Checks if the coin age exceeds the age from the condition
1701@@ -85,7 +90,7 @@ def mempool_assert_relative_time_exceeds(condition: ConditionVarPair, unspent: C
1702 Check if the current time in millis exceeds the time specified by condition
1703 """
1704 try:
1705- expected_mili_time = int_from_bytes(condition.vars[0])
1706+ expected_mili_time = int_from_bytes(condition.var1)
1707 except ValueError:
1708 return Err.INVALID_CONDITION
1709
1710@@ -95,25 +100,42 @@ def mempool_assert_relative_time_exceeds(condition: ConditionVarPair, unspent: C
1711 return None
1712
1713
1714-def get_name_puzzle_conditions(block_program):
1715- cost, result = GENERATOR_MOD.run_with_cost(block_program)
1716+def get_name_puzzle_conditions(
1717+ block_program: Program,
1718+) -> Tuple[Optional[Err], List[NPC], uint64]:
1719+ """
1720+ Returns an error if it's unable to evaluate, otherwise
1721+ returns a list of NPC (coin_name, solved_puzzle_hash, conditions_dict)
1722+ """
1723+ cost_sum = 0
1724+ try:
1725+ cost_run, sexp = block_program.run_with_cost([])
1726+ cost_sum += cost_run
1727+ except Program.EvalError:
1728+ return Err.INVALID_COIN_SOLUTION, [], uint64(0)
1729+
1730 npc_list = []
1731- opcodes = set(item.value for item in ConditionOpcode)
1732- for res in result.as_python():
1733- conditions_list = []
1734- name = res[0]
1735- puzzle_hash = bytes32(res[1])
1736- for cond in res[2]:
1737- if cond[0] in opcodes:
1738- opcode = ConditionOpcode(cond[0])
1739- else:
1740- opcode = ConditionOpcode.UNKNOWN
1741- if len(cond) == 3:
1742- cvp = ConditionVarPair(opcode, cond[1], cond[2])
1743- else:
1744- cvp = ConditionVarPair(opcode, cond[1])
1745- conditions_list.append(cvp)
1746- conditions_dict = conditions_by_opcode(conditions_list)
1747+ for name_solution in sexp.as_iter():
1748+ _ = name_solution.as_python()
1749+ if len(_) != 2:
1750+ return Err.INVALID_COIN_SOLUTION, [], uint64(cost_sum)
1751+ if not isinstance(_[0], bytes) or len(_[0]) != 32:
1752+ return Err.INVALID_COIN_SOLUTION, [], uint64(cost_sum)
1753+ coin_name = bytes32(_[0])
1754+ if not isinstance(_[1], list) or len(_[1]) != 2:
1755+ return Err.INVALID_COIN_SOLUTION, [], uint64(cost_sum)
1756+ puzzle_solution_program = name_solution.rest().first()
1757+ puzzle_program = puzzle_solution_program.first()
1758+ puzzle_hash = Program.to(puzzle_program).get_tree_hash()
1759+ try:
1760+ error, conditions_dict, cost_run = conditions_dict_for_solution(
1761+ puzzle_solution_program
1762+ )
1763+ cost_sum += cost_run
1764+ if error:
1765+ return error, [], uint64(cost_sum)
1766+ except Program.EvalError:
1767+ return Err.INVALID_COIN_SOLUTION, [], uint64(cost_sum)
1768 if conditions_dict is None:
1769 conditions_dict = {}
1770 npc_list.append(NPC(name, puzzle_hash, conditions_dict))
1771@@ -121,10 +143,10 @@ def get_name_puzzle_conditions(block_program):
1772
1773
1774 def mempool_check_conditions_dict(
1775- unspent: CoinRecord,
1776- spend_bundle: SpendBundle,
1777- conditions_dict: Dict[ConditionOpcode, List[ConditionVarPair]],
1778- peak_height: uint32,
1779+ unspent: CoinRecord,
1780+ spend_bundle: SpendBundle,
1781+ conditions_dict: Dict[ConditionOpcode, List[ConditionVarPair]],
1782+ peak_height: uint32,
1783 ) -> Optional[Err]:
1784 """
1785 Check all conditions against current state.
1786diff --git a/src/full_node/mempool_manager.py b/src/full_node/mempool_manager.py
1787index cbb28218..2eb3726e 100644
1788--- a/src/full_node/mempool_manager.py
1789+++ b/src/full_node/mempool_manager.py
1790@@ -4,7 +4,7 @@ from typing import Dict, Optional, Tuple, List, Set
1791 import logging
1792
1793 from chiabip158 import PyBIP158
1794-from blspy import G1Element, AugSchemeMPL, G2Element
1795+from blspy import G1Element, AugSchemeMPL
1796
1797 from src.consensus.constants import ConsensusConstants
1798 from src.consensus.sub_block_record import SubBlockRecord
1799@@ -196,6 +196,7 @@ class MempoolManager:
1800 return None, MempoolInclusionStatus.FAILED, Err.UNKNOWN_UNSPENT
1801
1802 if addition_amount > removal_amount:
1803+ print("Removal: ", removal_record)
1804 print(addition_amount, removal_amount)
1805 return None, MempoolInclusionStatus.FAILED, Err.MINTING_COIN
1806
1807@@ -206,7 +207,7 @@ class MempoolManager:
1808 if ConditionOpcode.ASSERT_FEE in npc.condition_dict:
1809 fee_list: List[ConditionVarPair] = npc.condition_dict[ConditionOpcode.ASSERT_FEE]
1810 for cvp in fee_list:
1811- fee = int_from_bytes(cvp.vars[0])
1812+ fee = int_from_bytes(cvp.var1)
1813 assert_fee_sum = assert_fee_sum + fee
1814
1815 if fees < assert_fee_sum:
1816@@ -282,12 +283,8 @@ class MempoolManager:
1817 return None, MempoolInclusionStatus.FAILED, error
1818
1819 # Verify aggregated signature
1820- if len(pks) == 0 and len(msgs) == 0:
1821- validates = new_spend.aggregated_signature == G2Element.infinity()
1822- else:
1823- validates = AugSchemeMPL.aggregate_verify(pks, msgs, new_spend.aggregated_signature)
1824+ validates = AugSchemeMPL.aggregate_verify(pks, msgs, new_spend.aggregated_signature)
1825 if not validates:
1826- log.warning(f"{pks} {msgs} {new_spend}")
1827 return None, MempoolInclusionStatus.FAILED, Err.BAD_AGGREGATE_SIGNATURE
1828
1829 # Remove all conflicting Coins and SpendBundles
1830diff --git a/src/full_node/weight_proof.py b/src/full_node/weight_proof.py
1831index 1cfae19c..68c78bd0 100644
1832--- a/src/full_node/weight_proof.py
1833+++ b/src/full_node/weight_proof.py
1834@@ -3,14 +3,22 @@ import random
1835 from typing import Dict, Optional, List, Tuple
1836
1837 from src.consensus.constants import ConsensusConstants
1838-from src.consensus.pot_iterations import is_overflow_sub_block, calculate_iterations_quality, calculate_ip_iters
1839+from src.consensus.pot_iterations import (
1840+ is_overflow_sub_block,
1841+ calculate_iterations_quality,
1842+ calculate_ip_iters,
1843+)
1844 from src.consensus.sub_block_record import SubBlockRecord
1845 from src.full_node.block_cache import BlockCache
1846 from src.types.classgroup import ClassgroupElement
1847 from src.types.end_of_slot_bundle import EndOfSubSlotBundle
1848 from src.types.header_block import HeaderBlock
1849 from src.types.sized_bytes import bytes32
1850-from src.types.slots import ChallengeChainSubSlot, RewardChainSubSlot, InfusedChallengeChainSubSlot
1851+from src.types.slots import (
1852+ ChallengeChainSubSlot,
1853+ RewardChainSubSlot,
1854+ InfusedChallengeChainSubSlot,
1855+)
1856 from src.types.sub_epoch_summary import SubEpochSummary
1857 from src.types.vdf import VDFProof, VDFInfo
1858 from src.types.weight_proof import (
1859@@ -22,6 +30,34 @@ from src.types.weight_proof import (
1860 )
1861 from src.util.hash import std_hash
1862 from src.util.ints import uint32, uint64, uint8, uint128
1863+from src.wallet.wallet_block_store import WalletBlockStore
1864+from src.wallet.wallet_blockchain import WalletBlockchain
1865+
1866+
1867+class BlockCache:
1868+ def __init__(self, blockchain: Union[Blockchain, WalletBlockchain]):
1869+ # todo make these read only copies from here
1870+ self._sub_blocks = blockchain.sub_blocks
1871+ self._block_store = blockchain.block_store
1872+ self._sub_height_to_hash = blockchain.sub_height_to_hash
1873+
1874+ async def header_block(self, header_hash: bytes32) -> HeaderBlock:
1875+ if isinstance(self._block_store, BlockStore):
1876+ block = await self._block_store.get_full_block(header_hash)
1877+ assert block is not None
1878+ return await block.get_block_header()
1879+ elif isinstance(self._block_store, WalletBlockStore):
1880+ h_block = await self._block_store.get_header_block(header_hash)
1881+ assert h_block is not None
1882+ return h_block
1883+
1884+ async def height_to_header_block(self, height: uint32) -> HeaderBlock:
1885+ if isinstance(self._block_store, BlockStore):
1886+ block = await self._block_store.get_full_blocks_at([height])
1887+ return await block[0].get_block_header()
1888+ elif isinstance(self._block_store, WalletBlockStore):
1889+ h_block = await self._block_store.get_header_block_at([height])
1890+ return h_block[0]
1891
1892
1893 class WeightProofHandler:
1894@@ -91,7 +127,10 @@ class WeightProofHandler:
1895 # add to needed reward chain recent blocks
1896 header_block = self.block_cache.height_to_header_block(curr_height)
1897 proof_blocks.append(
1898- ProofBlockHeader(header_block.finished_sub_slots, header_block.reward_chain_sub_block)
1899+ ProofBlockHeader(
1900+ header_block.finished_sub_slots,
1901+ header_block.reward_chain_sub_block,
1902+ )
1903 )
1904
1905 blocks_left = uint32(blocks_left - 1)
1906@@ -307,12 +346,10 @@ class WeightProofHandler:
1907 None,
1908 None,
1909 None,
1910- None,
1911 combine_proofs(cc_proofs),
1912 combine_proofs(icc_proofs),
1913 None,
1914 None,
1915- None,
1916 )
1917 )
1918
1919@@ -409,7 +446,10 @@ class WeightProofHandler:
1920 return sub_slots, next_slot_height
1921
1922 def __get_quality_string(
1923- self, segment: SubEpochChallengeSegment, idx: int, ses: SubEpochSummary
1924+ self,
1925+ segment: SubEpochChallengeSegment,
1926+ ses: SubEpochSummary,
1927+ slot_iters: uint64,
1928 ) -> Optional[bytes32]:
1929
1930 # find challenge block sub slot
1931@@ -421,12 +461,25 @@ class WeightProofHandler:
1932 cc_sub_slot = ChallengeChainSubSlot(cc_vdf, icc_vdf.get_hash(), None, None, None)
1933 challenge = cc_sub_slot.get_hash()
1934
1935- if challenge_sub_slot.cc_sp_vdf_info is None:
1936- self.log.info(f"challenge from prev slot {challenge_sub_slot.cc_sp_vdf_info}")
1937+ # check filter
1938+ assert challenge_sub_slot is not None
1939+ assert challenge_sub_slot.proof_of_space is not None
1940+ if challenge_sub_slot.cc_signage_point is None:
1941 cc_sp_hash: bytes32 = cc_sub_slot.get_hash()
1942 else:
1943- self.log.info(f"challenge from sp vdf {challenge_sub_slot.cc_sp_vdf_info}")
1944- cc_sp_hash = challenge_sub_slot.cc_sp_vdf_info.output.get_hash()
1945+ assert challenge_sub_slot.cc_signage_point is not None
1946+ # cc_sp_hash = challenge_sub_slot.cc_signage_point.output.get_hash()
1947+ # TODO(almog): fix
1948+ cc_sp_hash = b""
1949+
1950+ if not AugSchemeMPL.verify(
1951+ challenge_sub_slot.proof_of_space.plot_public_key,
1952+ cc_sp_hash,
1953+ challenge_sub_slot.cc_sp_sig,
1954+ ):
1955+ self.log.error("did not pass filter")
1956+ return None
1957+
1958 # validate proof of space
1959 assert challenge_sub_slot.proof_of_space is not None
1960 return challenge_sub_slot.proof_of_space.verify_and_get_quality_string(
1961@@ -524,7 +577,7 @@ def get_sub_epoch_block_num(last_block: SubBlockRecord, cache: BlockCache) -> ui
1962 raise Exception("block does not finish a sub_epoch")
1963
1964 curr = cache.sub_block_record(last_block.prev_hash)
1965- count: uint32 = uint32(0)
1966+ count = 0
1967 while not curr.sub_epoch_summary_included:
1968 # todo skip overflows from last sub epoch
1969 if curr.sub_block_height == uint32(0):
1970@@ -549,7 +602,9 @@ def choose_sub_epoch(sub_epoch_blocks_n: uint32, rng: random.Random, total_numbe
1971
1972 # returns a challenge chain vdf from infusion point to end of slot
1973 def count_sub_epochs_in_range(
1974- curr: SubBlockRecord, sub_blocks: Dict[bytes32, SubBlockRecord], total_number_of_blocks: int
1975+ curr: SubBlockRecord,
1976+ sub_blocks: Dict[bytes32, SubBlockRecord],
1977+ total_number_of_blocks: int,
1978 ):
1979 sub_epochs_n = 0
1980 while not total_number_of_blocks == 0:
1981@@ -562,7 +617,10 @@ def count_sub_epochs_in_range(
1982
1983
1984 def validate_sub_slot_vdfs(
1985- constants: ConsensusConstants, sub_slot: SubSlotData, vdf_info: VDFInfo, infused: bool
1986+ constants: ConsensusConstants,
1987+ sub_slot: SubSlotData,
1988+ vdf_info: VDFInfo,
1989+ infused: bool,
1990 ) -> bool:
1991 default = ClassgroupElement.get_default_element()
1992 if infused:
1993diff --git a/src/plotting/create_plots.py b/src/plotting/create_plots.py
1994index bc056ef7..9b4214cd 100644
1995--- a/src/plotting/create_plots.py
1996+++ b/src/plotting/create_plots.py
1997@@ -141,6 +141,7 @@ def create_plots(args, root_path, use_datetime=True, test_private_keys: Optional
1998 args.buckets,
1999 args.stripe_size,
2000 args.num_threads,
2001+ args.nobitfield,
2002 )
2003 finished_filenames.append(filename)
2004 else:
2005diff --git a/src/remote/JSONMessage.py b/src/remote/JSONMessage.py
2006new file mode 100644
2007index 00000000..1eb785ee
2008--- /dev/null
2009+++ b/src/remote/JSONMessage.py
2010@@ -0,0 +1,96 @@
2011+import datetime
2012+import json
2013+
2014+from typing import Any, Optional
2015+
2016+
2017+class JSONMessage:
2018+ def __init__(self, d):
2019+ self.d = d
2020+
2021+ @classmethod
2022+ def deserialize(cls, blob):
2023+ return cls.deserialize_text(blob.decode("utf8"))
2024+
2025+ @classmethod
2026+ def deserialize_text(cls, text):
2027+ return cls(json.loads(text))
2028+
2029+ def serialize(self):
2030+ return self.serialize_text().encode("utf8")
2031+
2032+ def serialize_text(self):
2033+ return json.dumps(self.d)
2034+
2035+ @classmethod
2036+ def for_invocation(cls, method_name, args, kwargs, source, target):
2037+ d = dict(m=method_name)
2038+ if args:
2039+ d["a"] = args
2040+ if kwargs:
2041+ d["k"] = kwargs
2042+ if source is not None:
2043+ d["s"] = source
2044+ if target is not None:
2045+ d["t"] = target
2046+
2047+ return cls(d)
2048+
2049+ @classmethod
2050+ def for_response(cls, target, r):
2051+ return cls(dict(t=target, r=r))
2052+
2053+ @classmethod
2054+ def for_exception(cls, target, exception):
2055+ return cls(dict(t=target, e=repr(exception)))
2056+
2057+ def source(self):
2058+ return self.d.get("s")
2059+
2060+ def target(self):
2061+ return self.d.get("t", 0)
2062+
2063+ def method_name(self) -> Optional[str]:
2064+ return self.d.get("m")
2065+
2066+ def exception(self) -> Optional[Exception]:
2067+ e_text = self.d.get("e")
2068+ if e_text:
2069+ return IOError(e_text)
2070+ return None
2071+
2072+ def response(self) -> Optional[Any]:
2073+ return self.d.get("r")
2074+
2075+ def args_and_kwargs(self):
2076+ pair = (self.d.get("a", []), self.d.get("k", {}))
2077+ return pair
2078+
2079+ @classmethod
2080+ def from_simple_types(cls, v, t, rpc_streamer):
2081+ d = {
2082+ None: lambda a: None,
2083+ bool: lambda a: True if a else False,
2084+ str: lambda a: a,
2085+ int: lambda a: a,
2086+ datetime.datetime: lambda v: datetime.datetime.fromtimestamp(float(v)),
2087+ }
2088+ return cls.convert_with_table(v, t, d)
2089+
2090+ @classmethod
2091+ def to_simple_types(cls, v, t, rpc_streamer):
2092+ d = {
2093+ None: lambda a: 0,
2094+ bool: lambda a: 1 if a else 0,
2095+ str: lambda a: a,
2096+ int: lambda a: a,
2097+ datetime.datetime: lambda v: str(v.timestamp()),
2098+ }
2099+ return cls.convert_with_table(v, t, d)
2100+
2101+ @classmethod
2102+ def convert_with_table(cls, v, t, lookup):
2103+ f = lookup.get(t)
2104+ if f:
2105+ return f(v)
2106+ raise TypeError(f"can't convert {v} to type {t}")
2107diff --git a/src/remote/RPCMessage.py b/src/remote/RPCMessage.py
2108new file mode 100644
2109index 00000000..f55688ab
2110--- /dev/null
2111+++ b/src/remote/RPCMessage.py
2112@@ -0,0 +1,48 @@
2113+from typing import Any, Optional, Type
2114+
2115+
2116+class RPCMessage:
2117+ @classmethod
2118+ def deserialize(cls, blob):
2119+ pass
2120+
2121+ def serialize(self):
2122+ pass
2123+
2124+ @classmethod
2125+ def for_invocation(cls, method_name, args, kwargs, source, target):
2126+ pass
2127+
2128+ @classmethod
2129+ def for_response(cls, target, r):
2130+ pass
2131+
2132+ @classmethod
2133+ def for_exception(cls, target, text):
2134+ pass
2135+
2136+ def source(self):
2137+ pass
2138+
2139+ def target(self):
2140+ pass
2141+
2142+ def exception(self) -> Optional[Exception]:
2143+ pass
2144+
2145+ def response(self) -> Optional[Any]:
2146+ pass
2147+
2148+ def method_name(self) -> Optional[str]:
2149+ # return None if it's not a request
2150+ pass
2151+
2152+ def args_and_kwargs(self):
2153+ pass
2154+
2155+ def from_simple_types(self, v: Any, t: Type, rpc_streamer) -> Any:
2156+ pass
2157+
2158+ @classmethod
2159+ def to_simple_types(cls: Type, v: Any, t: Type, rpc_streamer) -> Any:
2160+ pass
2161diff --git a/src/remote/RPCStream.py b/src/remote/RPCStream.py
2162new file mode 100644
2163index 00000000..df4ec94f
2164--- /dev/null
2165+++ b/src/remote/RPCStream.py
2166@@ -0,0 +1,196 @@
2167+import asyncio
2168+import weakref
2169+
2170+from typing import Any, AsyncGenerator, Awaitable, Callable, Dict, List, Optional, Type
2171+
2172+from .proxy import Proxy
2173+from .response import Response
2174+from .RPCMessage import RPCMessage
2175+
2176+
2177+class RPCStream:
2178+ def __init__(
2179+ self,
2180+ msg_aiter_in: AsyncGenerator[RPCMessage, None],
2181+ async_msg_out_callback,
2182+ rpc_message_class: Type[RPCMessage],
2183+ bad_channel_callback=None,
2184+ ):
2185+ """
2186+ msg_aiter_in: yields `RPCMessage`
2187+ async_msg_out_callback: accepts push of `RPCMessage`
2188+ msg_for_invocation: turns the invocation into an (opaque) message
2189+ bad_channel_callback: this is called when a reference an invalid channel occurs. For debugging.
2190+ """
2191+ self._msg_aiter_in = msg_aiter_in
2192+ self._async_msg_out_callback = async_msg_out_callback
2193+ self._rpc_message_class: Type[RPCMessage] = rpc_message_class
2194+ self._bad_channel_callback = bad_channel_callback
2195+ self._next_channel: int = 0
2196+ self._inputs_task: Optional[Awaitable] = None
2197+ self._local_objects_by_channel: Any = weakref.WeakValueDictionary() # Type Dict[int, Any]
2198+ self._remote_channels_by_proxy: Any = weakref.WeakKeyDictionary() # Type : Dict[Proxy, int]
2199+
2200+ def next_channel(self) -> int:
2201+ channel = self._next_channel
2202+ self._next_channel += 1
2203+ return channel
2204+
2205+ def register_local_obj(self, obj: Any):
2206+ if obj in self._local_objects_by_channel:
2207+ return self._local_objects_by_channel.get(obj)
2208+ channel = self.next_channel()
2209+ self._local_objects_by_channel[channel] = obj
2210+ return channel
2211+
2212+ def local_object_for_channel(self, channel: int) -> Optional[Any]:
2213+ return self._local_objects_by_channel.get(channel)
2214+
2215+ def remote_obj(self, cls, channel: int) -> Proxy:
2216+ """
2217+ This returns a `Proxy` instance which only allows async method invocations.
2218+ """
2219+
2220+ async def callback_f(attr_name, args, kwargs, annotations, is_one_shot):
2221+ future = asyncio.Future()
2222+ return_type = annotations.get("return")
2223+ response = Response(future, return_type)
2224+ source = self.register_local_obj(response)
2225+
2226+ to_simple_types = self._rpc_message_class.to_simple_types
2227+ raw_args, raw_kwargs = recast_arguments(annotations, to_simple_types, args, kwargs, self)
2228+ msg = self._rpc_message_class.for_invocation(attr_name, raw_args, raw_kwargs, source, channel)
2229+ await self._async_msg_out_callback(msg)
2230+ if is_one_shot:
2231+ return None
2232+
2233+ return await future
2234+
2235+ proxy = Proxy(cls, callback_f)
2236+ self._remote_channels_by_proxy[proxy] = channel
2237+ return proxy
2238+
2239+ def start(self) -> None:
2240+ """
2241+ Start the task that fetches requests and generates responses.
2242+ It runs until the `msg_aiter_in` stops.
2243+ """
2244+ if self._inputs_task:
2245+ raise RuntimeError(f"{self} already running")
2246+ self._inputs_task = asyncio.create_task(self._run_inputs())
2247+
2248+ async def process_msg_for_obj(self, msg: RPCMessage, obj: Any) -> Any:
2249+ """
2250+ This method accepts a message and an object, and handles it.
2251+ There are two cases: the message is a request, or the message is a response.
2252+ """
2253+ # check if request vs response
2254+ method_name = msg.method_name()
2255+ if method_name:
2256+ # it's a request
2257+
2258+ source = msg.source()
2259+ try:
2260+ method = getattr(obj, method_name, None)
2261+ if method is None:
2262+ raise ValueError(f"no method {method} on {obj}")
2263+ annotations = method.__annotations__
2264+
2265+ raw_args, raw_kwargs = msg.args_and_kwargs()
2266+ args, kwargs = recast_arguments(annotations, msg.from_simple_types, raw_args, raw_kwargs, self)
2267+ is_one_shot = getattr(method, "one_shot", False)
2268+ r = await method(*args, **kwargs)
2269+
2270+ if is_one_shot:
2271+ return None
2272+
2273+ return_type = annotations.get("return")
2274+ simple_r = recast_to_type(r, return_type, msg.to_simple_types, self)
2275+
2276+ return self._rpc_message_class.for_response(source, simple_r)
2277+ except Exception as ex:
2278+ return self._rpc_message_class.for_exception(source, ex)
2279+
2280+ # it's a response, and obj is a Response
2281+ return_type = obj.return_type
2282+ exception = msg.exception()
2283+ if exception:
2284+ obj.future.set_exception(exception)
2285+ else:
2286+ final_r = recast_to_type(msg.response(), return_type, msg.from_simple_types, self)
2287+ obj.future.set_result(final_r)
2288+ return None
2289+
2290+ async def handle_message(self, msg: RPCMessage) -> None:
2291+ target = msg.target()
2292+ obj = self._local_objects_by_channel.get(target)
2293+ if obj is None:
2294+ if self._bad_channel_callback:
2295+ self._bad_channel_callback(target)
2296+ return
2297+ r_msg = await self.process_msg_for_obj(msg, obj)
2298+ if r_msg:
2299+ await self._async_msg_out_callback(r_msg)
2300+
2301+ async def _run_inputs(self):
2302+ async for msg in self._msg_aiter_in:
2303+ await self.handle_message(msg)
2304+
2305+ async def await_closed(self):
2306+ """
2307+ Wait for `msg_aiter_in` to stop.
2308+ """
2309+ await self._inputs_task
2310+
2311+
2312+def recast_to_type(
2313+ value: Any,
2314+ the_type: Type,
2315+ cast_simple_type: Callable[[Any, Type, RPCStream], Any],
2316+ rpc_stream: RPCStream,
2317+) -> Any:
2318+ """
2319+ Take the given value `value`, and recast it to type `the_type`, using `cast_simple_type`,
2320+ drilling down through the hierarchy if necessary.
2321+ """
2322+
2323+ origin = getattr(the_type, "__origin__", None)
2324+
2325+ if origin is dict:
2326+ key_type, value_type = the_type.__args__
2327+ return {
2328+ recast_to_type(k, key_type, cast_simple_type, rpc_stream): recast_to_type(
2329+ v, value_type, cast_simple_type, rpc_stream
2330+ )
2331+ for k, v in value.items()
2332+ }
2333+
2334+ if origin is list:
2335+ value_type = the_type.__args__[0]
2336+ return list(recast_to_type(_, value_type, cast_simple_type, rpc_stream) for _ in value)
2337+
2338+ return cast_simple_type(value, the_type, rpc_stream)
2339+
2340+
2341+def recast_arguments(
2342+ annotations: Dict[str, Type],
2343+ cast_simple_type: Callable[[Any, Type, RPCStream], Any],
2344+ args: List[Any],
2345+ kwargs: Dict[str, Any],
2346+ rpc_stream: RPCStream,
2347+) -> Any:
2348+ """
2349+ Returns `args`, `kwargs`, using the annotation hints and cast function.
2350+ """
2351+
2352+ cast_args = [recast_to_type(v, t, cast_simple_type, rpc_stream) for v, t in zip(args, annotations.values())]
2353+
2354+ cast_kwargs = {}
2355+
2356+ for k, t in kwargs.items():
2357+ annotation = annotations.get(k)
2358+ if annotation is None:
2359+ raise ValueError("Annotation is None")
2360+ cast_kwargs[k] = recast_to_type(t, annotation, cast_simple_type, rpc_stream)
2361+
2362+ return cast_args, cast_kwargs
2363diff --git a/src/remote/__init__.py b/src/remote/__init__.py
2364new file mode 100644
2365index 00000000..e69de29b
2366diff --git a/src/remote/demo.py b/src/remote/demo.py
2367new file mode 100644
2368index 00000000..ac5e6b57
2369--- /dev/null
2370+++ b/src/remote/demo.py
2371@@ -0,0 +1,52 @@
2372+class DemoAPI:
2373+ def __init__(self, v):
2374+ self._v = v
2375+
2376+ async def add(self, t: int) -> int:
2377+ return self._v + t
2378+
2379+ async def multiply(self, t: int) -> int:
2380+ return self._v * t
2381+
2382+ async def inc(self) -> None:
2383+ self._v += 1
2384+
2385+
2386+"""
2387+You can run this demo from the command-line, using ipython
2388+
2389+pip install ipython
2390+
2391+SERVER:
2392+
2393+ipython
2394+
2395+import asyncio; from src.remote.demo import DemoAPI;
2396+from src.remote.websocket_server import simple_server;
2397+d = DemoAPI(100); await asyncio.Task(simple_server(12345, d))
2398+
2399+
2400+CLIENT:
2401+
2402+ipython
2403+from src.remote.demo import DemoAPI;
2404+from src.remote.websocket_client import connect_to_remote_api;
2405+demo = await connect_to_remote_api("ws://127.0.0.1:12345/ws/", DemoAPI)
2406+print(await demo.add(100))
2407+print(await demo.inc())
2408+
2409+
2410+
2411+TO LOG THE JSON, apply this patch:
2412+
2413+--- a/src/remote/JSONMessage.py
2414++++ b/src/remote/JSONMessage.py
2415+@@ -14,6 +14,7 @@ class JSONMessage:
2416+
2417+ @classmethod
2418+ def deserialize_text(cls, text):
2419++ print(text)
2420+ return cls(json.loads(text))
2421+
2422+ def serialize(self):
2423+"""
2424diff --git a/src/remote/json_packaging.py b/src/remote/json_packaging.py
2425new file mode 100644
2426index 00000000..8ceee72b
2427--- /dev/null
2428+++ b/src/remote/json_packaging.py
2429@@ -0,0 +1,66 @@
2430+"""
2431+This serves as an example for how to stream messages using JSON.
2432+
2433+If you replace this, you can change how messages are marshaled.
2434+
2435+Requests:
2436+{
2437+ s: source_object, # an integer
2438+ t: target_object, # an integer, default 0 object used if missing
2439+ m: method_name,
2440+ a: args, # *args arguments, or [] if missing
2441+ k: kwargs, # **kwargs arguments, or {} if missing
2442+}
2443+
2444+Responses:
2445+{
2446+ t: target_object, # use the source of the request
2447+ r: return_value,
2448+ e: text of remote exception (or missing if there is an r value)
2449+}
2450+"""
2451+
2452+from aiter import map_aiter
2453+
2454+
2455+from .JSONMessage import JSONMessage
2456+from .RPCStream import RPCStream
2457+
2458+
2459+def make_push_callback(push):
2460+ """
2461+ This is just an async wrapper around a synchronous function.
2462+ """
2463+
2464+ async def push_callback(msg):
2465+ await push(msg.serialize_text())
2466+
2467+ return push_callback
2468+
2469+
2470+def rpc_stream(ws, msg_aiter_in, async_msg_out_callback):
2471+ return RPCStream(
2472+ msg_aiter_in,
2473+ async_msg_out_callback,
2474+ JSONMessage,
2475+ )
2476+
2477+
2478+"""
2479+There are two main websocket libraries: `websockets` and `aiohttp`, and each
2480+creates slightly different natural aiter streams, so these two functions
2481+make them look the same.
2482+"""
2483+
2484+
2485+def rpc_stream_for_websocket(ws):
2486+ msg_aiter_in = map_aiter(JSONMessage.deserialize_text, ws)
2487+ async_msg_out_callback = make_push_callback(ws.push)
2488+ return rpc_stream(ws, msg_aiter_in, async_msg_out_callback)
2489+
2490+
2491+def rpc_stream_for_websocket_aiohttp(ws):
2492+ aiter_1 = map_aiter(lambda _: _.data, ws)
2493+ msg_aiter_in = map_aiter(JSONMessage.deserialize_text, aiter_1)
2494+ async_msg_out_callback = make_push_callback(ws.send_str)
2495+ return rpc_stream(ws, msg_aiter_in, async_msg_out_callback)
2496diff --git a/src/remote/one_shot.py b/src/remote/one_shot.py
2497new file mode 100644
2498index 00000000..59736569
2499--- /dev/null
2500+++ b/src/remote/one_shot.py
2501@@ -0,0 +1,8 @@
2502+def one_shot(method):
2503+ async def new_f(*args, **kwargs):
2504+ await method(*args, **kwargs)
2505+ return None
2506+
2507+ new_f.__annotations__ = method.__annotations__
2508+ new_f.one_shot = True
2509+ return new_f
2510diff --git a/src/remote/proxy.py b/src/remote/proxy.py
2511new file mode 100644
2512index 00000000..4834cc1f
2513--- /dev/null
2514+++ b/src/remote/proxy.py
2515@@ -0,0 +1,43 @@
2516+class Proxy:
2517+ """
2518+ This class is a "proxy" object that turns all its attributes
2519+ into callables that simply invoke "callback_f" with the name
2520+ of the attribute and the given context.
2521+
2522+ This is so you can create a proxy, then do something like
2523+
2524+ proxy.call_my_function(foo, bar)
2525+
2526+ and it will actually call
2527+
2528+ callback_f("call_my_function", context, foo, bar)
2529+
2530+ so the callback_f can actually start a remote procedure call.
2531+ """
2532+
2533+ def __init__(self, cls, callback_f):
2534+ self.cls = cls
2535+ self.callback_f = callback_f
2536+
2537+ def __getattr__(self, attr_name: str):
2538+ """
2539+ Call the callback_f with `attr_name`, `args`, `kwargs`, `annotations`.
2540+ """
2541+
2542+ async def invoke(*args, **kwargs):
2543+ # look in the class for the attribute
2544+ # make sure it's an async function
2545+ # collect up the metadata with types to build args, kwargs with `Argument`
2546+ attribute = getattr(self.cls, attr_name, None)
2547+ if attribute is None:
2548+ raise AttributeError(f"bad attribute {attr_name}")
2549+ is_one_shot = getattr(attribute, "one_shot", False)
2550+ annotations = attribute.__annotations__
2551+ return await self.callback_f(
2552+ attr_name, args, kwargs, annotations, is_one_shot
2553+ )
2554+
2555+ return invoke
2556+
2557+ def __repr__(self):
2558+ return f"<Proxy for {self.cls} at {hex(id(self))}>"
2559diff --git a/src/remote/response.py b/src/remote/response.py
2560new file mode 100644
2561index 00000000..3c362250
2562--- /dev/null
2563+++ b/src/remote/response.py
2564@@ -0,0 +1,13 @@
2565+import asyncio
2566+
2567+from dataclasses import dataclass
2568+from typing import Type
2569+
2570+
2571+@dataclass
2572+class Response:
2573+ future: asyncio.Future
2574+ return_type: Type
2575+
2576+ def __hash__(self):
2577+ return id(self)
2578diff --git a/src/remote/simple_types.py b/src/remote/simple_types.py
2579new file mode 100644
2580index 00000000..bafb5a32
2581--- /dev/null
2582+++ b/src/remote/simple_types.py
2583@@ -0,0 +1,37 @@
2584+"""
2585+This converts arbitrary python types into "simple" types supported by JSON.
2586+
2587+If you have additional python types that need to be serialized that aren't
2588+currently supported, you can add support here.
2589+"""
2590+
2591+import datetime
2592+
2593+
2594+def convert_with_table(v, t, lookup):
2595+ f = lookup.get(t)
2596+ if f:
2597+ return f(v)
2598+ raise TypeError(f"can't convert {v} to type {t}")
2599+
2600+
2601+def from_simple_types(v, t):
2602+ d = {
2603+ None: lambda a: None,
2604+ bool: lambda a: True if a else False,
2605+ str: lambda a: a,
2606+ int: lambda a: a,
2607+ datetime.datetime: lambda v: datetime.datetime.fromtimestamp(float(v)),
2608+ }
2609+ return convert_with_table(v, t, d)
2610+
2611+
2612+def to_simple_types(v, t):
2613+ d = {
2614+ None: lambda a: 0,
2615+ bool: lambda a: 1 if a else 0,
2616+ str: lambda a: a,
2617+ int: lambda a: a,
2618+ datetime.datetime: lambda v: str(v.timestamp()),
2619+ }
2620+ return convert_with_table(v, t, d)
2621diff --git a/src/remote/typecasting.py b/src/remote/typecasting.py
2622new file mode 100644
2623index 00000000..d9020765
2624--- /dev/null
2625+++ b/src/remote/typecasting.py
2626@@ -0,0 +1,54 @@
2627+from typing import Any, Callable, Dict, List, Type
2628+
2629+
2630+def recast_to_type(
2631+ value: Any, the_type: Type, cast_simple_type: Callable[[Any, Type], Any]
2632+):
2633+ """
2634+ Take the given value `value`, and recast it to type `the_type`, using `cast_simple_type`,
2635+ drilling down through the hierarchy if necessary.
2636+ """
2637+
2638+ origin = getattr(the_type, "__origin__", None)
2639+
2640+ if origin is dict:
2641+ key_type, value_type = the_type.__args__
2642+ return {
2643+ recast_to_type(k, key_type, cast_simple_type): recast_to_type(
2644+ v, value_type, cast_simple_type
2645+ )
2646+ for k, v in value.items()
2647+ }
2648+
2649+ if origin is list:
2650+ value_type = the_type.__args__[0]
2651+ return list(recast_to_type(_, value_type, cast_simple_type) for _ in value)
2652+
2653+ return cast_simple_type(value, the_type)
2654+
2655+
2656+def recast_arguments(
2657+ annotations: Dict[str, Type],
2658+ cast_simple_type: Callable[[Any, Type], Any],
2659+ args: List[Any],
2660+ kwargs: Dict[str, Any],
2661+):
2662+ """
2663+ Returns `args`, `kwargs`, using the annotation hints and cast function.
2664+ """
2665+
2666+ cast_args = [
2667+ recast_to_type(v, t, cast_simple_type)
2668+ for v, t in zip(args, annotations.values())
2669+ ]
2670+
2671+ cast_kwargs = {}
2672+
2673+ for k, t in kwargs.items():
2674+ kw = kwargs[k]
2675+ annotation = annotations.get(k)
2676+ if annotation is None:
2677+ raise ValueError("Annotation is None")
2678+ cast_kwargs[k] = recast_to_type(kw, annotation, cast_simple_type)
2679+
2680+ return cast_args, cast_kwargs
2681diff --git a/src/remote/websocket_client.py b/src/remote/websocket_client.py
2682new file mode 100644
2683index 00000000..25e6d831
2684--- /dev/null
2685+++ b/src/remote/websocket_client.py
2686@@ -0,0 +1,34 @@
2687+import websockets
2688+
2689+from .json_packaging import rpc_stream_for_websocket
2690+
2691+
2692+class WebsocketRemote:
2693+ def __init__(self, uri):
2694+ self._uri = uri
2695+
2696+ async def start(self):
2697+ self._websocket = await websockets.connect(self._uri)
2698+
2699+ async def __aiter__(self):
2700+ while True:
2701+ _ = await self._websocket.recv()
2702+ yield _
2703+
2704+ async def push(self, msg):
2705+ await self._websocket.send(msg)
2706+
2707+
2708+async def connect_to_remote_api(url, api):
2709+ """
2710+ The given API will be attached to target `0`. The remote better
2711+ have an object that looks like the given api also attached to `0`
2712+ or you'll surely suffer.
2713+ """
2714+ ws = WebsocketRemote(url)
2715+ await ws.start()
2716+ rpc_stream = rpc_stream_for_websocket(ws)
2717+ remote_api = rpc_stream.remote_obj(api, 0)
2718+ rpc_stream.start()
2719+
2720+ return remote_api
2721diff --git a/src/remote/websocket_server.py b/src/remote/websocket_server.py
2722new file mode 100644
2723index 00000000..7f8096f8
2724--- /dev/null
2725+++ b/src/remote/websocket_server.py
2726@@ -0,0 +1,81 @@
2727+import asyncio
2728+import logging
2729+
2730+from aiohttp import web
2731+
2732+from .json_packaging import rpc_stream_for_websocket_aiohttp
2733+
2734+log = logging.getLogger(__name__)
2735+
2736+
2737+def create_server_for_ws_callback(ws_callback):
2738+ routes = web.RouteTableDef()
2739+
2740+ @routes.get("/ws/")
2741+ async def ws_request(request):
2742+ ws = web.WebSocketResponse()
2743+ await ws.prepare(request)
2744+ await ws_callback(ws)
2745+ return ws
2746+
2747+ app = web.Application()
2748+ app.add_routes(routes)
2749+ return app
2750+
2751+
2752+async def create_unix_site(runner, path):
2753+ site = web.UnixSite(runner, path)
2754+ await site.start()
2755+ return site
2756+
2757+
2758+async def create_tcp_site(runner, path, start_port, end_port=65536, host="127.0.0.1"):
2759+ port = start_port
2760+ while port < end_port:
2761+ site = web.TCPSite(runner, port=port, host=host)
2762+ try:
2763+ await site.start()
2764+ return site, port
2765+ except IOError:
2766+ port += 1
2767+ raise IOError("couldn't find a port to listen on")
2768+
2769+
2770+def ws_callback_for_api(api_list):
2771+ async def ws_callback(ws):
2772+ rpc_stream = rpc_stream_for_websocket_aiohttp(ws)
2773+ for api in api_list:
2774+ _ = rpc_stream.register_local_obj(api)
2775+ rpc_stream.start()
2776+ await rpc_stream.await_closed()
2777+
2778+ return ws_callback
2779+
2780+
2781+async def connect_runner_and_apis(site_for_runner_f, *api_list):
2782+ ws_callback = ws_callback_for_api(api_list)
2783+
2784+ app = create_server_for_ws_callback(ws_callback)
2785+ runner = web.AppRunner(app)
2786+ await runner.setup()
2787+
2788+ site = await site_for_runner_f(runner)
2789+
2790+ app["site"] = site
2791+ task = asyncio.create_task(site._server.wait_closed())
2792+ return site, task
2793+
2794+
2795+async def simple_server(port, api, host="0.0.0.0"):
2796+ """
2797+ This simple example will attach an API to a given port. The URL for the client to
2798+ connect to will be ws://127.0.0.1:port/ws/
2799+ """
2800+
2801+ async def site_for_runner(runner):
2802+ site = web.TCPSite(runner, port=port, host=host)
2803+ await site.start()
2804+ return site
2805+
2806+ site, task = await connect_runner_and_apis(site_for_runner, api)
2807+ await task
2808diff --git a/src/types/full_block.py b/src/types/full_block.py
2809index 82b7df68..2ca84b2d 100644
2810--- a/src/types/full_block.py
2811+++ b/src/types/full_block.py
2812@@ -46,7 +46,7 @@ class FullBlock(Streamable):
2813 @property
2814 def height(self):
2815 if self.foliage_block is None:
2816- raise ValueError("Not a block")
2817+ return None
2818 return self.foliage_block.height
2819
2820 @property
2821@@ -72,15 +72,16 @@ class FullBlock(Streamable):
2822 pool_amount = calculate_pool_reward(height)
2823 farmer_amount = calculate_base_farmer_reward(height)
2824 if self.is_block():
2825+ farmer_amount = calculate_base_farmer_reward(height)
2826 assert self.transactions_info is not None
2827 farmer_amount = uint64(farmer_amount + self.transactions_info.fees)
2828 pool_coin: Coin = create_pool_coin(
2829- self.sub_block_height,
2830+ self.height,
2831 self.foliage_sub_block.foliage_sub_block_data.pool_target.puzzle_hash,
2832 pool_amount,
2833 )
2834 farmer_coin: Coin = create_farmer_coin(
2835- self.sub_block_height,
2836+ self.height,
2837 self.foliage_sub_block.foliage_sub_block_data.farmer_reward_puzzle_hash,
2838 farmer_amount,
2839 )
2840@@ -116,7 +117,7 @@ class FullBlock(Streamable):
2841 self.foliage_sub_block,
2842 self.foliage_block,
2843 encoded_filter,
2844- self.transactions_info,
2845+ self.transactions_info
2846 )
2847
2848 def get_included_reward_coins(self) -> Set[Coin]:
2849diff --git a/src/types/weight_proof.py b/src/types/weight_proof.py
2850index ab0edc50..17e11a74 100644
2851--- a/src/types/weight_proof.py
2852+++ b/src/types/weight_proof.py
2853@@ -57,9 +57,18 @@ class SubSlotData(Streamable):
2854 rc_slot_end_info: Optional[VDFInfo]
2855
2856 def is_challenge(self):
2857- if self.proof_of_space is not None:
2858- return True
2859- return False
2860+ if self.cc_slot_end is not None:
2861+ return False
2862+ if self.cc_sp_sig is None:
2863+ return False
2864+ if self.cc_signage_point is None:
2865+ return False
2866+ if self.cc_infusion_point is None:
2867+ return False
2868+ if self.icc_slot_end_info is None:
2869+ return False
2870+
2871+ return True
2872
2873
2874 @dataclass(frozen=True)
2875diff --git a/src/util/block_tools.py b/src/util/block_tools.py
2876index 96c92511..6aca70c8 100644
2877--- a/src/util/block_tools.py
2878+++ b/src/util/block_tools.py
2879@@ -101,6 +101,7 @@ class BlockTools:
2880 self,
2881 constants: ConsensusConstants = test_constants,
2882 root_path: Optional[Path] = None,
2883+ real_plots: bool = False,
2884 ):
2885 self._tempdir = None
2886 if root_path is None:
2887@@ -108,17 +109,31 @@ class BlockTools:
2888 root_path = Path(self._tempdir.name)
2889
2890 self.root_path = root_path
2891+ self.real_plots = real_plots
2892 self.constants = constants
2893- create_default_chia_config(root_path)
2894- self.keychain = Keychain("testing-1.8.0", True)
2895- self.keychain.delete_all_keys()
2896- self.farmer_master_sk = self.keychain.add_private_key(
2897- bytes_to_mnemonic(std_hash(b"block_tools farmer key")), ""
2898- )
2899- self.pool_master_sk = self.keychain.add_private_key(bytes_to_mnemonic(std_hash(b"block_tools pool key")), "")
2900- self.farmer_pk = master_sk_to_farmer_sk(self.farmer_master_sk).get_g1()
2901- self.pool_pk = master_sk_to_pool_sk(self.pool_master_sk).get_g1()
2902- self.init_plots(root_path)
2903+ if not real_plots:
2904+ create_default_chia_config(root_path)
2905+ # No real plots supplied, so we will use the small test plots
2906+ self.use_any_pos = True
2907+ self.keychain = Keychain("testing-1.8.0", True)
2908+ self.keychain.delete_all_keys()
2909+ self.farmer_master_sk = self.keychain.add_private_key(
2910+ bytes_to_mnemonic(std_hash(b"block_tools farmer key")), ""
2911+ )
2912+ self.pool_master_sk = self.keychain.add_private_key(
2913+ bytes_to_mnemonic(std_hash(b"block_tools pool key")), ""
2914+ )
2915+ self.farmer_pk = master_sk_to_farmer_sk(self.farmer_master_sk).get_g1()
2916+ self.pool_pk = master_sk_to_pool_sk(self.pool_master_sk).get_g1()
2917+ self.init_plots(root_path)
2918+
2919+ else:
2920+ self.keychain = Keychain()
2921+ self.use_any_pos = False
2922+ sk_and_ent = self.keychain.get_first_private_key()
2923+ assert sk_and_ent is not None
2924+ self.farmer_master_sk = sk_and_ent[0]
2925+ self.pool_master_sk = sk_and_ent[0]
2926
2927 initialize_ssl(root_path)
2928 self.farmer_ph: bytes32 = create_puzzlehash_for_pk(
2929diff --git a/src/util/errors.py b/src/util/errors.py
2930index 186d8546..59168318 100644
2931--- a/src/util/errors.py
2932+++ b/src/util/errors.py
2933@@ -127,8 +127,7 @@ class Err(Enum):
2934 INVALID_SP_INDEX = 101
2935 TOO_MANY_SUB_BLOCKS = 102
2936 INVALID_CC_CHALLENGE = 103
2937- INVALID_PREFARM = 104
2938- ASSERT_RELATIVE_TIME_EXCEEDS_FAILED = 105
2939+ ASSERT_RELATIVE_TIME_EXCEEDS_FAILED = 104
2940
2941
2942 class ValidationError(Exception):
2943diff --git a/src/wallet/wallet_state_manager.py b/src/wallet/wallet_state_manager.py
2944index 5bb163a3..e21b3a46 100644
2945--- a/src/wallet/wallet_state_manager.py
2946+++ b/src/wallet/wallet_state_manager.py
2947@@ -492,12 +492,7 @@ class WalletStateManager:
2948 if info is not None:
2949 wallet_id, wallet_type = info
2950 await self.coin_added(
2951- coin,
2952- height,
2953- is_coinbase,
2954- is_fee_reward,
2955- uint32(wallet_id),
2956- wallet_type,
2957+ coin, height, is_coinbase, uint32(wallet_id), wallet_type
2958 )
2959
2960 return trade_adds
2961diff --git a/tests/full_node/test_block_store.py b/tests/full_node/test_block_store.py
2962index ce124a91..68563111 100644
2963--- a/tests/full_node/test_block_store.py
2964+++ b/tests/full_node/test_block_store.py
2965@@ -21,7 +21,7 @@ class TestBlockStore:
2966 @pytest.mark.asyncio
2967 async def test_block_store(self):
2968 assert sqlite3.threadsafety == 1
2969- blocks = bt.get_consecutive_blocks(10)
2970+ blocks = bt.get_consecutive_blocks(test_constants, 10)
2971
2972 db_filename = Path("blockchain_test.db")
2973 db_filename_2 = Path("blockchain_test2.db")
2974@@ -84,7 +84,7 @@ class TestBlockStore:
2975 This test was added because the store was deadlocking in certain situations, when fetching and
2976 adding blocks repeatedly. The issue was patched.
2977 """
2978- blocks = bt.get_consecutive_blocks(10)
2979+ blocks = bt.get_consecutive_blocks(test_constants, 10)
2980 db_filename = Path("blockchain_test.db")
2981 db_filename_2 = Path("blockchain_test2.db")
2982
2983diff --git a/tests/consensus/test_blockchain.py b/tests/full_node/test_blockchain.py
2984similarity index 99%
2985rename from tests/consensus/test_blockchain.py
2986rename to tests/full_node/test_blockchain.py
2987index c759b4f9..cbd58485 100644
2988--- a/tests/consensus/test_blockchain.py
2989+++ b/tests/full_node/test_blockchain.py
2990@@ -167,7 +167,7 @@ class TestBlockHeaderValidation:
2991 block.transactions_info,
2992 block.transactions_generator,
2993 )
2994- _, err = await blockchain.validate_unfinished_block(unf, False)
2995+ _, err = await blockchain.validate_unfinished_block(unf)
2996 assert err is None
2997 result, err, _ = await blockchain.receive_block(block)
2998 blocks = bt.get_consecutive_blocks(1, block_list_input=blocks, force_overflow=True)
2999@@ -182,7 +182,7 @@ class TestBlockHeaderValidation:
3000 block.transactions_info,
3001 block.transactions_generator,
3002 )
3003- _, err = await blockchain.validate_unfinished_block(unf, False)
3004+ _, err = await blockchain.validate_unfinished_block(unf)
3005 assert err is None
3006
3007 @pytest.mark.asyncio
3008@@ -865,7 +865,7 @@ class TestBlockHeaderValidation:
3009
3010 @pytest.mark.asyncio
3011 async def test_bad_pos(self, empty_blockchain):
3012- # 5
3013+ # 4
3014 blocks = bt.get_consecutive_blocks(2)
3015 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3016
3017@@ -903,7 +903,7 @@ class TestBlockHeaderValidation:
3018
3019 @pytest.mark.asyncio
3020 async def test_bad_signage_point_index(self, empty_blockchain):
3021- # 6
3022+ # 5
3023 blocks = bt.get_consecutive_blocks(2)
3024 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3025
3026@@ -943,7 +943,7 @@ class TestBlockHeaderValidation:
3027
3028 @pytest.mark.asyncio
3029 async def test_bad_total_iters(self, empty_blockchain):
3030- # 10
3031+ # 8
3032 blocks = bt.get_consecutive_blocks(2)
3033 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3034
3035@@ -954,7 +954,7 @@ class TestBlockHeaderValidation:
3036
3037 @pytest.mark.asyncio
3038 async def test_bad_rc_sp_vdf(self, empty_blockchain):
3039- # 11
3040+ # 9
3041 blocks = bt.get_consecutive_blocks(1)
3042 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3043
3044@@ -988,7 +988,7 @@ class TestBlockHeaderValidation:
3045
3046 @pytest.mark.asyncio
3047 async def test_bad_rc_sp_sig(self, empty_blockchain):
3048- # 12
3049+ # 10
3050 blocks = bt.get_consecutive_blocks(2)
3051 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3052 block_bad = recursive_replace(
3053@@ -998,7 +998,7 @@ class TestBlockHeaderValidation:
3054
3055 @pytest.mark.asyncio
3056 async def test_bad_cc_sp_vdf(self, empty_blockchain):
3057- # 13. Note: does not validate fully due to proof of space being validated first
3058+ # 11. Note: does not validate fully due to proof of space being validated first
3059 blocks = bt.get_consecutive_blocks(1)
3060 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3061
3062@@ -1032,7 +1032,7 @@ class TestBlockHeaderValidation:
3063
3064 @pytest.mark.asyncio
3065 async def test_bad_cc_sp_sig(self, empty_blockchain):
3066- # 14
3067+ # 12
3068 blocks = bt.get_consecutive_blocks(2)
3069 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3070 block_bad = recursive_replace(
3071@@ -1047,7 +1047,7 @@ class TestBlockHeaderValidation:
3072
3073 @pytest.mark.asyncio
3074 async def test_bad_foliage_sb_sig(self, empty_blockchain):
3075- # 16
3076+ # 14
3077 blocks = bt.get_consecutive_blocks(2)
3078 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3079 block_bad = recursive_replace(
3080@@ -1057,7 +1057,7 @@ class TestBlockHeaderValidation:
3081
3082 @pytest.mark.asyncio
3083 async def test_bad_foliage_block_sig(self, empty_blockchain):
3084- # 17
3085+ # 15
3086 blocks = bt.get_consecutive_blocks(1)
3087 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3088
3089@@ -1073,7 +1073,7 @@ class TestBlockHeaderValidation:
3090
3091 @pytest.mark.asyncio
3092 async def test_unfinished_reward_chain_sb_hash(self, empty_blockchain):
3093- # 18
3094+ # 16
3095 blocks = bt.get_consecutive_blocks(2)
3096 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3097 block_bad: FullBlock = recursive_replace(
3098@@ -1086,7 +1086,7 @@ class TestBlockHeaderValidation:
3099
3100 @pytest.mark.asyncio
3101 async def test_pool_target_height(self, empty_blockchain):
3102- # 19
3103+ # 17
3104 blocks = bt.get_consecutive_blocks(3)
3105 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3106 assert (await empty_blockchain.receive_block(blocks[1]))[0] == ReceiveBlockResult.NEW_PEAK
3107@@ -1126,7 +1126,7 @@ class TestBlockHeaderValidation:
3108
3109 @pytest.mark.asyncio
3110 async def test_foliage_data_presence(self, empty_blockchain):
3111- # 22
3112+ # 20
3113 blocks = bt.get_consecutive_blocks(1)
3114 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3115 case_1, case_2 = False, False
3116@@ -1146,7 +1146,7 @@ class TestBlockHeaderValidation:
3117
3118 @pytest.mark.asyncio
3119 async def test_foliage_block_hash(self, empty_blockchain):
3120- # 23
3121+ # 21
3122 blocks = bt.get_consecutive_blocks(1)
3123 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3124 case_1, case_2 = False, False
3125@@ -1168,7 +1168,7 @@ class TestBlockHeaderValidation:
3126
3127 @pytest.mark.asyncio
3128 async def test_genesis_bad_prev_block(self, empty_blockchain):
3129- # 24a
3130+ # 22a
3131 blocks = bt.get_consecutive_blocks(1)
3132 block_bad: FullBlock = recursive_replace(blocks[-1], "foliage_block.prev_block_hash", std_hash(b"2"))
3133 block_bad: FullBlock = recursive_replace(
3134@@ -1181,7 +1181,7 @@ class TestBlockHeaderValidation:
3135
3136 @pytest.mark.asyncio
3137 async def test_bad_prev_block_non_genesis(self, empty_blockchain):
3138- # 24b
3139+ # 22b
3140 blocks = bt.get_consecutive_blocks(1)
3141 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3142 while True:
3143@@ -1202,7 +1202,7 @@ class TestBlockHeaderValidation:
3144
3145 @pytest.mark.asyncio
3146 async def test_bad_filter_hash(self, empty_blockchain):
3147- # 25
3148+ # 23
3149 blocks = bt.get_consecutive_blocks(1)
3150 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3151 while True:
3152@@ -1223,7 +1223,7 @@ class TestBlockHeaderValidation:
3153
3154 @pytest.mark.asyncio
3155 async def test_bad_timestamp(self, empty_blockchain):
3156- # 26
3157+ # 24
3158 blocks = bt.get_consecutive_blocks(1)
3159 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3160 while True:
3161@@ -1259,7 +1259,7 @@ class TestBlockHeaderValidation:
3162
3163 @pytest.mark.asyncio
3164 async def test_sub_block_height(self, empty_blockchain):
3165- # 27
3166+ # 25
3167 blocks = bt.get_consecutive_blocks(2)
3168 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3169 block_bad: FullBlock = recursive_replace(blocks[-1], "reward_chain_sub_block.sub_block_height", 2)
3170@@ -1267,14 +1267,14 @@ class TestBlockHeaderValidation:
3171
3172 @pytest.mark.asyncio
3173 async def test_sub_block_height_genesis(self, empty_blockchain):
3174- # 27
3175+ # 25
3176 blocks = bt.get_consecutive_blocks(1)
3177 block_bad: FullBlock = recursive_replace(blocks[-1], "reward_chain_sub_block.sub_block_height", 1)
3178 assert (await empty_blockchain.receive_block(block_bad))[1] == Err.INVALID_PREV_BLOCK_HASH
3179
3180 @pytest.mark.asyncio
3181 async def test_weight(self, empty_blockchain):
3182- # 28
3183+ # 26
3184 blocks = bt.get_consecutive_blocks(2)
3185 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3186 block_bad: FullBlock = recursive_replace(blocks[-1], "reward_chain_sub_block.weight", 22131)
3187@@ -1282,14 +1282,14 @@ class TestBlockHeaderValidation:
3188
3189 @pytest.mark.asyncio
3190 async def test_weight_genesis(self, empty_blockchain):
3191- # 28
3192+ # 26
3193 blocks = bt.get_consecutive_blocks(1)
3194 block_bad: FullBlock = recursive_replace(blocks[-1], "reward_chain_sub_block.weight", 0)
3195 assert (await empty_blockchain.receive_block(block_bad))[1] == Err.INVALID_WEIGHT
3196
3197 @pytest.mark.asyncio
3198 async def test_bad_cc_ip_vdf(self, empty_blockchain):
3199- # 29
3200+ # 27
3201 blocks = bt.get_consecutive_blocks(1)
3202 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3203
3204@@ -1319,7 +1319,7 @@ class TestBlockHeaderValidation:
3205
3206 @pytest.mark.asyncio
3207 async def test_bad_rc_ip_vdf(self, empty_blockchain):
3208- # 30
3209+ # 28
3210 blocks = bt.get_consecutive_blocks(1)
3211 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3212
3213@@ -1349,7 +1349,7 @@ class TestBlockHeaderValidation:
3214
3215 @pytest.mark.asyncio
3216 async def test_bad_icc_ip_vdf(self, empty_blockchain):
3217- # 31
3218+ # 29
3219 blocks = bt.get_consecutive_blocks(1)
3220 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3221
3222@@ -1380,7 +1380,7 @@ class TestBlockHeaderValidation:
3223
3224 @pytest.mark.asyncio
3225 async def test_reward_block_hash(self, empty_blockchain):
3226- # 32
3227+ # 30
3228 blocks = bt.get_consecutive_blocks(2)
3229 assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
3230 block_bad: FullBlock = recursive_replace(blocks[-1], "foliage_sub_block.reward_block_hash", std_hash(b""))
3231@@ -1413,7 +1413,7 @@ class TestBlockHeaderValidation:
3232 class TestBodyValidation:
3233 @pytest.mark.asyncio
3234 async def test_not_block_but_has_data(self, empty_blockchain):
3235- # TODO
3236+ # 1
3237 pass
3238
3239
3240@@ -1453,12 +1453,13 @@ class TestReorgs:
3241 for block in blocks:
3242 assert (await b.receive_block(block))[0] == ReceiveBlockResult.NEW_PEAK
3243 chain_1_height = b.get_peak().sub_block_height
3244- chain_1_weight = b.get_peak().weight
3245+ chain_1_weight = b.get_peak().sub_block_height
3246 assert chain_1_height == (num_blocks_chain_1 - 1)
3247
3248 # These blocks will have less time between them (timestamp) and therefore will make difficulty go up
3249 # This means that the weight will grow faster, and we can get a heavier chain with lower height
3250 blocks_reorg_chain = bt.get_consecutive_blocks(
3251+ test_constants,
3252 num_blocks_chain_2 - num_blocks_chain_2_start,
3253 blocks[:num_blocks_chain_2_start],
3254 seed=b"2",
3255diff --git a/tests/full_node/test_coin_store.py b/tests/full_node/test_coin_store.py
3256index fff4c572..63e63159 100644
3257--- a/tests/full_node/test_coin_store.py
3258+++ b/tests/full_node/test_coin_store.py
3259@@ -32,6 +32,7 @@ class TestCoinStore:
3260 reward_ph = wallet_a.get_new_puzzlehash()
3261
3262 # Generate some coins
3263+ print("paying to ph", reward_ph)
3264 blocks = bt.get_consecutive_blocks(
3265 10,
3266 [],
3267@@ -65,24 +66,14 @@ class TestCoinStore:
3268 # Adding blocks to the coin store
3269 should_be_included_prev: Set[Coin] = set()
3270 should_be_included: Set[Coin] = set()
3271- last_block_height = -1
3272 for block in blocks:
3273- print(f"Block {block.sub_block_height} {block.is_block()}")
3274- farmer_coin, pool_coin = block.get_future_reward_coins(last_block_height + 1)
3275+ farmer_coin, pool_coin = block.get_future_reward_coins()
3276 should_be_included.add(farmer_coin)
3277 should_be_included.add(pool_coin)
3278 if block.is_block():
3279- last_block_height = block.height
3280 removals, additions = await block.tx_removals_and_additions()
3281-
3282- print(len(block.get_included_reward_coins()), len(should_be_included_prev))
3283- print([c.amount for c in block.get_included_reward_coins()])
3284- print([c.amount for c in should_be_included_prev])
3285-
3286 assert block.get_included_reward_coins() == should_be_included_prev
3287-
3288 await coin_store.new_block(block)
3289-
3290 for expected_coin in should_be_included_prev:
3291 # Check that the coinbase rewards are added
3292 record = await coin_store.get_coin_record(expected_coin.name())
3293@@ -160,18 +151,17 @@ class TestCoinStore:
3294 reorg_index = 8
3295 await coin_store.rollback_to_block(reorg_index)
3296
3297- for block in blocks:
3298- if block.is_block():
3299- coins = block.get_included_reward_coins()
3300- records: List[Optional[CoinRecord]] = [await coin_store.get_coin_record(coin.name()) for coin in coins]
3301+ for c, block in enumerate(blocks):
3302+ coins = block.get_included_reward_coins()
3303+ records: List[Optional[CoinRecord]] = [await coin_store.get_coin_record(coin.name()) for coin in coins]
3304
3305- if block.height <= reorg_index:
3306- for record in records:
3307- assert record is not None
3308- assert record.spent
3309- else:
3310- for record in records:
3311- assert record is None
3312+ if c <= reorg_index:
3313+ for record in records:
3314+ assert record is not None
3315+ assert record.spent
3316+ else:
3317+ for record in records:
3318+ assert record is None
3319
3320 await connection.close()
3321 Path("fndb_test.db").unlink()
3322@@ -192,7 +182,7 @@ class TestCoinStore:
3323
3324 for block in blocks:
3325 await b.receive_block(block)
3326- assert b.get_peak().sub_block_height == initial_block_count - 1
3327+ assert b.get_peak().height == initial_block_count - 1
3328
3329 for c, block in enumerate(blocks):
3330 if block.is_block():
3331@@ -209,12 +199,12 @@ class TestCoinStore:
3332
3333 for reorg_block in blocks_reorg_chain:
3334 result, error_code, _ = await b.receive_block(reorg_block)
3335- print(f"Height {reorg_block.sub_block_height} {initial_block_count - 10} result {result}")
3336- if reorg_block.sub_block_height < initial_block_count - 10:
3337+ print(f"Height {reorg_block.height} {initial_block_count - 10} result {result}")
3338+ if reorg_block.height < initial_block_count - 10:
3339 assert result == ReceiveBlockResult.ALREADY_HAVE_BLOCK
3340- elif reorg_block.sub_block_height < initial_block_count - 1:
3341+ elif reorg_block.height < initial_block_count - 1:
3342 assert result == ReceiveBlockResult.ADDED_AS_ORPHAN
3343- elif reorg_block.sub_block_height >= initial_block_count:
3344+ elif reorg_block.height >= initial_block_count:
3345 assert result == ReceiveBlockResult.NEW_PEAK
3346 if reorg_block.is_block():
3347 coins = reorg_block.get_included_reward_coins()
3348@@ -226,7 +216,7 @@ class TestCoinStore:
3349 assert record.confirmed_block_index == reorg_block.height
3350 assert record.spent_block_index == 0
3351 assert error_code is None
3352- assert b.get_peak().sub_block_height == initial_block_count - 10 + reorg_length - 1
3353+ assert b.get_peak().height == initial_block_count - 10 + reorg_length - 1
3354 except Exception as e:
3355 await connection.close()
3356 Path("blockchain_test.db").unlink()
3357@@ -257,7 +247,7 @@ class TestCoinStore:
3358 last_block_height = block.height
3359 assert b.get_peak().sub_block_height == num_blocks - 1
3360
3361- pool_coin, farmer_coin = blocks[-2].get_future_reward_coins(last_block_height + 1)
3362+ pool_coin, farmer_coin = blocks[-2].get_future_reward_coins(last_block_height)
3363
3364 coins_farmer = await coin_store.get_coin_records_by_puzzle_hash(farmer_coin.puzzle_hash)
3365 coins_pool = await coin_store.get_coin_records_by_puzzle_hash(pool_coin.puzzle_hash)
3366diff --git a/tests/full_node/test_full_node.py b/tests/full_node/test_full_node.py
3367index 931da023..d7cc839e 100644
3368--- a/tests/full_node/test_full_node.py
3369+++ b/tests/full_node/test_full_node.py
3370@@ -80,13 +80,13 @@ async def connect_and_get_peer(server_1: ChiaServer, server_2: ChiaServer) -> WS
3371 await server_2.start_client(PeerInfo("127.0.0.1", uint16(server_1._port)))
3372
3373 async def connected():
3374- for node_id_c, _ in server_1.all_connections.items():
3375+ for node_id_c, _ in server_1.full_nodes.items():
3376 if node_id_c == server_2.node_id:
3377 return True
3378 return False
3379
3380 await time_out_assert(10, connected, True)
3381- for node_id, wsc in server_1.all_connections.items():
3382+ for node_id, wsc in server_1.full_nodes.items():
3383 if node_id == server_2.node_id:
3384 return wsc
3385 assert False
3386@@ -185,12 +185,12 @@ class TestFullNodeProtocol:
3387
3388 await time_out_assert(10, time_out_messages(incoming_queue, "new_peak", 1))
3389
3390- assert full_node_1.full_node.blockchain.get_peak().sub_block_height == 0
3391+ assert full_node_1.full_node.blockchain.get_peak().height == 0
3392
3393 for block in bt.get_consecutive_blocks(30):
3394 await full_node_1.respond_sub_block(fnp.RespondSubBlock(block), peer)
3395
3396- assert full_node_1.full_node.blockchain.get_peak().sub_block_height == 29
3397+ assert full_node_1.full_node.blockchain.get_peak().height == 29
3398
3399 @pytest.mark.asyncio
3400 async def test_respond_end_of_sub_slot(self, two_empty_nodes):
3401@@ -348,7 +348,7 @@ class TestFullNodeProtocol:
3402 for block in blocks:
3403 new_peak = fnp.NewPeak(
3404 block.header_hash,
3405- block.sub_block_height,
3406+ block.height,
3407 block.weight,
3408 uint32(0),
3409 block.reward_chain_sub_block.get_unfinished().get_hash(),
3410@@ -364,7 +364,7 @@ class TestFullNodeProtocol:
3411 # Ignores low weight
3412 new_peak = fnp.NewPeak(
3413 blocks_reorg[-2].header_hash,
3414- blocks_reorg[-2].sub_block_height,
3415+ blocks_reorg[-2].height,
3416 blocks_reorg[-2].weight,
3417 uint32(0),
3418 blocks_reorg[-2].reward_chain_sub_block.get_unfinished().get_hash(),
3419@@ -375,7 +375,7 @@ class TestFullNodeProtocol:
3420 # Does not ignore equal weight
3421 new_peak = fnp.NewPeak(
3422 blocks_reorg[-1].header_hash,
3423- blocks_reorg[-1].sub_block_height,
3424+ blocks_reorg[-1].height,
3425 blocks_reorg[-1].weight,
3426 uint32(0),
3427 blocks_reorg[-1].reward_chain_sub_block.get_unfinished().get_hash(),
3428@@ -409,7 +409,7 @@ class TestFullNodeProtocol:
3429 spend_bundle = wallet_a.generate_signed_transaction(
3430 100,
3431 puzzle_hashes[0],
3432- blocks[1].get_future_reward_coins(1)[0],
3433+ blocks[1].get_future_reward_coins()[0],
3434 condition_dic=conditions_dict,
3435 )
3436 assert spend_bundle is not None
3437@@ -512,34 +512,34 @@ class TestFullNodeProtocol:
3438 assert msg is not None
3439 assert msg.data == fnp.RespondTransaction(spend_bundle)
3440
3441- # @pytest.mark.asyncio
3442- # async def test_respond_transaction_fail(self, two_nodes, wallet_blocks):
3443- # full_node_1, full_node_2, server_1, server_2 = two_nodes
3444- # wallet_a, wallet_receiver, blocks = wallet_blocks
3445- #
3446- # incoming_queue, dummy_node_id = await add_dummy_connection(server_1, 12312)
3447- #
3448- # tx_id = token_bytes(32)
3449- # request_transaction = fnp.RequestTransaction(tx_id)
3450- # msg = await full_node_1.request_transaction(request_transaction)
3451- # assert msg is None
3452- #
3453- # receiver_puzzlehash = wallet_receiver.get_new_puzzlehash()
3454- #
3455- # # Invalid transaction does not propagate
3456- # spend_bundle = wallet_a.generate_signed_transaction(
3457- # 100000000000000,
3458- # receiver_puzzlehash,
3459- # blocks[3].get_coinbase(),
3460- # )
3461- # while incoming_queue.qsize() > 0:
3462- # await incoming_queue.get()
3463- # assert spend_bundle is not None
3464- # respond_transaction = fnp.RespondTransaction(spend_bundle)
3465- # msg = await full_node_1.respond_transaction(respond_transaction)
3466- # assert msg is None
3467- # await asyncio.sleep(1)
3468- # assert incoming_queue.qsize() == 0
3469+ @pytest.mark.asyncio
3470+ async def test_respond_transaction_fail(self, two_nodes, wallet_blocks):
3471+ full_node_1, full_node_2, server_1, server_2 = two_nodes
3472+ wallet_a, wallet_receiver, blocks = wallet_blocks
3473+
3474+ incoming_queue, dummy_node_id = await add_dummy_connection(server_1, 12312)
3475+
3476+ tx_id = token_bytes(32)
3477+ request_transaction = fnp.RequestTransaction(tx_id)
3478+ msg = await full_node_1.request_transaction(request_transaction)
3479+ assert msg is None
3480+
3481+ receiver_puzzlehash = wallet_receiver.get_new_puzzlehash()
3482+
3483+ # Invalid transaction does not propagate
3484+ spend_bundle = wallet_a.generate_signed_transaction(
3485+ 100000000000000,
3486+ receiver_puzzlehash,
3487+ blocks[3].get_coinbase(),
3488+ )
3489+ while incoming_queue.qsize() > 0:
3490+ await incoming_queue.get()
3491+ assert spend_bundle is not None
3492+ respond_transaction = fnp.RespondTransaction(spend_bundle)
3493+ msg = await full_node_1.respond_transaction(respond_transaction)
3494+ assert msg is None
3495+ await asyncio.sleep(1)
3496+ assert incoming_queue.qsize() == 0
3497
3498 # @pytest.mark.asyncio
3499 # async def test_new_unfinished(self, two_nodes, wallet_blocks):
3500@@ -948,7 +948,7 @@ class TestFullNodeProtocol:
3501 # # Don't have
3502 # res = await full_node_1.request_header(wallet_protocol.RequestHeader(uint32(2), blocks[2].header_hash))
3503 # assert isinstance(res.data, wallet_protocol.RejectHeaderRequest)
3504-# assert res.data.sub_block_height == 2
3505+# assert res.data.height == 2
3506 # assert res.data.header_hash == blocks[2].header_hash
3507 #
3508 # @pytest.mark.asyncio
3509@@ -962,7 +962,7 @@ class TestFullNodeProtocol:
3510 #
3511 # # Request removals for nonexisting block fails
3512 # res = await full_node_1.request_removals(
3513-# wallet_protocol.RequestRemovals(blocks_new[-1].sub_block_height, blocks_new[-1].header_hash, None)
3514+# wallet_protocol.RequestRemovals(blocks_new[-1].height, blocks_new[-1].header_hash, None)
3515 # )
3516 # assert isinstance(res.data, wallet_protocol.RejectRemovalsRequest)
3517 #
3518@@ -971,7 +971,7 @@ class TestFullNodeProtocol:
3519 # await full_node_1.respond_sub_block(fnp.RespondSubBlock(block))
3520 #
3521 # res = await full_node_1.request_removals(
3522-# wallet_protocol.RequestRemovals(blocks_new[-1].sub_block_height, blocks_new[-1].header_hash, None)
3523+# wallet_protocol.RequestRemovals(blocks_new[-1].height, blocks_new[-1].header_hash, None)
3524 # )
3525 # assert isinstance(res.data, wallet_protocol.RejectRemovalsRequest)
3526 #
3527@@ -985,7 +985,7 @@ class TestFullNodeProtocol:
3528 # await full_node_1.respond_sub_block(fnp.RespondSubBlock(block))
3529 #
3530 # res = await full_node_1.request_removals(
3531-# wallet_protocol.RequestRemovals(blocks_new[-4].sub_block_height, blocks_new[-4].header_hash, None)
3532+# wallet_protocol.RequestRemovals(blocks_new[-4].height, blocks_new[-4].header_hash, None)
3533 # )
3534 #
3535 # assert isinstance(res.data, wallet_protocol.RespondRemovals)
3536@@ -1019,7 +1019,7 @@ class TestFullNodeProtocol:
3537 # # If no coins requested, respond all coins and NO proof
3538 # res = await full_node_1.request_removals(
3539 # wallet_protocol.RequestRemovals(
3540-# blocks_new[height_with_transactions].sub_block_height,
3541+# blocks_new[height_with_transactions].height,
3542 # blocks_new[height_with_transactions].header_hash,
3543 # None,
3544 # )
3545@@ -1038,7 +1038,7 @@ class TestFullNodeProtocol:
3546 # coin_list = [spend_bundles[0].removals()[0].name()]
3547 # res = await full_node_1.request_removals(
3548 # wallet_protocol.RequestRemovals(
3549-# blocks_new[height_with_transactions].sub_block_height,
3550+# blocks_new[height_with_transactions].height,
3551 # blocks_new[height_with_transactions].header_hash,
3552 # coin_list,
3553 # )
3554@@ -1059,7 +1059,7 @@ class TestFullNodeProtocol:
3555 #
3556 # res = await full_node_1.request_removals(
3557 # wallet_protocol.RequestRemovals(
3558-# blocks_new[height_with_transactions].sub_block_height,
3559+# blocks_new[height_with_transactions].height,
3560 # blocks_new[height_with_transactions].header_hash,
3561 # coin_list,
3562 # )
3563@@ -1080,7 +1080,7 @@ class TestFullNodeProtocol:
3564 #
3565 # res = await full_node_1.request_removals(
3566 # wallet_protocol.RequestRemovals(
3567-# blocks_new[height_with_transactions].sub_block_height,
3568+# blocks_new[height_with_transactions].height,
3569 # blocks_new[height_with_transactions].header_hash,
3570 # coin_list,
3571 # )
3572@@ -1114,7 +1114,7 @@ class TestFullNodeProtocol:
3573 #
3574 # # Request additinos for nonexisting block fails
3575 # res = await full_node_1.request_additions(
3576-# wallet_protocol.RequestAdditions(blocks_new[-1].sub_block_height, blocks_new[-1].header_hash, None)
3577+# wallet_protocol.RequestAdditions(blocks_new[-1].height, blocks_new[-1].header_hash, None)
3578 # )
3579 # assert isinstance(res.data, wallet_protocol.RejectAdditionsRequest)
3580 #
3581@@ -1123,7 +1123,7 @@ class TestFullNodeProtocol:
3582 # await full_node_1.respond_sub_block(fnp.RespondSubBlock(block))
3583 #
3584 # res = await full_node_1.request_additions(
3585-# wallet_protocol.RequestAdditions(blocks_new[-1].sub_block_height, blocks_new[-1].header_hash, None)
3586+# wallet_protocol.RequestAdditions(blocks_new[-1].height, blocks_new[-1].header_hash, None)
3587 # )
3588 # assert isinstance(res.data, wallet_protocol.RejectAdditionsRequest)
3589 #
3590@@ -1137,7 +1137,7 @@ class TestFullNodeProtocol:
3591 # await full_node_1.respond_sub_block(fnp.RespondSubBlock(block))
3592 #
3593 # res = await full_node_1.request_additions(
3594-# wallet_protocol.RequestAdditions(blocks_new[-4].sub_block_height, blocks_new[-4].header_hash, None)
3595+# wallet_protocol.RequestAdditions(blocks_new[-4].height, blocks_new[-4].header_hash, None)
3596 # )
3597 # assert isinstance(res.data, wallet_protocol.RespondAdditions)
3598 # assert len(res.data.coins) == 2
3599@@ -1171,7 +1171,7 @@ class TestFullNodeProtocol:
3600 # # If no puzzle hashes requested, respond all coins and NO proof
3601 # res = await full_node_1.request_additions(
3602 # wallet_protocol.RequestAdditions(
3603-# blocks_new[height_with_transactions].sub_block_height,
3604+# blocks_new[height_with_transactions].height,
3605 # blocks_new[height_with_transactions].header_hash,
3606 # None,
3607 # )
3608@@ -1191,7 +1191,7 @@ class TestFullNodeProtocol:
3609 # ph_list = [puzzle_hashes[0]]
3610 # res = await full_node_1.request_additions(
3611 # wallet_protocol.RequestAdditions(
3612-# blocks_new[height_with_transactions].sub_block_height,
3613+# blocks_new[height_with_transactions].height,
3614 # blocks_new[height_with_transactions].header_hash,
3615 # ph_list,
3616 # )
3617@@ -1219,7 +1219,7 @@ class TestFullNodeProtocol:
3618 # ph_list = [token_bytes(32)]
3619 # res = await full_node_1.request_additions(
3620 # wallet_protocol.RequestAdditions(
3621-# blocks_new[height_with_transactions].sub_block_height,
3622+# blocks_new[height_with_transactions].height,
3623 # blocks_new[height_with_transactions].header_hash,
3624 # ph_list,
3625 # )
3626@@ -1240,7 +1240,7 @@ class TestFullNodeProtocol:
3627 # ph_list = [puzzle_hashes[0], token_bytes(32)]
3628 # res = await full_node_1.request_additions(
3629 # wallet_protocol.RequestAdditions(
3630-# blocks_new[height_with_transactions].sub_block_height,
3631+# blocks_new[height_with_transactions].height,
3632 # blocks_new[height_with_transactions].header_hash,
3633 # ph_list,
3634 # )
3635diff --git a/tests/full_node/test_mempool.py b/tests/full_node/test_mempool.py
3636index 622bd183..91bff507 100644
3637--- a/tests/full_node/test_mempool.py
3638+++ b/tests/full_node/test_mempool.py
3639@@ -67,7 +67,7 @@ class TestMempool:
3640 for block in blocks:
3641 await full_node_1.full_node.respond_sub_block(full_node_protocol.RespondSubBlock(block))
3642
3643- await time_out_assert(60, node_height_at_least, True, full_node_2, 2)
3644+ await time_out_assert(60, node_height_at_least, True, full_node_1, 2)
3645
3646 spend_bundle = generate_test_spend_bundle(list(blocks[-1].get_included_reward_coins())[0])
3647 assert spend_bundle is not None
3648diff --git a/tests/consensus/test_weight_proof.py b/tests/full_node/test_weight_proof.py
3649similarity index 98%
3650rename from tests/consensus/test_weight_proof.py
3651rename to tests/full_node/test_weight_proof.py
3652index e37a58bf..2dc8254e 100644
3653--- a/tests/consensus/test_weight_proof.py
3654+++ b/tests/full_node/test_weight_proof.py
3655@@ -24,7 +24,12 @@ from src.util.default_root import DEFAULT_ROOT_PATH
3656 from src.util.ints import uint32, uint64
3657 from src.util.logging import initialize_logging
3658 from tests.setup_nodes import test_constants
3659-from tests.full_node.fixtures import empty_blockchain, default_1000_blocks, default_400_blocks, default_10000_blocks
3660+from tests.full_node.fixtures import (
3661+ empty_blockchain,
3662+ default_1000_blocks,
3663+ default_400_blocks,
3664+ default_10000_blocks,
3665+)
3666
3667
3668 @pytest.fixture(scope="module")
3669