@@ -65,9 +65,9 @@ test('pending offense transitions and matching delivery fanout are atomic and de
6565 certainty : 'pending' ,
6666 } ) ;
6767 assert . deepEqual ( events . map ( ( event ) => event . title ) , [
68- 'Offense detected' ,
69- 'Offense changed' ,
70- 'Offense returned' ,
68+ 'Inactivity offense detected' ,
69+ 'Inactivity offense changed' ,
70+ 'Inactivity offense returned' ,
7171 ] ) ;
7272 assert . equal ( repository . getDeliveryCounts ( ) . pending , 3 ) ;
7373 const deliveries = [ ] ;
@@ -85,6 +85,57 @@ test('pending offense transitions and matching delivery fanout are atomic and de
8585 }
8686} ) ;
8787
88+ test ( 'pending offenses connect their epoch or slot to offense and proposal rounds when L1 context is ready' , ( ) => {
89+ const repository = new OffenseRepository ( ':memory:' ) ;
90+ try {
91+ repository . recordSourceSuccess ( 'l1' , {
92+ epochDuration : '32' ,
93+ stacks : [ {
94+ role : 'active' ,
95+ parameters : {
96+ roundSize : '128' ,
97+ slashOffsetInRounds : '2' ,
98+ } ,
99+ } ] ,
100+ } , 10 ) ;
101+ const [ epochOffense ] = parseOffenseSnapshot ( [ OFFENSE_A ] ) ;
102+ const [ slotOffense ] = parseOffenseSnapshot ( [ OFFENSE_B ] ) ;
103+
104+ repository . recordSuccessfulPoll ( [ epochOffense , slotOffense ] , {
105+ observedAt : 20 ,
106+ network : 'mainnet' ,
107+ } ) ;
108+
109+ const events = repository . listEvents ( { network : 'mainnet' , limit : 10 } ) . data ;
110+ const epochEvent = events . find ( ( event ) => event . data . offenseId === epochOffense . id ) ;
111+ const slotEvent = events . find ( ( event ) => event . data . offenseId === slotOffense . id ) ;
112+ assert . deepEqual ( {
113+ epoch : epochEvent . data . epoch ,
114+ slot : epochEvent . data . slot ,
115+ offenseRound : epochEvent . data . offenseRound ,
116+ proposalRound : epochEvent . data . proposalRound ,
117+ } , {
118+ epoch : '42' ,
119+ slot : '1344' ,
120+ offenseRound : '10' ,
121+ proposalRound : '12' ,
122+ } ) ;
123+ assert . deepEqual ( {
124+ epoch : slotEvent . data . epoch ,
125+ slot : slotEvent . data . slot ,
126+ offenseRound : slotEvent . data . offenseRound ,
127+ proposalRound : slotEvent . data . proposalRound ,
128+ } , {
129+ epoch : '281' ,
130+ slot : '9001' ,
131+ offenseRound : '70' ,
132+ proposalRound : '72' ,
133+ } ) ;
134+ } finally {
135+ repository . close ( ) ;
136+ }
137+ } ) ;
138+
88139test ( 'expired outbox leases are recovered on a later claim without restarting the worker' , ( ) => {
89140 const repository = new OffenseRepository ( ':memory:' ) ;
90141 try {
@@ -1165,6 +1216,48 @@ test('L1 snapshots alert on the first address-level vote, quorum, execution wind
11651216 }
11661217} ) ;
11671218
1219+ test ( 'L1 payload events count only tally actions and expose epoch, slot, and UTC execution timing' , ( ) => {
1220+ const repository = new OffenseRepository ( ':memory:' ) ;
1221+ try {
1222+ repository . recordSuccessfulL1Snapshot ( 'mainnet' , snapshot ( {
1223+ block : 100 ,
1224+ earlyTargets : [ earlyTarget ( SEQUENCER_A , 100 ) , earlyTarget ( SEQUENCER_B , 1 ) ] ,
1225+ actions : [ { sequencer : SEQUENCER_A , amount : '1000' } ] ,
1226+ payloadAddress : '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ,
1227+ status : 'quorum-reached' ,
1228+ targetEpochs : [ '38' , '39' ] ,
1229+ executableSlot : '1100' ,
1230+ expirySlot : '1200' ,
1231+ } ) , { observedAt : 100 } ) ;
1232+
1233+ const event = repository . listEvents ( { network : 'mainnet' } ) . data
1234+ . find ( ( candidate ) => candidate . type === 'onchain_targeted' ) ;
1235+ assert . ok ( event ) ;
1236+ assert . deepEqual ( event . targets , [ SEQUENCER_A ] ) ;
1237+ assert . match ( event . body , / 1 s e q u e n c e r / i) ;
1238+ assert . doesNotMatch ( event . body , / 2 s e q u e n c e r s / i) ;
1239+ assert . deepEqual ( {
1240+ targetEpochs : event . data . targetEpochs ,
1241+ currentEpoch : event . data . currentEpoch ,
1242+ currentSlot : event . data . currentSlot ,
1243+ executableSlot : event . data . executableSlot ,
1244+ executableAt : event . data . executableAt ,
1245+ expirySlot : event . data . expirySlot ,
1246+ expiryAt : event . data . expiryAt ,
1247+ } , {
1248+ targetEpochs : [ '38' , '39' ] ,
1249+ currentEpoch : '10' ,
1250+ currentSlot : '1000' ,
1251+ executableSlot : '1100' ,
1252+ executableAt : '1970-01-01T03:41:40.000Z' ,
1253+ expirySlot : '1200' ,
1254+ expiryAt : '1970-01-01T04:01:40.000Z' ,
1255+ } ) ;
1256+ } finally {
1257+ repository . close ( ) ;
1258+ }
1259+ } ) ;
1260+
11681261test ( 'L1 payload changes alert only sequencers whose own slash amount changed' , ( ) => {
11691262 const repository = new OffenseRepository ( ':memory:' ) ;
11701263 try {
@@ -1843,6 +1936,9 @@ function snapshot({
18431936 isProtected = false ,
18441937 pauseStartedAtSlot = null ,
18451938 pauseEndsAtSlot = null ,
1939+ targetEpochs = [ ] ,
1940+ executableSlot = '900' ,
1941+ expirySlot = '1200' ,
18461942} ) {
18471943 return {
18481944 chainId : 1 ,
@@ -1852,6 +1948,9 @@ function snapshot({
18521948 registryAddress : '0x0000000000000000000000000000000000000001' ,
18531949 rollupAddress : '0x0000000000000000000000000000000000000002' ,
18541950 rollupVersion : '1' ,
1951+ l1GenesisTime : '100' ,
1952+ slotDuration : '12' ,
1953+ epochDuration : '32' ,
18551954 currentSlot : '1000' ,
18561955 currentEpoch : '10' ,
18571956 stackErrors : [ ] ,
@@ -1881,9 +1980,9 @@ function snapshot({
18811980 earlyTargets,
18821981 actions,
18831982 committees : [ ] ,
1884- targetEpochs : [ ] ,
1885- executableSlot : '900' ,
1886- expirySlot : '1200' ,
1983+ targetEpochs,
1984+ executableSlot,
1985+ expirySlot,
18871986 } ] ,
18881987 } ] ,
18891988 } ;
0 commit comments