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  }