github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/core/blockstm/executor.go (about) 1 package blockstm 2 3 import ( 4 "container/heap" 5 "context" 6 "fmt" 7 "sync" 8 "time" 9 10 "github.com/ethereum/go-ethereum/common" 11 "github.com/ethereum/go-ethereum/log" 12 ) 13 14 type ExecResult struct { 15 err error 16 ver Version 17 txIn TxnInput 18 txOut TxnOutput 19 txAllOut TxnOutput 20 } 21 22 type ExecTask interface { 23 Execute(mvh *MVHashMap, incarnation int) error 24 MVReadList() []ReadDescriptor 25 MVWriteList() []WriteDescriptor 26 MVFullWriteList() []WriteDescriptor 27 Hash() common.Hash 28 Sender() common.Address 29 Settle() 30 Dependencies() []int 31 } 32 33 type ExecVersionView struct { 34 ver Version 35 et ExecTask 36 mvh *MVHashMap 37 sender common.Address 38 } 39 40 var NumSpeculativeProcs int = 8 41 42 func SetProcs(specProcs int) { 43 NumSpeculativeProcs = specProcs 44 } 45 46 func (ev *ExecVersionView) Execute() (er ExecResult) { 47 er.ver = ev.ver 48 if er.err = ev.et.Execute(ev.mvh, ev.ver.Incarnation); er.err != nil { 49 return 50 } 51 52 er.txIn = ev.et.MVReadList() 53 er.txOut = ev.et.MVWriteList() 54 er.txAllOut = ev.et.MVFullWriteList() 55 56 return 57 } 58 59 type ErrExecAbortError struct { 60 Dependency int 61 OriginError error 62 } 63 64 func (e ErrExecAbortError) Error() string { 65 if e.Dependency >= 0 { 66 return fmt.Sprintf("Execution aborted due to dependency %d", e.Dependency) 67 } else { 68 return "Execution aborted" 69 } 70 } 71 72 type ParallelExecFailedError struct { 73 Msg string 74 } 75 76 func (e ParallelExecFailedError) Error() string { 77 return e.Msg 78 } 79 80 type IntHeap []int 81 82 func (h IntHeap) Len() int { return len(h) } 83 func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } 84 func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 85 86 func (h *IntHeap) Push(x any) { 87 // Push and Pop use pointer receivers because they modify the slice's length, 88 // not just its contents. 89 *h = append(*h, x.(int)) 90 } 91 92 func (h *IntHeap) Pop() any { 93 old := *h 94 n := len(old) 95 x := old[n-1] 96 *h = old[0 : n-1] 97 98 return x 99 } 100 101 type SafeQueue interface { 102 Push(v int, d interface{}) 103 Pop() interface{} 104 Len() int 105 } 106 107 type SafeFIFOQueue struct { 108 c chan interface{} 109 } 110 111 func NewSafeFIFOQueue(capacity int) *SafeFIFOQueue { 112 return &SafeFIFOQueue{ 113 c: make(chan interface{}, capacity), 114 } 115 } 116 117 func (q *SafeFIFOQueue) Push(v int, d interface{}) { 118 q.c <- d 119 } 120 121 func (q *SafeFIFOQueue) Pop() interface{} { 122 return <-q.c 123 } 124 125 func (q *SafeFIFOQueue) Len() int { 126 return len(q.c) 127 } 128 129 // A thread safe priority queue 130 type SafePriorityQueue struct { 131 m sync.Mutex 132 queue *IntHeap 133 data map[int]interface{} 134 } 135 136 func NewSafePriorityQueue(capacity int) *SafePriorityQueue { 137 q := make(IntHeap, 0, capacity) 138 139 return &SafePriorityQueue{ 140 m: sync.Mutex{}, 141 queue: &q, 142 data: make(map[int]interface{}, capacity), 143 } 144 } 145 146 func (pq *SafePriorityQueue) Push(v int, d interface{}) { 147 pq.m.Lock() 148 149 heap.Push(pq.queue, v) 150 pq.data[v] = d 151 152 pq.m.Unlock() 153 } 154 155 func (pq *SafePriorityQueue) Pop() interface{} { 156 pq.m.Lock() 157 defer pq.m.Unlock() 158 159 v := heap.Pop(pq.queue).(int) 160 161 return pq.data[v] 162 } 163 164 func (pq *SafePriorityQueue) Len() int { 165 return pq.queue.Len() 166 } 167 168 type ParallelExecutionResult struct { 169 TxIO *TxnInputOutput 170 Stats *map[int]ExecutionStat 171 Deps *DAG 172 AllDeps map[int]map[int]bool 173 } 174 175 const numGoProcs = 1 176 177 type ParallelExecutor struct { 178 tasks []ExecTask 179 180 // Stores the execution statistics for the last incarnation of each task 181 stats map[int]ExecutionStat 182 183 statsMutex sync.Mutex 184 185 // Channel for tasks that should be prioritized 186 chTasks chan ExecVersionView 187 188 // Channel for speculative tasks 189 chSpeculativeTasks chan struct{} 190 191 // Channel to signal that the result of a transaction could be written to storage 192 specTaskQueue SafeQueue 193 194 // A priority queue that stores speculative tasks 195 chSettle chan int 196 197 // Channel to signal that a transaction has finished executing 198 chResults chan struct{} 199 200 // A priority queue that stores the transaction index of results, so we can validate the results in order 201 resultQueue SafeQueue 202 203 // A wait group to wait for all settling tasks to finish 204 settleWg sync.WaitGroup 205 206 // An integer that tracks the index of last settled transaction 207 lastSettled int 208 209 // For a task that runs only after all of its preceding tasks have finished and passed validation, 210 // its result will be absolutely valid and therefore its validation could be skipped. 211 // This map stores the boolean value indicating whether a task satisfy this condition ( absolutely valid). 212 skipCheck map[int]bool 213 214 // Execution tasks stores the state of each execution task 215 execTasks taskStatusManager 216 217 // Validate tasks stores the state of each validation task 218 validateTasks taskStatusManager 219 220 // Stats for debugging purposes 221 cntExec, cntSuccess, cntAbort, cntTotalValidations, cntValidationFail int 222 223 diagExecSuccess, diagExecAbort []int 224 225 // Multi-version hash map 226 mvh *MVHashMap 227 228 // Stores the inputs and outputs of the last incardanotion of all transactions 229 lastTxIO *TxnInputOutput 230 231 // Tracks the incarnation number of each transaction 232 txIncarnations []int 233 234 // A map that stores the estimated dependency of a transaction if it is aborted without any known dependency 235 estimateDeps map[int][]int 236 237 // A map that records whether a transaction result has been speculatively validated 238 preValidated map[int]bool 239 240 // Time records when the parallel execution starts 241 begin time.Time 242 243 // Enable profiling 244 profile bool 245 246 // Worker wait group 247 workerWg sync.WaitGroup 248 } 249 250 type ExecutionStat struct { 251 TxIdx int 252 Incarnation int 253 Start uint64 254 End uint64 255 Worker int 256 } 257 258 func NewParallelExecutor(tasks []ExecTask, profile bool, metadata bool) *ParallelExecutor { 259 numTasks := len(tasks) 260 261 var resultQueue SafeQueue 262 263 var specTaskQueue SafeQueue 264 265 if metadata { 266 resultQueue = NewSafeFIFOQueue(numTasks) 267 specTaskQueue = NewSafeFIFOQueue(numTasks) 268 } else { 269 resultQueue = NewSafePriorityQueue(numTasks) 270 specTaskQueue = NewSafePriorityQueue(numTasks) 271 } 272 273 pe := &ParallelExecutor{ 274 tasks: tasks, 275 stats: make(map[int]ExecutionStat, numTasks), 276 chTasks: make(chan ExecVersionView, numTasks), 277 chSpeculativeTasks: make(chan struct{}, numTasks), 278 chSettle: make(chan int, numTasks), 279 chResults: make(chan struct{}, numTasks), 280 specTaskQueue: specTaskQueue, 281 resultQueue: resultQueue, 282 lastSettled: -1, 283 skipCheck: make(map[int]bool), 284 execTasks: makeStatusManager(numTasks), 285 validateTasks: makeStatusManager(0), 286 diagExecSuccess: make([]int, numTasks), 287 diagExecAbort: make([]int, numTasks), 288 mvh: MakeMVHashMap(), 289 lastTxIO: MakeTxnInputOutput(numTasks), 290 txIncarnations: make([]int, numTasks), 291 estimateDeps: make(map[int][]int), 292 preValidated: make(map[int]bool), 293 begin: time.Now(), 294 profile: profile, 295 } 296 297 return pe 298 } 299 300 // nolint: gocognit 301 func (pe *ParallelExecutor) Prepare() error { 302 prevSenderTx := make(map[common.Address]int) 303 304 for i, t := range pe.tasks { 305 clearPendingFlag := false 306 307 pe.skipCheck[i] = false 308 pe.estimateDeps[i] = make([]int, 0) 309 310 if len(t.Dependencies()) > 0 { 311 for _, val := range t.Dependencies() { 312 clearPendingFlag = true 313 314 pe.execTasks.addDependencies(val, i) 315 } 316 317 if clearPendingFlag { 318 pe.execTasks.clearPending(i) 319 320 clearPendingFlag = false 321 } 322 } else { 323 if tx, ok := prevSenderTx[t.Sender()]; ok { 324 pe.execTasks.addDependencies(tx, i) 325 pe.execTasks.clearPending(i) 326 } 327 328 prevSenderTx[t.Sender()] = i 329 } 330 } 331 332 pe.workerWg.Add(NumSpeculativeProcs + numGoProcs) 333 334 // Launch workers that execute transactions 335 for i := 0; i < NumSpeculativeProcs+numGoProcs; i++ { 336 go func(procNum int) { 337 defer pe.workerWg.Done() 338 339 doWork := func(task ExecVersionView) { 340 start := time.Duration(0) 341 if pe.profile { 342 start = time.Since(pe.begin) 343 } 344 345 res := task.Execute() 346 347 if res.err == nil { 348 pe.mvh.FlushMVWriteSet(res.txAllOut) 349 } 350 351 pe.resultQueue.Push(res.ver.TxnIndex, res) 352 pe.chResults <- struct{}{} 353 354 if pe.profile { 355 end := time.Since(pe.begin) 356 357 pe.statsMutex.Lock() 358 pe.stats[res.ver.TxnIndex] = ExecutionStat{ 359 TxIdx: res.ver.TxnIndex, 360 Incarnation: res.ver.Incarnation, 361 Start: uint64(start), 362 End: uint64(end), 363 Worker: procNum, 364 } 365 pe.statsMutex.Unlock() 366 } 367 } 368 369 if procNum < NumSpeculativeProcs { 370 for range pe.chSpeculativeTasks { 371 doWork(pe.specTaskQueue.Pop().(ExecVersionView)) 372 } 373 } else { 374 for task := range pe.chTasks { 375 doWork(task) 376 } 377 } 378 }(i) 379 } 380 381 pe.settleWg.Add(1) 382 383 go func() { 384 for t := range pe.chSettle { 385 pe.tasks[t].Settle() 386 } 387 388 pe.settleWg.Done() 389 }() 390 391 // bootstrap first execution 392 tx := pe.execTasks.takeNextPending() 393 394 if tx == -1 { 395 return ParallelExecFailedError{"no executable transactions due to bad dependency"} 396 } 397 398 pe.cntExec++ 399 400 pe.chTasks <- ExecVersionView{ver: Version{tx, 0}, et: pe.tasks[tx], mvh: pe.mvh, sender: pe.tasks[tx].Sender()} 401 402 return nil 403 } 404 405 func (pe *ParallelExecutor) Close(wait bool) { 406 close(pe.chTasks) 407 close(pe.chSpeculativeTasks) 408 close(pe.chSettle) 409 410 if wait { 411 pe.workerWg.Wait() 412 } 413 414 if wait { 415 pe.settleWg.Wait() 416 } 417 } 418 419 // nolint: gocognit 420 func (pe *ParallelExecutor) Step(res *ExecResult) (result ParallelExecutionResult, err error) { 421 tx := res.ver.TxnIndex 422 423 if abortErr, ok := res.err.(ErrExecAbortError); ok && abortErr.OriginError != nil && pe.skipCheck[tx] { 424 // If the transaction failed when we know it should not fail, this means the transaction itself is 425 // bad (e.g. wrong nonce), and we should exit the execution immediately 426 err = fmt.Errorf("could not apply tx %d [%v]: %w", tx, pe.tasks[tx].Hash(), abortErr.OriginError) 427 pe.Close(true) 428 429 return 430 } 431 432 // nolint: nestif 433 if execErr, ok := res.err.(ErrExecAbortError); ok { 434 addedDependencies := false 435 436 if execErr.Dependency >= 0 { 437 l := len(pe.estimateDeps[tx]) 438 for l > 0 && pe.estimateDeps[tx][l-1] > execErr.Dependency { 439 pe.execTasks.removeDependency(pe.estimateDeps[tx][l-1]) 440 pe.estimateDeps[tx] = pe.estimateDeps[tx][:l-1] 441 l-- 442 } 443 444 addedDependencies = pe.execTasks.addDependencies(execErr.Dependency, tx) 445 } else { 446 estimate := 0 447 448 if len(pe.estimateDeps[tx]) > 0 { 449 estimate = pe.estimateDeps[tx][len(pe.estimateDeps[tx])-1] 450 } 451 addedDependencies = pe.execTasks.addDependencies(estimate, tx) 452 newEstimate := estimate + (estimate+tx)/2 453 if newEstimate >= tx { 454 newEstimate = tx - 1 455 } 456 pe.estimateDeps[tx] = append(pe.estimateDeps[tx], newEstimate) 457 } 458 459 pe.execTasks.clearInProgress(tx) 460 461 if !addedDependencies { 462 pe.execTasks.pushPending(tx) 463 } 464 pe.txIncarnations[tx]++ 465 pe.diagExecAbort[tx]++ 466 pe.cntAbort++ 467 } else { 468 pe.lastTxIO.recordRead(tx, res.txIn) 469 470 if res.ver.Incarnation == 0 { 471 pe.lastTxIO.recordWrite(tx, res.txOut) 472 pe.lastTxIO.recordAllWrite(tx, res.txAllOut) 473 } else { 474 if res.txAllOut.hasNewWrite(pe.lastTxIO.AllWriteSet(tx)) { 475 pe.validateTasks.pushPendingSet(pe.execTasks.getRevalidationRange(tx + 1)) 476 } 477 478 prevWrite := pe.lastTxIO.AllWriteSet(tx) 479 480 // Remove entries that were previously written but are no longer written 481 482 cmpMap := make(map[Key]bool) 483 484 for _, w := range res.txAllOut { 485 cmpMap[w.Path] = true 486 } 487 488 for _, v := range prevWrite { 489 if _, ok := cmpMap[v.Path]; !ok { 490 pe.mvh.Delete(v.Path, tx) 491 } 492 } 493 494 pe.lastTxIO.recordWrite(tx, res.txOut) 495 pe.lastTxIO.recordAllWrite(tx, res.txAllOut) 496 } 497 498 pe.validateTasks.pushPending(tx) 499 pe.execTasks.markComplete(tx) 500 pe.diagExecSuccess[tx]++ 501 pe.cntSuccess++ 502 503 pe.execTasks.removeDependency(tx) 504 } 505 506 // do validations ... 507 maxComplete := pe.execTasks.maxAllComplete() 508 509 toValidate := make([]int, 0, 2) 510 511 for pe.validateTasks.minPending() <= maxComplete && pe.validateTasks.minPending() >= 0 { 512 toValidate = append(toValidate, pe.validateTasks.takeNextPending()) 513 } 514 515 for i := 0; i < len(toValidate); i++ { 516 pe.cntTotalValidations++ 517 518 tx := toValidate[i] 519 520 if pe.skipCheck[tx] || ValidateVersion(tx, pe.lastTxIO, pe.mvh) { 521 pe.validateTasks.markComplete(tx) 522 } else { 523 pe.cntValidationFail++ 524 pe.diagExecAbort[tx]++ 525 for _, v := range pe.lastTxIO.AllWriteSet(tx) { 526 pe.mvh.MarkEstimate(v.Path, tx) 527 } 528 // 'create validation tasks for all transactions > tx ...' 529 pe.validateTasks.pushPendingSet(pe.execTasks.getRevalidationRange(tx + 1)) 530 pe.validateTasks.clearInProgress(tx) // clear in progress - pending will be added again once new incarnation executes 531 532 pe.execTasks.clearComplete(tx) 533 pe.execTasks.pushPending(tx) 534 535 pe.preValidated[tx] = false 536 pe.txIncarnations[tx]++ 537 } 538 } 539 540 // Settle transactions that have been validated to be correct and that won't be re-executed again 541 maxValidated := pe.validateTasks.maxAllComplete() 542 543 for pe.lastSettled < maxValidated { 544 pe.lastSettled++ 545 if pe.execTasks.checkInProgress(pe.lastSettled) || pe.execTasks.checkPending(pe.lastSettled) || pe.execTasks.isBlocked(pe.lastSettled) { 546 pe.lastSettled-- 547 break 548 } 549 pe.chSettle <- pe.lastSettled 550 } 551 552 if pe.validateTasks.countComplete() == len(pe.tasks) && pe.execTasks.countComplete() == len(pe.tasks) { 553 log.Debug("blockstm exec summary", "execs", pe.cntExec, "success", pe.cntSuccess, "aborts", pe.cntAbort, "validations", pe.cntTotalValidations, "failures", pe.cntValidationFail, "#tasks/#execs", fmt.Sprintf("%.2f%%", float64(len(pe.tasks))/float64(pe.cntExec)*100)) 554 555 pe.Close(true) 556 557 var allDeps map[int]map[int]bool 558 559 var deps DAG 560 561 if pe.profile { 562 allDeps = GetDep(*pe.lastTxIO) 563 deps = BuildDAG(*pe.lastTxIO) 564 } 565 566 return ParallelExecutionResult{pe.lastTxIO, &pe.stats, &deps, allDeps}, err 567 } 568 569 // Send the next immediate pending transaction to be executed 570 if pe.execTasks.minPending() != -1 && pe.execTasks.minPending() == maxValidated+1 { 571 nextTx := pe.execTasks.takeNextPending() 572 if nextTx != -1 { 573 pe.cntExec++ 574 575 pe.skipCheck[nextTx] = true 576 577 pe.chTasks <- ExecVersionView{ver: Version{nextTx, pe.txIncarnations[nextTx]}, et: pe.tasks[nextTx], mvh: pe.mvh, sender: pe.tasks[nextTx].Sender()} 578 } 579 } 580 581 // Send speculative tasks 582 for pe.execTasks.minPending() != -1 { 583 nextTx := pe.execTasks.takeNextPending() 584 585 if nextTx != -1 { 586 pe.cntExec++ 587 588 task := ExecVersionView{ver: Version{nextTx, pe.txIncarnations[nextTx]}, et: pe.tasks[nextTx], mvh: pe.mvh, sender: pe.tasks[nextTx].Sender()} 589 590 pe.specTaskQueue.Push(nextTx, task) 591 pe.chSpeculativeTasks <- struct{}{} 592 } 593 } 594 595 return 596 } 597 598 type PropertyCheck func(*ParallelExecutor) error 599 600 func executeParallelWithCheck(tasks []ExecTask, profile bool, check PropertyCheck, metadata bool, interruptCtx context.Context) (result ParallelExecutionResult, err error) { 601 if len(tasks) == 0 { 602 return ParallelExecutionResult{MakeTxnInputOutput(len(tasks)), nil, nil, nil}, nil 603 } 604 605 pe := NewParallelExecutor(tasks, profile, metadata) 606 err = pe.Prepare() 607 608 if err != nil { 609 pe.Close(true) 610 return 611 } 612 613 for range pe.chResults { 614 if interruptCtx != nil && interruptCtx.Err() != nil { 615 pe.Close(true) 616 return result, interruptCtx.Err() 617 } 618 619 res := pe.resultQueue.Pop().(ExecResult) 620 621 result, err = pe.Step(&res) 622 623 if err != nil { 624 return result, err 625 } 626 627 if check != nil { 628 err = check(pe) 629 } 630 631 if result.TxIO != nil || err != nil { 632 return result, err 633 } 634 } 635 636 return 637 } 638 639 func ExecuteParallel(tasks []ExecTask, profile bool, metadata bool, interruptCtx context.Context) (result ParallelExecutionResult, err error) { 640 return executeParallelWithCheck(tasks, profile, nil, metadata, interruptCtx) 641 }