Skip to content
51 changes: 51 additions & 0 deletions gherkin/scp/publisher/start_instance.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
Feature: Publisher Start Instance
The shared publisher is responsible for launching every SCP instance.
When an instance starts, it broadcasts the StartInstance message to each rollup that
appears in the XTRequest and starts a local timeout for the decision phase.

Background:
Given there is a shared publisher "SP"
And there is a chain "1" with sequencer "A"
And there is a chain "2" with sequencer "B"
And there is a chain "3" with sequencer "C"
And there is a chain "4" with sequencer "D"

@publisher @scp @start-instance
Scenario: Broadcasts StartInstance to all participating sequencers
When the shared publisher "SP" starts an instance:
"""
instance_id: 0x1
period_id: 7
sequence_number: 3
xtrequest:
1: [tx1_1, tx1_2]
2: [tx2]
3: [tx3]
"""
Then sequencers "A,B,C" should receive StartInstance:
"""
instance_id: 0x1
period_id: 7
sequence_number: 3
xtrequest:
1: [tx1_1, tx1_2]
2: [tx2]
3: [tx3]
"""
And a timer for instance "0x1" should start at the shared publisher

@publisher @scp @start-instance
Scenario: Notifies only chains referenced in the XTRequest
When the shared publisher "SP" starts an instance:
"""
instance_id: 0x2
period_id: 8
sequence_number: 4
xtrequest:
1: [tx1]
2: [tx2]
"""
Then sequencer "A" should receive StartInstance with instance ID "0x2"
And sequencer "B" should receive StartInstance with instance ID "0x2"
And sequencer "C" should not receive StartInstance for instance "0x2"
And sequencer "D" should not receive StartInstance for instance "0x2"
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature file is missing a closing newline at the end. Gherkin feature files should end with a newline character for better compatibility with version control systems and text editors.

Copilot uses AI. Check for mistakes.
52 changes: 52 additions & 0 deletions gherkin/scp/publisher/timeout.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
Feature: Publisher Timeout
The shared publisher starts a local timer when it launches an SCP instance.
If the timer expires before a decision is sent, it must reject the instance and notify
every participant. Once a decision is broadcast, any subsequent timeout should be ignored.

Background:
Given there is a shared publisher "SP"
And there is a chain "1" with sequencer "A"
And there is a chain "2" with sequencer "B"

@publisher @scp @timeout
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it redundant to have @timeout if you can just run a single feature file?
Maybe it isn't if you will have timeout tests outside the file

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can argue that it adds clutter but adds convenience...
We can ask @ayaz-ssvlabs if he likes the @timeout for cli commands

Scenario: Rejects instance when the timer expires before all votes are received
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe also add "before any votes" scenario

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

Given the shared publisher "SP" started an instance:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be same as above, unless you want this to be glued to different code?

Suggested change
Given the shared publisher "SP" started an instance:
Given the shared publisher "SP" starts an instance:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree

"""
instance_id: 0x8
period_id: 14
sequence_number: 10
xtrequest:
1: [tx1]
2: [tx2]
"""
And sequencer "A" publishes Vote with:
| field | value |
| instance_id | 0x8 |
| chain_id | 1 |
| vote | true |
When the timer for the shared publisher "SP" expires for instance "0x8"
Then the shared publisher "SP" should publish Decided with:
| field | value |
| instance_id | 0x8 |
| decision | false |
And the shared publisher "SP" should mark the instance "0x8" as rejected

@publisher @scp @timeout
Scenario Outline: Ignores timer expiry after a decision has been published
Given the shared publisher "SP" started an instance:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Given the shared publisher "SP" started an instance:
Given the shared publisher "SP" starts an instance:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree

"""
instance_id: 0x9
period_id: 15
sequence_number: 11
xtrequest:
1: [tx1]
2: [tx2]
"""
And the shared publisher "SP" has already published Decided for instance "0x9" with decision "<decision>"
When the timer for the shared publisher "SP" expires for instance "0x9"
Then no additional Decided message should be sent for instance "0x9"

Examples:
| decision |
| true |
| false |
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature file is missing a closing newline at the end. Gherkin feature files should end with a newline character for better compatibility with version control systems and text editors.

