github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/computation/computer/transaction_coordinator_test.go (about) 1 package computer 2 3 import ( 4 "fmt" 5 "testing" 6 "time" 7 8 "github.com/rs/zerolog" 9 "github.com/stretchr/testify/require" 10 11 "github.com/onflow/flow-go/fvm" 12 "github.com/onflow/flow-go/fvm/storage" 13 "github.com/onflow/flow-go/fvm/storage/logical" 14 "github.com/onflow/flow-go/fvm/storage/snapshot" 15 "github.com/onflow/flow-go/model/flow" 16 ) 17 18 type testCoordinatorVM struct{} 19 20 func (testCoordinatorVM) NewExecutor( 21 ctx fvm.Context, 22 proc fvm.Procedure, 23 txnState storage.TransactionPreparer, 24 ) fvm.ProcedureExecutor { 25 return testCoordinatorExecutor{ 26 executionTime: proc.ExecutionTime(), 27 } 28 } 29 30 func (testCoordinatorVM) Run( 31 ctx fvm.Context, 32 proc fvm.Procedure, 33 storageSnapshot snapshot.StorageSnapshot, 34 ) ( 35 *snapshot.ExecutionSnapshot, 36 fvm.ProcedureOutput, 37 error, 38 ) { 39 panic("not implemented") 40 } 41 42 func (testCoordinatorVM) GetAccount( 43 _ fvm.Context, 44 _ flow.Address, 45 _ snapshot.StorageSnapshot, 46 ) ( 47 *flow.Account, 48 error, 49 ) { 50 panic("not implemented") 51 } 52 53 type testCoordinatorExecutor struct { 54 executionTime logical.Time 55 } 56 57 func (testCoordinatorExecutor) Cleanup() {} 58 59 func (testCoordinatorExecutor) Preprocess() error { 60 return nil 61 } 62 63 func (testCoordinatorExecutor) Execute() error { 64 return nil 65 } 66 67 func (executor testCoordinatorExecutor) Output() fvm.ProcedureOutput { 68 return fvm.ProcedureOutput{ 69 ComputationUsed: uint64(executor.executionTime), 70 } 71 } 72 73 type testCoordinator struct { 74 *transactionCoordinator 75 committed []uint64 76 } 77 78 func newTestCoordinator(t *testing.T) *testCoordinator { 79 db := &testCoordinator{} 80 db.transactionCoordinator = newTransactionCoordinator( 81 testCoordinatorVM{}, 82 nil, 83 nil, 84 db) 85 86 require.Equal(t, db.SnapshotTime(), logical.Time(0)) 87 88 // commit a transaction to increment the snapshot time 89 setupTxn, err := db.newTransaction(0) 90 require.NoError(t, err) 91 92 err = setupTxn.Finalize() 93 require.NoError(t, err) 94 95 err = setupTxn.Commit() 96 require.NoError(t, err) 97 98 require.Equal(t, db.SnapshotTime(), logical.Time(1)) 99 100 return db 101 102 } 103 104 func (db *testCoordinator) AddTransactionResult( 105 txn TransactionRequest, 106 snapshot *snapshot.ExecutionSnapshot, 107 output fvm.ProcedureOutput, 108 timeSpent time.Duration, 109 numConflictRetries int, 110 ) { 111 db.committed = append(db.committed, output.ComputationUsed) 112 } 113 114 func (db *testCoordinator) newTransaction(txnIndex uint32) ( 115 *transaction, 116 error, 117 ) { 118 return db.NewTransaction( 119 newTransactionRequest( 120 collectionInfo{}, 121 fvm.NewContext(), 122 zerolog.Nop(), 123 txnIndex, 124 &flow.TransactionBody{}, 125 false), 126 0) 127 } 128 129 type testWaitValues struct { 130 startTime logical.Time 131 startErr error 132 snapshotTime logical.Time 133 abortErr error 134 } 135 136 func (db *testCoordinator) setupWait(txn *transaction) chan testWaitValues { 137 ret := make(chan testWaitValues, 1) 138 go func() { 139 startTime, startErr, snapshotTime, abortErr := db.waitForUpdatesNewerThan( 140 txn.SnapshotTime()) 141 ret <- testWaitValues{ 142 startTime: startTime, 143 startErr: startErr, 144 snapshotTime: snapshotTime, 145 abortErr: abortErr, 146 } 147 }() 148 149 // Sleep a bit to ensure goroutine is running before returning the channel. 150 time.Sleep(10 * time.Millisecond) 151 return ret 152 } 153 154 func TestTransactionCoordinatorBasicCommit(t *testing.T) { 155 db := newTestCoordinator(t) 156 157 txns := []*transaction{} 158 for i := uint32(1); i < 6; i++ { 159 txn, err := db.newTransaction(i) 160 require.NoError(t, err) 161 162 txns = append(txns, txn) 163 } 164 165 for i, txn := range txns { 166 executionTime := logical.Time(1 + i) 167 168 require.Equal(t, txn.SnapshotTime(), logical.Time(1)) 169 170 err := txn.Finalize() 171 require.NoError(t, err) 172 173 err = txn.Validate() 174 require.NoError(t, err) 175 176 require.Equal(t, txn.SnapshotTime(), executionTime) 177 178 err = txn.Commit() 179 require.NoError(t, err) 180 181 require.Equal(t, db.SnapshotTime(), executionTime+1) 182 } 183 184 require.Equal(t, db.committed, []uint64{0, 1, 2, 3, 4, 5}) 185 } 186 187 func TestTransactionCoordinatorBlockingWaitForCommit(t *testing.T) { 188 db := newTestCoordinator(t) 189 190 testTxn, err := db.newTransaction(6) 191 require.NoError(t, err) 192 193 require.Equal(t, db.SnapshotTime(), logical.Time(1)) 194 require.Equal(t, testTxn.SnapshotTime(), logical.Time(1)) 195 196 ret := db.setupWait(testTxn) 197 198 setupTxn, err := db.newTransaction(1) 199 require.NoError(t, err) 200 201 err = setupTxn.Finalize() 202 require.NoError(t, err) 203 204 err = setupTxn.Commit() 205 require.NoError(t, err) 206 207 require.Equal(t, db.SnapshotTime(), logical.Time(2)) 208 209 select { 210 case val := <-ret: 211 require.Equal( 212 t, 213 val, 214 testWaitValues{ 215 startTime: 1, 216 startErr: nil, 217 snapshotTime: 2, 218 abortErr: nil, 219 }) 220 case <-time.After(time.Second): 221 require.Fail(t, "Failed to return result") 222 } 223 224 require.Equal(t, testTxn.SnapshotTime(), logical.Time(1)) 225 226 err = testTxn.Validate() 227 require.NoError(t, err) 228 229 require.Equal(t, testTxn.SnapshotTime(), logical.Time(2)) 230 231 } 232 233 func TestTransactionCoordinatorNonblockingWaitForCommit(t *testing.T) { 234 db := newTestCoordinator(t) 235 236 testTxn, err := db.newTransaction(6) 237 require.NoError(t, err) 238 239 setupTxn, err := db.newTransaction(1) 240 require.NoError(t, err) 241 242 err = setupTxn.Finalize() 243 require.NoError(t, err) 244 245 err = setupTxn.Commit() 246 require.NoError(t, err) 247 248 require.Equal(t, db.SnapshotTime(), logical.Time(2)) 249 require.Equal(t, testTxn.SnapshotTime(), logical.Time(1)) 250 251 ret := db.setupWait(testTxn) 252 253 select { 254 case val := <-ret: 255 require.Equal( 256 t, 257 val, 258 testWaitValues{ 259 startTime: 2, 260 startErr: nil, 261 snapshotTime: 2, 262 abortErr: nil, 263 }) 264 case <-time.After(time.Second): 265 require.Fail(t, "Failed to return result") 266 } 267 } 268 269 func TestTransactionCoordinatorBasicAbort(t *testing.T) { 270 db := newTestCoordinator(t) 271 272 txn, err := db.newTransaction(1) 273 require.NoError(t, err) 274 275 abortErr := fmt.Errorf("abort") 276 db.AbortAllOutstandingTransactions(abortErr) 277 278 err = txn.Finalize() 279 require.NoError(t, err) 280 281 err = txn.Commit() 282 require.Equal(t, err, abortErr) 283 284 txn, err = db.newTransaction(2) 285 require.Equal(t, err, abortErr) 286 require.Nil(t, txn) 287 } 288 289 func TestTransactionCoordinatorBlockingWaitForAbort(t *testing.T) { 290 db := newTestCoordinator(t) 291 292 testTxn, err := db.newTransaction(6) 293 require.NoError(t, err) 294 295 // start waiting before aborting. 296 require.Equal(t, testTxn.SnapshotTime(), logical.Time(1)) 297 ret := db.setupWait(testTxn) 298 299 abortErr := fmt.Errorf("abort") 300 db.AbortAllOutstandingTransactions(abortErr) 301 302 select { 303 case val := <-ret: 304 require.Equal( 305 t, 306 val, 307 testWaitValues{ 308 startTime: 1, 309 startErr: nil, 310 snapshotTime: 1, 311 abortErr: abortErr, 312 }) 313 case <-time.After(time.Second): 314 require.Fail(t, "Failed to return result") 315 } 316 317 err = testTxn.Finalize() 318 require.NoError(t, err) 319 320 err = testTxn.Commit() 321 require.Equal(t, err, abortErr) 322 } 323 324 func TestTransactionCoordinatorNonblockingWaitForAbort(t *testing.T) { 325 db := newTestCoordinator(t) 326 327 testTxn, err := db.newTransaction(6) 328 require.NoError(t, err) 329 330 // start aborting before waiting. 331 abortErr := fmt.Errorf("abort") 332 db.AbortAllOutstandingTransactions(abortErr) 333 334 require.Equal(t, testTxn.SnapshotTime(), logical.Time(1)) 335 ret := db.setupWait(testTxn) 336 337 select { 338 case val := <-ret: 339 require.Equal( 340 t, 341 val, 342 testWaitValues{ 343 startTime: 1, 344 startErr: abortErr, 345 snapshotTime: 1, 346 abortErr: abortErr, 347 }) 348 case <-time.After(time.Second): 349 require.Fail(t, "Failed to return result") 350 } 351 352 err = testTxn.Finalize() 353 require.NoError(t, err) 354 355 err = testTxn.Commit() 356 require.Equal(t, err, abortErr) 357 }