Skip to content

Commit 5156465

Browse files
committed
Fast forwardability
1 parent 3b3131a commit 5156465

File tree

2 files changed

+109
-26
lines changed

2 files changed

+109
-26
lines changed

explorer/src/lib/parser.ts

Lines changed: 106 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
Clvm,
44
Coin,
55
CoinSpend,
6+
Constants,
67
Output,
78
Program,
89
Puzzle,
@@ -71,6 +72,7 @@ interface BundleContext {
7172
spentCoinIds: Set<string>;
7273
spentPuzzleHashes: Set<string>;
7374
createdCoinIds: Set<string>;
75+
assertedCoinIds: Set<string>;
7476
}
7577

7678
interface DeserializedCoinSpend {
@@ -98,6 +100,7 @@ export function parseSpendBundle(
98100
spentCoinIds: new Set(),
99101
spentPuzzleHashes: new Set(),
100102
createdCoinIds: new Set(),
103+
assertedCoinIds: new Set(),
101104
};
102105

103106
for (const coinSpend of spendBundle.coinSpends) {
@@ -157,6 +160,11 @@ export function parseSpendBundle(
157160
),
158161
);
159162
}
163+
164+
const assertConcurrentSpend = condition.parseAssertConcurrentSpend();
165+
if (assertConcurrentSpend) {
166+
announcements.assertedCoinIds.add(toHex(assertConcurrentSpend.coinId));
167+
}
160168
}
161169