Copilot uses AI. Check for mistakes.
128 changes: 128 additions & 0 deletions gherkin/scp/publisher/votes.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
Feature: Publisher Vote Processing
The shared publisher collects votes from the sequencers that were included in the XTRequest.
It keeps one vote per chain, rejects invalid senders, and finalizes the instance once the
voting rules are satisfied.

Background:
Given there is a shared publisher "SP"
And there is a chain "1" with sequencer "A"
And there is a chain "2" with sequencer "B"
And there is a chain "3" with sequencer "C"

@publisher @scp @votes
Scenario: Accepts instance after collecting all positive votes
Given the shared publisher "SP" started an instance:
"""
instance_id: 0x3
period_id: 9
sequence_number: 5
xtrequest:
1: [tx1]
2: [tx2]
"""
When sequencer "A" publishes Vote with:
| field | value |
| instance_id | 0x3 |
| chain_id | 1 |
| vote | true |
And sequencer "B" publishes Vote with:
| field | value |
| instance_id | 0x3 |
| chain_id | 2 |
| vote | true |
Then the shared publisher "SP" should publish Decided with:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Then the shared publisher "SP" should publish Decided with:
Then the shared publisher "SP" publishes Decided with:

You don't have to take this.
I just think it reads better in case the step will be also used as an AND step.
But only some humans might care

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but isn't "should" the standard for "Then" clauses? For me both ways are fine, just wanted to confirm this w you first

| field | value |
| instance_id | 0x3 |
| decision | true |
And the shared publisher "SP" should mark the instance "0x3" as accepted

@publisher @scp @votes
Scenario: Rejects instance immediately when a vote is false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc wise it is clear from this what will happen in other scenarios
But since we are trying to uncover bugs it is good to add more cases like a true voter that came before or after.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, adding it

Given the shared publisher "SP" started an instance:
"""
instance_id: 0x4
period_id: 10
sequence_number: 6
xtrequest:
1: [tx1]
2: [tx2]
3: [tx3]
"""
When sequencer "C" publishes Vote with:
| field | value |
| instance_id | 0x4 |
| chain_id | 3 |
| vote | false |
Then the shared publisher "SP" should publish Decided with:
| field | value |
| instance_id | 0x4 |
| decision | false |
And the shared publisher "SP" should mark the instance "0x4" as rejected
And subsequent votes for instance "0x4" should be ignored
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just note that since in this scenario you are not sending votes after, this step won't do much.
You can still leave it here though, no need to remove unless you want to

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree


@publisher @scp @votes
Scenario: Errors when receiving a duplicated vote from the same chain
Given the shared publisher "SP" started an instance:
"""
instance_id: 0x5
period_id: 11
sequence_number: 7
xtrequest:
1: [tx1]
2: [tx2]
"""
And sequencer "A" has already published Vote with:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the logical difference between a simple: "sequencer A publishes Vote with"?

A goal that we have is that eventually we will have tests that can be ran with no changes in the golang side

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree 100%

| field | value |
| instance_id | 0x5 |
| chain_id | 1 |
| vote | true |
When sequencer "A" publishes another Vote for instance "0x5"
Then an error occurs:
"""
duplicated vote for chain 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what did we decide with errors?
Do we want error codes (less readable) or creating error messages from text (text can also be a code)

@ayaz-ssvlabs

"""

@publisher @scp @votes
Scenario: Rejects votes from chains that are not part of the instance
Given the shared publisher "SP" started an instance:
"""
instance_id: 0x6
period_id: 12
sequence_number: 8
xtrequest:
1: [tx1]
2: [tx2]
"""
When sequencer "C" publishes Vote with:
| field | value |
| instance_id | 0x6 |
| chain_id | 3 |
| vote | true |
Then an error occurs:
"""
vote from non-participant chain
"""

@publisher @scp @votes
Scenario: Ignores votes that arrive after the instance has been decided
Given the shared publisher "SP" started an instance:
"""
instance_id: 0x7
period_id: 13
sequence_number: 9
xtrequest:
1: [tx1]
2: [tx2]
"""
And sequencer "A" has already published Vote with:
| field | value |
| instance_id | 0x7 |
| chain_id | 1 |
| vote | false |
And the shared publisher "SP" has already published Decided for instance "0x7" with decision "false"
When sequencer "B" publishes Vote with:
| field | value |
| instance_id | 0x7 |
| chain_id | 2 |
| vote | true |
Then no additional Decided message should be sent for instance "0x7"
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature file is missing a closing newline at the end. Gherkin feature files should end with a newline character for better compatibility with version control systems and text editors.

