@@ -69,7 +69,7 @@ describe('session (pure)', () => {
6969 describe ( 'server-authored hints' , ( ) => {
7070 const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex
7171
72- test ( 'prefers requiredCumulative and hydrates channel from challenge hints ' , async ( ) => {
72+ test ( 'hydrates accounting hints without inflating the next signed voucher ' , async ( ) => {
7373 const method = session ( {
7474 getClient : ( ) => pureClient ,
7575 account : pureAccount ,
@@ -95,11 +95,11 @@ describe('session (pure)', () => {
9595 expect ( cred . payload . action ) . toBe ( 'voucher' )
9696 if ( cred . payload . action === 'voucher' ) {
9797 expect ( cred . payload . channelId ) . toBe ( channelId )
98- expect ( cred . payload . cumulativeAmount ) . toBe ( '6000000 ' )
98+ expect ( cred . payload . cumulativeAmount ) . toBe ( '1000000 ' )
9999 }
100100 } )
101101
102- test ( 'does not let stale requiredCumulative move local cumulative backwards ' , async ( ) => {
102+ test ( 'keeps cumulative strictly local across repeated hinted requests ' , async ( ) => {
103103 const method = session ( {
104104 getClient : ( ) => pureClient ,
105105 account : pureAccount ,
@@ -124,10 +124,49 @@ describe('session (pure)', () => {
124124 expect ( first . payload . action ) . toBe ( 'voucher' )
125125 expect ( second . payload . action ) . toBe ( 'voucher' )
126126 if ( first . payload . action === 'voucher' ) {
127- expect ( first . payload . cumulativeAmount ) . toBe ( '6000000 ' )
127+ expect ( first . payload . cumulativeAmount ) . toBe ( '1000000 ' )
128128 }
129129 if ( second . payload . action === 'voucher' ) {
130- expect ( second . payload . cumulativeAmount ) . toBe ( '7000000' )
130+ expect ( second . payload . cumulativeAmount ) . toBe ( '2000000' )
131+ }
132+ } )
133+
134+ test ( 'keeps the current local channel when a server-supplied replacement cannot be verified' , async ( ) => {
135+ const channelIdA = '0x00000000000000000000000000000000000000000000000000000000000000aa' as Hex
136+ const channelIdB = '0x00000000000000000000000000000000000000000000000000000000000000bb' as Hex
137+ const method = session ( {
138+ getClient : ( ) => pureClient ,
139+ account : pureAccount ,
140+ deposit : '10' ,
141+ } )
142+
143+ const challengeA = makeChallenge ( {
144+ methodDetails : {
145+ acceptedCumulative : '5000000' ,
146+ chainId : 42431 ,
147+ channelId : channelIdA ,
148+ deposit : '10000000' ,
149+ escrowContract : escrowAddress ,
150+ spent : '4000000' ,
151+ } ,
152+ } )
153+ const challengeB = makeChallenge ( {
154+ methodDetails : {
155+ chainId : 42431 ,
156+ channelId : channelIdB ,
157+ escrowContract : escrowAddress ,
158+ } ,
159+ } )
160+
161+ await method . createCredential ( { challenge : challengeA , context : { } } )
162+ const result = deserializePayload (
163+ await method . createCredential ( { challenge : challengeB , context : { } } ) ,
164+ )
165+
166+ expect ( result . payload . action ) . toBe ( 'voucher' )
167+ if ( result . payload . action === 'voucher' ) {
168+ expect ( result . payload . channelId ) . toBe ( channelIdA )
169+ expect ( result . payload . cumulativeAmount ) . toBe ( '2000000' )
131170 }
132171 } )
133172 } )
@@ -483,7 +522,7 @@ describe.runIf(isLocalnet)('session (on-chain)', () => {
483522 ) . rejects . toThrow ( 'cannot be reused' )
484523 } )
485524
486- test ( 'falls back to opening a new channel when hints omit cumulative state ' , async ( ) => {
525+ test ( 'throws when a server-supplied channelId cannot be recovered ' , async ( ) => {
487526 const hintedChannelId =
488527 '0x0000000000000000000000000000000000000000000000000000000000000bad' as Hex
489528 const method = session ( {
@@ -502,12 +541,77 @@ describe.runIf(isLocalnet)('session (on-chain)', () => {
502541 } ,
503542 } )
504543
505- const result = await method . createCredential ( { challenge, context : { } } )
506- const cred = deserializePayload ( result )
544+ await expect ( method . createCredential ( { challenge, context : { } } ) ) . rejects . toThrow (
545+ 'cannot be reused' ,
546+ )
547+ } )
507548
508- expect ( cred . payload . action ) . toBe ( 'open' )
509- if ( cred . payload . action === 'open' ) {
510- expect ( cred . payload . channelId ) . not . toBe ( hintedChannelId )
549+ test ( 'ignores stale receipts after rebinding to a newly recovered channel' , async ( ) => {
550+ const { channelId : channelIdA } = await openChannel ( {
551+ escrow : escrowContract ,
552+ payer,
553+ payee,
554+ token : asset ,
555+ deposit : 10_000_000n ,
556+ salt : nextSalt ( ) ,
557+ } )
558+ const { channelId : channelIdB } = await openChannel ( {
559+ escrow : escrowContract ,
560+ payer,
561+ payee,
562+ token : asset ,
563+ deposit : 10_000_000n ,
564+ salt : nextSalt ( ) ,
565+ } )
566+
567+ const method = session ( {
568+ getClient : ( ) => client ,
569+ account : payer ,
570+ deposit : '10' ,
571+ escrowContract,
572+ } )
573+
574+ const challengeA = makeLiveChallenge ( {
575+ methodDetails : {
576+ chainId : chain . id ,
577+ escrowContract,
578+ channelId : channelIdA ,
579+ } ,
580+ } )
581+ const challengeB = makeLiveChallenge ( {
582+ methodDetails : {
583+ chainId : chain . id ,
584+ escrowContract,
585+ channelId : channelIdB ,
586+ } ,
587+ } )
588+
589+ await method . createCredential ( { challenge : challengeA , context : { } } )
590+ await method . createCredential ( { challenge : challengeB , context : { } } )
591+
592+ method . onResponse (
593+ new Response ( null , {
594+ headers : {
595+ 'Payment-Receipt' : serializeSessionReceipt (
596+ createSessionReceipt ( {
597+ challengeId : challengeA . id ,
598+ channelId : channelIdA ,
599+ acceptedCumulative : 9_000_000n ,
600+ spent : 9_000_000n ,
601+ } ) ,
602+ ) ,
603+ } ,
604+ } ) ,
605+ )
606+
607+ const result = deserializePayload (
608+ await method . createCredential ( { challenge : challengeB , context : { } } ) ,
609+ )
610+
611+ expect ( result . payload . action ) . toBe ( 'voucher' )
612+ if ( result . payload . action === 'voucher' ) {
613+ expect ( result . payload . channelId ) . toBe ( channelIdB )
614+ expect ( result . payload . cumulativeAmount ) . toBe ( '2000000' )
511615 }
512616 } )
513617 } )
@@ -547,7 +651,7 @@ describe.runIf(isLocalnet)('session (on-chain)', () => {
547651 expect ( updates [ 1 ] ! . cumulativeAmount ) . toBe ( 2_000_000n )
548652 } )
549653
550- test ( 'reconciles local cumulative from Payment-Receipt before the next voucher' , async ( ) => {
654+ test ( 'does not let Payment-Receipt inflate the next voucher amount ' , async ( ) => {
551655 const method = session ( {
552656 getClient : ( ) => client ,
553657 account : payer ,
@@ -579,7 +683,7 @@ describe.runIf(isLocalnet)('session (on-chain)', () => {
579683 const secondCred = deserializePayload ( second )
580684 expect ( secondCred . payload . action ) . toBe ( 'voucher' )
581685 if ( secondCred . payload . action === 'voucher' ) {
582- expect ( secondCred . payload . cumulativeAmount ) . toBe ( '6000000 ' )
686+ expect ( secondCred . payload . cumulativeAmount ) . toBe ( '2000000 ' )
583687 }
584688 } )
585689 } )
0 commit comments