162170
deserializedCoinSpends.push({
@@ -178,16 +186,85 @@ export function parseSpendBundle(
178186
}
179187

180188
function parseCoinSpend(
181-
{ coinSpend, output, conditions }: DeserializedCoinSpend,
189+
{ coinSpend, output, puzzle, conditions }: DeserializedCoinSpend,
182190
ctx: BundleContext,
183191
): ParsedCoinSpend {
192+
const coinId = coinSpend.coin.coinId();
193+
194+
let isFastForwardable =
195+
bytesEqual(puzzle.modHash, Constants.singletonTopLayerV11Hash()) &&
196+
coinSpend.coin.amount % 2n === 1n &&
197+
!ctx.createdCoinIds.has(toHex(coinId)) &&
198+
!ctx.assertedCoinIds.has(toHex(coinId));
199+
200+
if (isFastForwardable) {
201+
let hasIdenticalOutput = false;
202+
203+
for (const condition of conditions.slice(2)) {
204+
const disqualifyingCondition =
205+
condition.parseAssertMyCoinId() ??
206+
condition.parseAssertMyParentId() ??
207+
condition.parseAssertHeightRelative() ??
208+
condition.parseAssertSecondsRelative() ??
209+
condition.parseAssertBeforeHeightRelative() ??
210+
condition.parseAssertBeforeSecondsRelative() ??
211+
condition.parseAssertMyBirthHeight() ??
212+
condition.parseAssertMyBirthSeconds() ??
213+
condition.parseAssertEphemeral() ??
214+
condition.parseAggSigPuzzle() ??
215+
condition.parseAggSigParent() ??
216+
condition.parseAggSigParentAmount() ??
217+
condition.parseAggSigParentPuzzle() ??
218+
condition.parseCreateCoinAnnouncement();
219+
220+
if (disqualifyingCondition) {
221+
isFastForwardable = false;
222+
}
223+
224+
const sendMessage = condition.parseSendMessage();
225+
if (sendMessage) {
226+
const sender = parseMessageFlags(sendMessage.mode, MessageSide.Sender);
227+
228+
if (sender.parent) {
229+
isFastForwardable = false;
230+
}
231+
}
232+
233+
const receiveMessage = condition.parseReceiveMessage();
234+
if (receiveMessage) {
235+
const receiver = parseMessageFlags(
236+
receiveMessage.mode,
237+
MessageSide.Receiver,
238+
);
239+
240+
if (receiver.parent) {
241+
isFastForwardable = false;
242+
}
243+
}
244+
245+
const createCoin = condition.parseCreateCoin();
246+
if (createCoin) {
247+
if (
248+
bytesEqual(createCoin.puzzleHash, coinSpend.coin.puzzleHash) &&
249+
createCoin.amount === coinSpend.coin.amount
250+
) {
251+
hasIdenticalOutput = true;
252+
}
253+
}
254+
}
255+
256+
if (!hasIdenticalOutput) {
257+
isFastForwardable = false;
258+
}
259+
}
260+
184261
return {
185262
coin: parseCoin(coinSpend.coin),
186263
puzzleReveal: toHex(coinSpend.puzzleReveal),
187264
solution: toHex(coinSpend.solution),
188265
runtimeCost: output.cost.toString(),
189266
conditions: conditions.map((condition) =>
190-
parseCondition(coinSpend.coin, condition, ctx),
267+
parseCondition(coinSpend.coin, condition, ctx, isFastForwardable),
191268
),
192269
};
193270
}
@@ -205,6 +282,7 @@ function parseCondition(
205282
coin: Coin,
206283
condition: Program,
207284
ctx: BundleContext,
285+
isFastForwardable: boolean,
208286
): ParsedCondition {
209287
let name = 'UNKNOWN';
210288
let type = ConditionType.Other;
@@ -305,16 +383,16 @@ function parseCondition(
305383
type = ConditionType.Announcement;
306384
name = 'CREATE_COIN_ANNOUNCEMENT';
307385

308-
args.message = {
309-
value: `0x${toHex(createCoinAnnouncement.message)}`,
310-
type: ConditionArgType.Copiable,
311-
};
312-
313386
args.coin_id = {
314387
value: `0x${toHex(coin.coinId())}`,
315388
type: ConditionArgType.CoinId,
316389
};
317390

391+
args.message = {
392+
value: `0x${toHex(createCoinAnnouncement.message)}`,
393+
type: ConditionArgType.Copiable,
394+
};
395+
318396
const announcementId = toHex(
319397
sha256(
320398
new Uint8Array([...coin.coinId(), ...createCoinAnnouncement.message]),
@@ -341,13 +419,6 @@ function parseCondition(
341419

342420
const announcementId = toHex(assertCoinAnnouncement.announcementId);
343421

344-
if (ctx.announcementMessages[announcementId]) {
345-
args.message = {
346-
value: `0x${toHex(ctx.announcementMessages[announcementId])}`,
347-
type: ConditionArgType.Copiable,
348-
};
349-
}
350-
351422
if (ctx.announcementCoinIds[announcementId]) {
352423
args.coin_id = {
353424
value: `0x${toHex(ctx.announcementCoinIds[announcementId])}`,
@@ -357,6 +428,13 @@ function parseCondition(
357428
warning = 'Announcement does not exist';
358429
}
359430

431+
if (ctx.announcementMessages[announcementId]) {
432+
args.message = {
433+
value: `0x${toHex(ctx.announcementMessages[announcementId])}`,
434+
type: ConditionArgType.Copiable,
435+
};
436+
}
437+
360438
args.announcement_id = {
361439
value: `0x${announcementId}`,
362440
type: ConditionArgType.Copiable,
@@ -368,13 +446,13 @@ function parseCondition(
368446
type = ConditionType.Announcement;
369447
name = 'CREATE_PUZZLE_ANNOUNCEMENT';
370448

371-
args.message = {
372-
value: `0x${toHex(createPuzzleAnnouncement.message)}`,
449+
args.puzzle_hash = {
450+
value: `0x${toHex(coin.puzzleHash)}`,
373451
type: ConditionArgType.Copiable,
374452
};
375453

376-
args.puzzle_hash = {
377-
value: `0x${toHex(coin.puzzleHash)}`,
454+
args.message = {
455+
value: `0x${toHex(createPuzzleAnnouncement.message)}`,
378456
type: ConditionArgType.Copiable,
379457
};
380458

@@ -407,13 +485,6 @@ function parseCondition(
407485

408486
const announcementId = toHex(assertPuzzleAnnouncement.announcementId);
409487

410-
if (ctx.announcementMessages[announcementId]) {
411-
args.message = {
412-
value: `0x${toHex(ctx.announcementMessages[announcementId])}`,
413-
type: ConditionArgType.Copiable,
414-
};
415-
}
416-
417488
if (ctx.announcementPuzzleHashes[announcementId]) {
418489
args.puzzle_hash = {
419490
value: `0x${toHex(ctx.announcementPuzzleHashes[announcementId])}`,
@@ -423,6 +494,13 @@ function parseCondition(
423494
warning = 'Announcement does not exist';
424495
}
425496

497+
if (ctx.announcementMessages[announcementId]) {
498+
args.message = {
499+
value: `0x${toHex(ctx.announcementMessages[announcementId])}`,
500+
type: ConditionArgType.Copiable,
501+
};
502+
}
503+
426504
args.announcement_id = {
427505
value: `0x${announcementId}`,
428506
type: ConditionArgType.Copiable,
@@ -532,7 +610,9 @@ function parseCondition(
532610
};
533611

534612
if (!bytesEqual(coin.parentCoinInfo, assertMyParentId.parentId)) {
535-
warning = 'Parent ID does not match';
613+
warning = isFastForwardable
614+
? 'This spend will need to be fast forwarded'
615+
: 'Parent ID does not match, and this spend cannot be fast forwarded';
536616
}
537617
}
538618

explorer/src/pages/Tools.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Layout } from '@/components/Layout';
22
import { Truncated } from '@/components/Truncated';
33
import { Textarea } from '@/components/ui/textarea';
4+
import { useMintGarden } from '@/hooks/useMintGarden';
45
import { parseJson } from '@/lib/json';
56
import {
67
ConditionArgType,
@@ -72,6 +73,8 @@ interface BundleViewerProps {
7273
}
7374

7475
function BundleViewer({ bundle }: BundleViewerProps) {
76+
const { fetchNft } = useMintGarden();
77+
7578
return (
7679
<div className='flex flex-col gap-2 mt-4'>
7780
{bundle.coinSpends.map((spend) => (

0 commit comments

Comments
 (0)