Copilot uses AI. Check for mistakes.
127 changes: 127 additions & 0 deletions gherkin/scp/sequencer/decision.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
Feature: Sequencer Decision
On the sequencer perspective, an instance terminates in two cases:
- when it sends a rejection vote (either due simulation failure or timeout)
- when it receives a decision from the SP
If the instance is decided as rejected, the sequencer should not include its transactions in a block.
Else, it should include the transactions along with the created mailbox.putInbox ones.
Note that the shared publisher can only send a decision true if all sequencers have voted true.
Thus, receiving a decision true without having voted true is an invalid protocol state.

Background:
Given there is a chain "1" with sequencer "A"
And there is a chain "2" with sequencer "B"

@sequencer @scp @decision
Scenario: Rejects instance upon simulation failure
Given sequencer "A" receives StartInstance:
"""
instance_id: 0x1
period_id: 2
sequence_number: 2
xtrequest:
1: [tx1]
2: [tx2]
"""
And the execution engine simulates "tx1" and returns an error other than "Read miss"
When sequencer "A" publishes Vote with:
| field | value |
| instance_id | 0x1 |
| chain_id | 1 |
| vote | false |
Then sequencer "A" should mark the instance "0x1" as rejected

@sequencer @scp @decision
Scenario: Rejects instance when decision is false
Given sequencer "A" receives StartInstance:
"""
instance_id: 0x1
period_id: 2
sequence_number: 2
xtrequest:
1: [tx1]
2: [tx2]
"""
When sequencer "A" receives Decided for instance "0x1" with decision "false"
Then sequencer "A" should mark the instance "0x1" as rejected

@sequencer @scp @decision
Scenario: Errors when decision true arrives without a prior vote
Given sequencer "A" receives StartInstance:
"""
instance_id: 0x1
period_id: 2
sequence_number: 2
xtrequest:
1: [tx1]
2: [tx2]
"""
And sequencer "A" has not published a Vote for instance "0x1"
When sequencer "A" receives Decided for instance "0x1" with decision "true"
Then an error occurs:
"""
decision true but no vote sent is an impossible state
"""

@sequencer @scp @decision
Scenario: Errors when decision true contradicts a prior false vote
Given sequencer "A" receives StartInstance:
"""
instance_id: 0x1
period_id: 2
sequence_number: 2
xtrequest:
1: [tx1]
2: [tx2]
"""
And sequencer "A" previously published Vote with:
| field | value |
| instance_id | 0x1 |
| chain_id | 1 |
| vote | false |
When sequencer "A" receives Decided for instance "0x1" with decision "true"
Then an error occurs:
"""
decision true but previous vote was false is an impossible state
"""

@sequencer @scp @decision
Scenario: Finalizes instance when decision is received from SP
Given sequencer "A" receives StartInstance:
"""
instance_id: 0x1
period_id: 2
sequence_number: 2
xtrequest:
1: [tx1]
2: [tx2]
"""
And the execution engine simulates "tx1" and returns success
And sequencer "A" previously published Vote with:
| field | value |
| instance_id | 0x1 |
| chain_id | 1 |
| vote | true |
When sequencer "A" receives Decided for instance "0x1" with decision <decision>
Then sequencer "A" should mark the instance "0x1" as <outcome>
Examples:
| decision | outcome |
| true | accepted |
| false | rejected |

@sequencer @scp @decision
Scenario: Raises error when a decided instance receives a second decision
Given sequencer "A" receives StartInstance:
"""
instance_id: 0x1
period_id: 2
sequence_number: 2
xtrequest:
1: [tx1]
2: [tx2]
"""
And sequencer "A" receives Decided for instance "0x1" with decision "false"
When sequencer "A" later receives Decided for instance "0x1" with decision "true"
Then an error occurs:
"""
instance already decided
"""
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature file is missing a closing newline at the end. Gherkin feature files should end with a newline character for better compatibility with version control systems and text editors.

Copilot uses AI. Check for mistakes.
Loading
Loading