@@ -71,3 +71,35 @@ func TestApplyReturnsErrorOnConflict(t *testing.T) {
7171 require .NoError (t , err )
7272 require .Equal (t , []byte ("v1" ), v )
7373}
74+
75+ func TestOnePhaseTxnDetectsWriteConflict (t * testing.T ) {
76+ ctx := context .Background ()
77+ st := store .NewMVCCStore ()
78+ require .NoError (t , st .PutAt (ctx , []byte ("k" ), []byte ("v1" ), 100 , 0 ))
79+
80+ fsm , ok := NewKvFSM (st ).(* kvFSM )
81+ require .True (t , ok )
82+
83+ // One-phase txn with startTS < latest commit (100) should be rejected.
84+ req := & pb.Request {
85+ IsTxn : true ,
86+ Phase : pb .Phase_NONE ,
87+ Ts : 90 ,
88+ Mutations : []* pb.Mutation {
89+ {Op : pb .Op_PUT , Key : []byte (txnMetaPrefix ), Value : EncodeTxnMeta (TxnMeta {PrimaryKey : []byte ("k" ), CommitTS : 110 })},
90+ {Op : pb .Op_PUT , Key : []byte ("k" ), Value : []byte ("v2" )},
91+ },
92+ }
93+ data , err := proto .Marshal (req )
94+ require .NoError (t , err )
95+
96+ resp := fsm .Apply (& raft.Log {Type : raft .LogCommand , Data : data })
97+ err , ok = resp .(error )
98+ require .True (t , ok )
99+ require .ErrorIs (t , err , store .ErrWriteConflict )
100+
101+ // Ensure the original value is unchanged.
102+ v , err := st .GetAt (ctx , []byte ("k" ), ^ uint64 (0 ))
103+ require .NoError (t , err )
104+ require .Equal (t , []byte ("v1" ), v )
105+ }
0 commit comments