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
7678interface 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
180188function 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
0 commit comments