mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-11 14:33:52 +00:00
Block stm miner dependency (#561)
* added support for dependencies (executor_tests) * added a function to get dependency map * getting all dependencies in the GetDep function * updated GetDep function * changed the type of AllDeps * added a function to get dependency map * updated GetDep function * generate and get dependencies from block producer * optimized getDep function * bug fix regarding txn index and dep structure * fixed gas bug * optimized getDep function * tests updated/added * few updates regarding dependencies * added channel to calculate the dependencies in a separate go routine * minor changes in the executor which uses latest changes of dependecies + removed metadata flag/argument * Use channel when metadata is available * small bug fix * getting reads and writes only when the transaction succeeds * fixed bug in adding dependencies * updated logic for delay/not delay * bug fix (shouldDelayFeeCal) in parallel state processor * lint fix * using EnableMVHashMap flag * fixed worker and stateProcessor and removed SetMVHashMapNil fumction from stateDB * addredded few comments and fixed bug in executor tests * commented executor tests with metadata (panic: test timed out after 5m0s) * added a check to check len(mvReadMapList) > 0 in miner * addressed comments, minor refactoring in dag.go * moved blockContext out of Execute and adding it in execution task * removed Author() from Settle() and added in execution task * not calling block.Header() again and again, using instead * removed EnableMVHashMap flag, and updated applyTransaction function * addressed comments * added unit test to check dependencies in the block header Co-authored-by: Jerry <jerrycgh@gmail.com>
This commit is contained in:
parent
96e66e5256
commit
d53c2e7902
10 changed files with 928 additions and 84 deletions
|
|
@ -14,6 +14,12 @@ type DAG struct {
|
||||||
*dag.DAG
|
*dag.DAG
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TxDep struct {
|
||||||
|
Index int
|
||||||
|
ReadList []ReadDescriptor
|
||||||
|
FullWriteList [][]WriteDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
func HasReadDep(txFrom TxnOutput, txTo TxnInput) bool {
|
func HasReadDep(txFrom TxnOutput, txTo TxnInput) bool {
|
||||||
reads := make(map[Key]bool)
|
reads := make(map[Key]bool)
|
||||||
|
|
||||||
|
|
@ -69,6 +75,54 @@ func BuildDAG(deps TxnInputOutput) (d DAG) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func depsHelper(dependencies map[int]map[int]bool, txFrom TxnOutput, txTo TxnInput, i int, j int) map[int]map[int]bool {
|
||||||
|
if HasReadDep(txFrom, txTo) {
|
||||||
|
dependencies[i][j] = true
|
||||||
|
|
||||||
|
for k := range dependencies[i] {
|
||||||
|
_, foundDep := dependencies[j][k]
|
||||||
|
|
||||||
|
if foundDep {
|
||||||
|
delete(dependencies[i], k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateDeps(deps map[int]map[int]bool, t TxDep) map[int]map[int]bool {
|
||||||
|
txTo := t.ReadList
|
||||||
|
|
||||||
|
deps[t.Index] = map[int]bool{}
|
||||||
|
|
||||||
|
for j := 0; j <= t.Index-1; j++ {
|
||||||
|
txFrom := t.FullWriteList[j]
|
||||||
|
|
||||||
|
deps = depsHelper(deps, txFrom, txTo, t.Index, j)
|
||||||
|
}
|
||||||
|
|
||||||
|
return deps
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDep(deps TxnInputOutput) map[int]map[int]bool {
|
||||||
|
newDependencies := map[int]map[int]bool{}
|
||||||
|
|
||||||
|
for i := 1; i < len(deps.inputs); i++ {
|
||||||
|
txTo := deps.inputs[i]
|
||||||
|
|
||||||
|
newDependencies[i] = map[int]bool{}
|
||||||
|
|
||||||
|
for j := 0; j <= i-1; j++ {
|
||||||
|
txFrom := deps.allOutputs[j]
|
||||||
|
|
||||||
|
newDependencies = depsHelper(newDependencies, txFrom, txTo, i, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDependencies
|
||||||
|
}
|
||||||
|
|
||||||
// Find the longest execution path in the DAG
|
// Find the longest execution path in the DAG
|
||||||
func (d DAG) LongestPath(stats map[int]ExecutionStat) ([]int, uint64) {
|
func (d DAG) LongestPath(stats map[int]ExecutionStat) ([]int, uint64) {
|
||||||
prev := make(map[int]int, len(d.GetVertices()))
|
prev := make(map[int]int, len(d.GetVertices()))
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ type ExecTask interface {
|
||||||
Hash() common.Hash
|
Hash() common.Hash
|
||||||
Sender() common.Address
|
Sender() common.Address
|
||||||
Settle()
|
Settle()
|
||||||
|
Dependencies() []int
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExecVersionView struct {
|
type ExecVersionView struct {
|
||||||
|
|
@ -82,6 +83,34 @@ func (h *IntHeap) Pop() any {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SafeQueue interface {
|
||||||
|
Push(v int, d interface{})
|
||||||
|
Pop() interface{}
|
||||||
|
Len() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type SafeFIFOQueue struct {
|
||||||
|
c chan interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSafeFIFOQueue(capacity int) *SafeFIFOQueue {
|
||||||
|
return &SafeFIFOQueue{
|
||||||
|
c: make(chan interface{}, capacity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *SafeFIFOQueue) Push(v int, d interface{}) {
|
||||||
|
q.c <- d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *SafeFIFOQueue) Pop() interface{} {
|
||||||
|
return <-q.c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *SafeFIFOQueue) Len() int {
|
||||||
|
return len(q.c)
|
||||||
|
}
|
||||||
|
|
||||||
// A thread safe priority queue
|
// A thread safe priority queue
|
||||||
type SafePriorityQueue struct {
|
type SafePriorityQueue struct {
|
||||||
m sync.Mutex
|
m sync.Mutex
|
||||||
|
|
@ -122,9 +151,10 @@ func (pq *SafePriorityQueue) Len() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParallelExecutionResult struct {
|
type ParallelExecutionResult struct {
|
||||||
TxIO *TxnInputOutput
|
TxIO *TxnInputOutput
|
||||||
Stats *map[int]ExecutionStat
|
Stats *map[int]ExecutionStat
|
||||||
Deps *DAG
|
Deps *DAG
|
||||||
|
AllDeps map[int]map[int]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const numGoProcs = 2
|
const numGoProcs = 2
|
||||||
|
|
@ -145,7 +175,7 @@ type ParallelExecutor struct {
|
||||||
chSpeculativeTasks chan struct{}
|
chSpeculativeTasks chan struct{}
|
||||||
|
|
||||||
// Channel to signal that the result of a transaction could be written to storage
|
// Channel to signal that the result of a transaction could be written to storage
|
||||||
specTaskQueue *SafePriorityQueue
|
specTaskQueue SafeQueue
|
||||||
|
|
||||||
// A priority queue that stores speculative tasks
|
// A priority queue that stores speculative tasks
|
||||||
chSettle chan int
|
chSettle chan int
|
||||||
|
|
@ -154,7 +184,7 @@ type ParallelExecutor struct {
|
||||||
chResults chan struct{}
|
chResults chan struct{}
|
||||||
|
|
||||||
// A priority queue that stores the transaction index of results, so we can validate the results in order
|
// A priority queue that stores the transaction index of results, so we can validate the results in order
|
||||||
resultQueue *SafePriorityQueue
|
resultQueue SafeQueue
|
||||||
|
|
||||||
// A wait group to wait for all settling tasks to finish
|
// A wait group to wait for all settling tasks to finish
|
||||||
settleWg sync.WaitGroup
|
settleWg sync.WaitGroup
|
||||||
|
|
@ -211,9 +241,21 @@ type ExecutionStat struct {
|
||||||
Worker int
|
Worker int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParallelExecutor(tasks []ExecTask, profile bool) *ParallelExecutor {
|
func NewParallelExecutor(tasks []ExecTask, profile bool, metadata bool) *ParallelExecutor {
|
||||||
numTasks := len(tasks)
|
numTasks := len(tasks)
|
||||||
|
|
||||||
|
var resultQueue SafeQueue
|
||||||
|
|
||||||
|
var specTaskQueue SafeQueue
|
||||||
|
|
||||||
|
if metadata {
|
||||||
|
resultQueue = NewSafeFIFOQueue(numTasks)
|
||||||
|
specTaskQueue = NewSafeFIFOQueue(numTasks)
|
||||||
|
} else {
|
||||||
|
resultQueue = NewSafePriorityQueue(numTasks)
|
||||||
|
specTaskQueue = NewSafePriorityQueue(numTasks)
|
||||||
|
}
|
||||||
|
|
||||||
pe := &ParallelExecutor{
|
pe := &ParallelExecutor{
|
||||||
tasks: tasks,
|
tasks: tasks,
|
||||||
stats: make(map[int]ExecutionStat, numTasks),
|
stats: make(map[int]ExecutionStat, numTasks),
|
||||||
|
|
@ -221,8 +263,8 @@ func NewParallelExecutor(tasks []ExecTask, profile bool) *ParallelExecutor {
|
||||||
chSpeculativeTasks: make(chan struct{}, numTasks),
|
chSpeculativeTasks: make(chan struct{}, numTasks),
|
||||||
chSettle: make(chan int, numTasks),
|
chSettle: make(chan int, numTasks),
|
||||||
chResults: make(chan struct{}, numTasks),
|
chResults: make(chan struct{}, numTasks),
|
||||||
specTaskQueue: NewSafePriorityQueue(numTasks),
|
specTaskQueue: specTaskQueue,
|
||||||
resultQueue: NewSafePriorityQueue(numTasks),
|
resultQueue: resultQueue,
|
||||||
lastSettled: -1,
|
lastSettled: -1,
|
||||||
skipCheck: make(map[int]bool),
|
skipCheck: make(map[int]bool),
|
||||||
execTasks: makeStatusManager(numTasks),
|
execTasks: makeStatusManager(numTasks),
|
||||||
|
|
@ -241,19 +283,36 @@ func NewParallelExecutor(tasks []ExecTask, profile bool) *ParallelExecutor {
|
||||||
return pe
|
return pe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint: gocognit
|
||||||
func (pe *ParallelExecutor) Prepare() {
|
func (pe *ParallelExecutor) Prepare() {
|
||||||
prevSenderTx := make(map[common.Address]int)
|
|
||||||
|
|
||||||
for i, t := range pe.tasks {
|
for i, t := range pe.tasks {
|
||||||
|
clearPendingFlag := false
|
||||||
|
|
||||||
pe.skipCheck[i] = false
|
pe.skipCheck[i] = false
|
||||||
pe.estimateDeps[i] = make([]int, 0)
|
pe.estimateDeps[i] = make([]int, 0)
|
||||||
|
|
||||||
if tx, ok := prevSenderTx[t.Sender()]; ok {
|
if len(t.Dependencies()) > 0 {
|
||||||
pe.execTasks.addDependencies(tx, i)
|
for _, val := range t.Dependencies() {
|
||||||
pe.execTasks.clearPending(i)
|
clearPendingFlag = true
|
||||||
}
|
|
||||||
|
|
||||||
prevSenderTx[t.Sender()] = i
|
pe.execTasks.addDependencies(val, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if clearPendingFlag {
|
||||||
|
pe.execTasks.clearPending(i)
|
||||||
|
|
||||||
|
clearPendingFlag = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prevSenderTx := make(map[common.Address]int)
|
||||||
|
|
||||||
|
if tx, ok := prevSenderTx[t.Sender()]; ok {
|
||||||
|
pe.execTasks.addDependencies(tx, i)
|
||||||
|
pe.execTasks.clearPending(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
prevSenderTx[t.Sender()] = i
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pe.workerWg.Add(numSpeculativeProcs + numGoProcs)
|
pe.workerWg.Add(numSpeculativeProcs + numGoProcs)
|
||||||
|
|
@ -478,13 +537,13 @@ func (pe *ParallelExecutor) Step(res *ExecResult) (result ParallelExecutionResul
|
||||||
|
|
||||||
pe.Close(true)
|
pe.Close(true)
|
||||||
|
|
||||||
var dag DAG
|
var allDeps map[int]map[int]bool
|
||||||
|
|
||||||
if pe.profile {
|
if pe.profile {
|
||||||
dag = BuildDAG(*pe.lastTxIO)
|
allDeps = GetDep(*pe.lastTxIO)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParallelExecutionResult{pe.lastTxIO, &pe.stats, &dag}, err
|
return ParallelExecutionResult{pe.lastTxIO, &pe.stats, nil, allDeps}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the next immediate pending transaction to be executed
|
// Send the next immediate pending transaction to be executed
|
||||||
|
|
@ -518,12 +577,12 @@ func (pe *ParallelExecutor) Step(res *ExecResult) (result ParallelExecutionResul
|
||||||
|
|
||||||
type PropertyCheck func(*ParallelExecutor) error
|
type PropertyCheck func(*ParallelExecutor) error
|
||||||
|
|
||||||
func executeParallelWithCheck(tasks []ExecTask, profile bool, check PropertyCheck) (result ParallelExecutionResult, err error) {
|
func executeParallelWithCheck(tasks []ExecTask, profile bool, check PropertyCheck, metadata bool) (result ParallelExecutionResult, err error) {
|
||||||
if len(tasks) == 0 {
|
if len(tasks) == 0 {
|
||||||
return ParallelExecutionResult{MakeTxnInputOutput(len(tasks)), nil, nil}, nil
|
return ParallelExecutionResult{MakeTxnInputOutput(len(tasks)), nil, nil, nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pe := NewParallelExecutor(tasks, profile)
|
pe := NewParallelExecutor(tasks, profile, metadata)
|
||||||
pe.Prepare()
|
pe.Prepare()
|
||||||
|
|
||||||
for range pe.chResults {
|
for range pe.chResults {
|
||||||
|
|
@ -547,6 +606,6 @@ func executeParallelWithCheck(tasks []ExecTask, profile bool, check PropertyChec
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExecuteParallel(tasks []ExecTask, profile bool) (result ParallelExecutionResult, err error) {
|
func ExecuteParallel(tasks []ExecTask, profile bool, metadata bool) (result ParallelExecutionResult, err error) {
|
||||||
return executeParallelWithCheck(tasks, profile, nil)
|
return executeParallelWithCheck(tasks, profile, nil, metadata)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ type OpType int
|
||||||
const readType = 0
|
const readType = 0
|
||||||
const writeType = 1
|
const writeType = 1
|
||||||
const otherType = 2
|
const otherType = 2
|
||||||
|
const greenTick = "✅"
|
||||||
|
const redCross = "❌"
|
||||||
|
|
||||||
|
const threeRockets = "🚀🚀🚀"
|
||||||
|
|
||||||
type Op struct {
|
type Op struct {
|
||||||
key Key
|
key Key
|
||||||
|
|
@ -28,30 +32,34 @@ type Op struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type testExecTask struct {
|
type testExecTask struct {
|
||||||
txIdx int
|
txIdx int
|
||||||
ops []Op
|
ops []Op
|
||||||
readMap map[Key]ReadDescriptor
|
readMap map[Key]ReadDescriptor
|
||||||
writeMap map[Key]WriteDescriptor
|
writeMap map[Key]WriteDescriptor
|
||||||
sender common.Address
|
sender common.Address
|
||||||
nonce int
|
nonce int
|
||||||
|
dependencies []int
|
||||||
}
|
}
|
||||||
|
|
||||||
type PathGenerator func(addr common.Address, i int, j int, total int) Key
|
type PathGenerator func(addr common.Address, i int, j int, total int) Key
|
||||||
|
|
||||||
type TaskRunner func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration)
|
type TaskRunner func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration)
|
||||||
|
|
||||||
|
type TaskRunnerWithMetadata func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration)
|
||||||
|
|
||||||
type Timer func(txIdx int, opIdx int) time.Duration
|
type Timer func(txIdx int, opIdx int) time.Duration
|
||||||
|
|
||||||
type Sender func(int) common.Address
|
type Sender func(int) common.Address
|
||||||
|
|
||||||
func NewTestExecTask(txIdx int, ops []Op, sender common.Address, nonce int) *testExecTask {
|
func NewTestExecTask(txIdx int, ops []Op, sender common.Address, nonce int) *testExecTask {
|
||||||
return &testExecTask{
|
return &testExecTask{
|
||||||
txIdx: txIdx,
|
txIdx: txIdx,
|
||||||
ops: ops,
|
ops: ops,
|
||||||
readMap: make(map[Key]ReadDescriptor),
|
readMap: make(map[Key]ReadDescriptor),
|
||||||
writeMap: make(map[Key]WriteDescriptor),
|
writeMap: make(map[Key]WriteDescriptor),
|
||||||
sender: sender,
|
sender: sender,
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
|
dependencies: []int{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,6 +165,10 @@ func (t *testExecTask) Hash() common.Hash {
|
||||||
return common.BytesToHash([]byte(fmt.Sprintf("%d", t.txIdx)))
|
return common.BytesToHash([]byte(fmt.Sprintf("%d", t.txIdx)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *testExecTask) Dependencies() []int {
|
||||||
|
return t.dependencies
|
||||||
|
}
|
||||||
|
|
||||||
func randTimeGenerator(min time.Duration, max time.Duration) func(txIdx int, opIdx int) time.Duration {
|
func randTimeGenerator(min time.Duration, max time.Duration) func(txIdx int, opIdx int) time.Duration {
|
||||||
return func(txIdx int, opIdx int) time.Duration {
|
return func(txIdx int, opIdx int) time.Duration {
|
||||||
return time.Duration(rand.Int63n(int64(max-min))) + min
|
return time.Duration(rand.Int63n(int64(max-min))) + min
|
||||||
|
|
@ -275,10 +287,10 @@ func testExecutorComb(t *testing.T, totalTxs []int, numReads []int, numWrites []
|
||||||
}
|
}
|
||||||
total++
|
total++
|
||||||
|
|
||||||
performance := "✅"
|
performance := greenTick
|
||||||
|
|
||||||
if execDuration >= expectedSerialDuration {
|
if execDuration >= expectedSerialDuration {
|
||||||
performance = "❌"
|
performance = redCross
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("exec duration %v, serial duration %v, time reduced %v %.2f%%, %v \n", execDuration, expectedSerialDuration, expectedSerialDuration-execDuration, float64(expectedSerialDuration-execDuration)/float64(expectedSerialDuration)*100, performance)
|
fmt.Printf("exec duration %v, serial duration %v, time reduced %v %.2f%%, %v \n", execDuration, expectedSerialDuration, expectedSerialDuration-execDuration, float64(expectedSerialDuration-execDuration)/float64(expectedSerialDuration)*100, performance)
|
||||||
|
|
@ -294,6 +306,66 @@ func testExecutorComb(t *testing.T, totalTxs []int, numReads []int, numWrites []
|
||||||
fmt.Printf("Total exec duration: %v, total serial duration: %v, time reduced: %v, time reduced percent: %.2f%%\n", totalExecDuration, totalSerialDuration, totalSerialDuration-totalExecDuration, float64(totalSerialDuration-totalExecDuration)/float64(totalSerialDuration)*100)
|
fmt.Printf("Total exec duration: %v, total serial duration: %v, time reduced: %v, time reduced percent: %.2f%%\n", totalExecDuration, totalSerialDuration, totalSerialDuration-totalExecDuration, float64(totalSerialDuration-totalExecDuration)/float64(totalSerialDuration)*100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint: gocognit
|
||||||
|
func testExecutorCombWithMetadata(t *testing.T, totalTxs []int, numReads []int, numWrites []int, numNonIOs []int, taskRunner TaskRunnerWithMetadata) {
|
||||||
|
t.Helper()
|
||||||
|
log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
|
||||||
|
|
||||||
|
improved := 0
|
||||||
|
improvedMetadata := 0
|
||||||
|
rocket := 0
|
||||||
|
total := 0
|
||||||
|
|
||||||
|
totalExecDuration := time.Duration(0)
|
||||||
|
totalExecDurationMetadata := time.Duration(0)
|
||||||
|
totalSerialDuration := time.Duration(0)
|
||||||
|
|
||||||
|
for _, numTx := range totalTxs {
|
||||||
|
for _, numRead := range numReads {
|
||||||
|
for _, numWrite := range numWrites {
|
||||||
|
for _, numNonIO := range numNonIOs {
|
||||||
|
log.Info("Executing block", "numTx", numTx, "numRead", numRead, "numWrite", numWrite, "numNonIO", numNonIO)
|
||||||
|
execDuration, execDurationMetadata, expectedSerialDuration := taskRunner(numTx, numRead, numWrite, numNonIO)
|
||||||
|
|
||||||
|
if execDuration < expectedSerialDuration {
|
||||||
|
improved++
|
||||||
|
}
|
||||||
|
total++
|
||||||
|
|
||||||
|
performance := greenTick
|
||||||
|
|
||||||
|
if execDuration >= expectedSerialDuration {
|
||||||
|
performance = redCross
|
||||||
|
|
||||||
|
if execDurationMetadata <= expectedSerialDuration {
|
||||||
|
performance = threeRockets
|
||||||
|
rocket++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if execDuration >= execDurationMetadata {
|
||||||
|
improvedMetadata++
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("WITHOUT METADATA: exec duration %v, serial duration %v, time reduced %v %.2f%%, %v \n", execDuration, expectedSerialDuration, expectedSerialDuration-execDuration, float64(expectedSerialDuration-execDuration)/float64(expectedSerialDuration)*100, performance)
|
||||||
|
fmt.Printf("WITH METADATA: exec duration %v, exec duration with metadata %v, time reduced %v %.2f%%\n", execDuration, execDurationMetadata, execDuration-execDurationMetadata, float64(execDuration-execDurationMetadata)/float64(execDuration)*100)
|
||||||
|
|
||||||
|
totalExecDuration += execDuration
|
||||||
|
totalExecDurationMetadata += execDurationMetadata
|
||||||
|
totalSerialDuration += expectedSerialDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\nImproved: ", improved, "Total: ", total, "success rate: ", float64(improved)/float64(total)*100)
|
||||||
|
fmt.Println("Metadata Better: ", improvedMetadata, "out of: ", total, "success rate: ", float64(improvedMetadata)/float64(total)*100)
|
||||||
|
fmt.Println("Rockets (Time of: metadata < serial < without metadata): ", rocket)
|
||||||
|
fmt.Printf("\nWithout metadata <> serial: Total exec duration: %v, total serial duration : %v, time reduced: %v, time reduced percent: %.2f%%\n", totalExecDuration, totalSerialDuration, totalSerialDuration-totalExecDuration, float64(totalSerialDuration-totalExecDuration)/float64(totalSerialDuration)*100)
|
||||||
|
fmt.Printf("With metadata <> serial: Total exec duration metadata: %v, total serial duration : %v, time reduced: %v, time reduced percent: %.2f%%\n", totalExecDurationMetadata, totalSerialDuration, totalSerialDuration-totalExecDurationMetadata, float64(totalSerialDuration-totalExecDurationMetadata)/float64(totalSerialDuration)*100)
|
||||||
|
fmt.Printf("Without metadata <> with metadata: Total exec duration: %v, total exec duration metadata: %v, time reduced: %v, time reduced percent: %.2f%%\n", totalExecDuration, totalExecDurationMetadata, totalExecDuration-totalExecDurationMetadata, float64(totalExecDuration-totalExecDurationMetadata)/float64(totalExecDuration)*100)
|
||||||
|
}
|
||||||
|
|
||||||
func composeValidations(checks []PropertyCheck) PropertyCheck {
|
func composeValidations(checks []PropertyCheck) PropertyCheck {
|
||||||
return func(pe *ParallelExecutor) error {
|
return func(pe *ParallelExecutor) error {
|
||||||
for _, check := range checks {
|
for _, check := range checks {
|
||||||
|
|
@ -345,13 +417,14 @@ func checkNoDroppedTx(pe *ParallelExecutor) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runParallel(t *testing.T, tasks []ExecTask, validation PropertyCheck) time.Duration {
|
// nolint: unparam
|
||||||
|
func runParallel(t *testing.T, tasks []ExecTask, validation PropertyCheck, metadata bool) time.Duration {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
profile := false
|
profile := false
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
result, err := executeParallelWithCheck(tasks, profile, validation)
|
result, err := executeParallelWithCheck(tasks, false, validation, metadata)
|
||||||
|
|
||||||
if result.Deps != nil && profile {
|
if result.Deps != nil && profile {
|
||||||
result.Deps.Report(*result.Stats, func(str string) { fmt.Println(str) })
|
result.Deps.Report(*result.Stats, func(str string) { fmt.Println(str) })
|
||||||
|
|
@ -381,6 +454,17 @@ func runParallel(t *testing.T, tasks []ExecTask, validation PropertyCheck) time.
|
||||||
return duration
|
return duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runParallelGetMetadata(t *testing.T, tasks []ExecTask, validation PropertyCheck) map[int]map[int]bool {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
// fmt.Println("len(tasks)", len(tasks))
|
||||||
|
res, err := executeParallelWithCheck(tasks, true, validation, false)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "error occur during parallel execution")
|
||||||
|
|
||||||
|
return res.AllDeps
|
||||||
|
}
|
||||||
|
|
||||||
func TestLessConflicts(t *testing.T) {
|
func TestLessConflicts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rand.Seed(0)
|
rand.Seed(0)
|
||||||
|
|
@ -399,12 +483,58 @@ func TestLessConflicts(t *testing.T) {
|
||||||
}
|
}
|
||||||
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
||||||
|
|
||||||
return runParallel(t, tasks, checks), serialDuration
|
return runParallel(t, tasks, checks, false), serialDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLessConflictsWithMetadata(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
rand.Seed(0)
|
||||||
|
|
||||||
|
totalTxs := []int{300}
|
||||||
|
numReads := []int{100, 200}
|
||||||
|
numWrites := []int{100, 200}
|
||||||
|
numNonIOs := []int{100, 500}
|
||||||
|
|
||||||
|
checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
|
||||||
|
|
||||||
|
taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
|
||||||
|
sender := func(i int) common.Address {
|
||||||
|
randomness := rand.Intn(10) + 10
|
||||||
|
return common.BigToAddress(big.NewInt(int64(i % randomness)))
|
||||||
|
}
|
||||||
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
||||||
|
|
||||||
|
parallelDuration := runParallel(t, tasks, checks, false)
|
||||||
|
|
||||||
|
allDeps := runParallelGetMetadata(t, tasks, checks)
|
||||||
|
|
||||||
|
newTasks := make([]ExecTask, 0, len(tasks))
|
||||||
|
|
||||||
|
for _, t := range tasks {
|
||||||
|
temp := t.(*testExecTask)
|
||||||
|
|
||||||
|
keys := make([]int, len(allDeps[temp.txIdx]))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for k := range allDeps[temp.txIdx] {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
temp.dependencies = keys
|
||||||
|
newTasks = append(newTasks, temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIOs, taskRunner)
|
||||||
|
}
|
||||||
|
|
||||||
func TestZeroTx(t *testing.T) {
|
func TestZeroTx(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rand.Seed(0)
|
rand.Seed(0)
|
||||||
|
|
@ -420,7 +550,7 @@ func TestZeroTx(t *testing.T) {
|
||||||
sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(1))) }
|
sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(1))) }
|
||||||
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
||||||
|
|
||||||
return runParallel(t, tasks, checks), serialDuration
|
return runParallel(t, tasks, checks, false), serialDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
|
|
@ -441,12 +571,55 @@ func TestAlternatingTx(t *testing.T) {
|
||||||
sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(i % 2))) }
|
sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(i % 2))) }
|
||||||
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
||||||
|
|
||||||
return runParallel(t, tasks, checks), serialDuration
|
return runParallel(t, tasks, checks, false), serialDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAlternatingTxWithMetadata(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
rand.Seed(0)
|
||||||
|
|
||||||
|
totalTxs := []int{200}
|
||||||
|
numReads := []int{20}
|
||||||
|
numWrites := []int{20}
|
||||||
|
numNonIO := []int{100}
|
||||||
|
|
||||||
|
checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
|
||||||
|
|
||||||
|
taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
|
||||||
|
sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(i % 2))) }
|
||||||
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
||||||
|
|
||||||
|
parallelDuration := runParallel(t, tasks, checks, false)
|
||||||
|
|
||||||
|
allDeps := runParallelGetMetadata(t, tasks, checks)
|
||||||
|
|
||||||
|
newTasks := make([]ExecTask, 0, len(tasks))
|
||||||
|
|
||||||
|
for _, t := range tasks {
|
||||||
|
temp := t.(*testExecTask)
|
||||||
|
|
||||||
|
keys := make([]int, len(allDeps[temp.txIdx]))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for k := range allDeps[temp.txIdx] {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
temp.dependencies = keys
|
||||||
|
newTasks = append(newTasks, temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
|
}
|
||||||
|
|
||||||
func TestMoreConflicts(t *testing.T) {
|
func TestMoreConflicts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rand.Seed(0)
|
rand.Seed(0)
|
||||||
|
|
@ -465,12 +638,58 @@ func TestMoreConflicts(t *testing.T) {
|
||||||
}
|
}
|
||||||
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
||||||
|
|
||||||
return runParallel(t, tasks, checks), serialDuration
|
return runParallel(t, tasks, checks, false), serialDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMoreConflictsWithMetadata(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
rand.Seed(0)
|
||||||
|
|
||||||
|
totalTxs := []int{300}
|
||||||
|
numReads := []int{100, 200}
|
||||||
|
numWrites := []int{100, 200}
|
||||||
|
numNonIO := []int{100, 500}
|
||||||
|
|
||||||
|
checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
|
||||||
|
|
||||||
|
taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
|
||||||
|
sender := func(i int) common.Address {
|
||||||
|
randomness := rand.Intn(10) + 10
|
||||||
|
return common.BigToAddress(big.NewInt(int64(i / randomness)))
|
||||||
|
}
|
||||||
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
||||||
|
|
||||||
|
parallelDuration := runParallel(t, tasks, checks, false)
|
||||||
|
|
||||||
|
allDeps := runParallelGetMetadata(t, tasks, checks)
|
||||||
|
|
||||||
|
newTasks := make([]ExecTask, 0, len(tasks))
|
||||||
|
|
||||||
|
for _, t := range tasks {
|
||||||
|
temp := t.(*testExecTask)
|
||||||
|
|
||||||
|
keys := make([]int, len(allDeps[temp.txIdx]))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for k := range allDeps[temp.txIdx] {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
temp.dependencies = keys
|
||||||
|
newTasks = append(newTasks, temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRandomTx(t *testing.T) {
|
func TestRandomTx(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rand.Seed(0)
|
rand.Seed(0)
|
||||||
|
|
@ -487,12 +706,56 @@ func TestRandomTx(t *testing.T) {
|
||||||
sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(rand.Intn(10)))) }
|
sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(rand.Intn(10)))) }
|
||||||
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
||||||
|
|
||||||
return runParallel(t, tasks, checks), serialDuration
|
return runParallel(t, tasks, checks, false), serialDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRandomTxWithMetadata(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
rand.Seed(0)
|
||||||
|
|
||||||
|
totalTxs := []int{300}
|
||||||
|
numReads := []int{100, 200}
|
||||||
|
numWrites := []int{100, 200}
|
||||||
|
numNonIO := []int{100, 500}
|
||||||
|
|
||||||
|
checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
|
||||||
|
|
||||||
|
taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
|
||||||
|
// Randomly assign this tx to one of 10 senders
|
||||||
|
sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(rand.Intn(10)))) }
|
||||||
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, readTime, writeTime, nonIOTime)
|
||||||
|
|
||||||
|
parallelDuration := runParallel(t, tasks, checks, false)
|
||||||
|
|
||||||
|
allDeps := runParallelGetMetadata(t, tasks, checks)
|
||||||
|
|
||||||
|
newTasks := make([]ExecTask, 0, len(tasks))
|
||||||
|
|
||||||
|
for _, t := range tasks {
|
||||||
|
temp := t.(*testExecTask)
|
||||||
|
|
||||||
|
keys := make([]int, len(allDeps[temp.txIdx]))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for k := range allDeps[temp.txIdx] {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
temp.dependencies = keys
|
||||||
|
newTasks = append(newTasks, temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTxWithLongTailRead(t *testing.T) {
|
func TestTxWithLongTailRead(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rand.Seed(0)
|
rand.Seed(0)
|
||||||
|
|
@ -514,12 +777,61 @@ func TestTxWithLongTailRead(t *testing.T) {
|
||||||
|
|
||||||
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, longTailReadTimer, writeTime, nonIOTime)
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, longTailReadTimer, writeTime, nonIOTime)
|
||||||
|
|
||||||
return runParallel(t, tasks, checks), serialDuration
|
return runParallel(t, tasks, checks, false), serialDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTxWithLongTailReadWithMetadata(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
rand.Seed(0)
|
||||||
|
|
||||||
|
totalTxs := []int{300}
|
||||||
|
numReads := []int{100, 200}
|
||||||
|
numWrites := []int{100, 200}
|
||||||
|
numNonIO := []int{100, 500}
|
||||||
|
|
||||||
|
checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, checkNoDroppedTx})
|
||||||
|
|
||||||
|
taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
|
||||||
|
sender := func(i int) common.Address {
|
||||||
|
randomness := rand.Intn(10) + 10
|
||||||
|
return common.BigToAddress(big.NewInt(int64(i / randomness)))
|
||||||
|
}
|
||||||
|
|
||||||
|
longTailReadTimer := longTailTimeGenerator(4*time.Microsecond, 12*time.Microsecond, 7, 10)
|
||||||
|
|
||||||
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, randomPathGenerator, longTailReadTimer, writeTime, nonIOTime)
|
||||||
|
|
||||||
|
parallelDuration := runParallel(t, tasks, checks, false)
|
||||||
|
|
||||||
|
allDeps := runParallelGetMetadata(t, tasks, checks)
|
||||||
|
|
||||||
|
newTasks := make([]ExecTask, 0, len(tasks))
|
||||||
|
|
||||||
|
for _, t := range tasks {
|
||||||
|
temp := t.(*testExecTask)
|
||||||
|
|
||||||
|
keys := make([]int, len(allDeps[temp.txIdx]))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for k := range allDeps[temp.txIdx] {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
temp.dependencies = keys
|
||||||
|
newTasks = append(newTasks, temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDexScenario(t *testing.T) {
|
func TestDexScenario(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rand.Seed(0)
|
rand.Seed(0)
|
||||||
|
|
@ -549,8 +861,65 @@ func TestDexScenario(t *testing.T) {
|
||||||
sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(i))) }
|
sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(i))) }
|
||||||
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, dexPathGenerator, readTime, writeTime, nonIOTime)
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, dexPathGenerator, readTime, writeTime, nonIOTime)
|
||||||
|
|
||||||
return runParallel(t, tasks, checks), serialDuration
|
return runParallel(t, tasks, checks, false), serialDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
testExecutorComb(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDexScenarioWithMetadata(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
rand.Seed(0)
|
||||||
|
|
||||||
|
totalTxs := []int{300}
|
||||||
|
numReads := []int{100, 200}
|
||||||
|
numWrites := []int{100, 200}
|
||||||
|
numNonIO := []int{100, 500}
|
||||||
|
|
||||||
|
postValidation := func(pe *ParallelExecutor) error {
|
||||||
|
if pe.lastSettled == len(pe.tasks) {
|
||||||
|
for i, inputs := range pe.lastTxIO.inputs {
|
||||||
|
for _, input := range inputs {
|
||||||
|
if input.V.TxnIndex != i-1 {
|
||||||
|
return fmt.Errorf("Tx %d should depend on tx %d, but it actually depends on %d", i, i-1, input.V.TxnIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
checks := composeValidations([]PropertyCheck{checkNoStatusOverlap, postValidation, checkNoDroppedTx})
|
||||||
|
|
||||||
|
taskRunner := func(numTx int, numRead int, numWrite int, numNonIO int) (time.Duration, time.Duration, time.Duration) {
|
||||||
|
sender := func(i int) common.Address { return common.BigToAddress(big.NewInt(int64(i))) }
|
||||||
|
tasks, serialDuration := taskFactory(numTx, sender, numRead, numWrite, numNonIO, dexPathGenerator, readTime, writeTime, nonIOTime)
|
||||||
|
|
||||||
|
parallelDuration := runParallel(t, tasks, checks, false)
|
||||||
|
|
||||||
|
allDeps := runParallelGetMetadata(t, tasks, checks)
|
||||||
|
|
||||||
|
newTasks := make([]ExecTask, 0, len(tasks))
|
||||||
|
|
||||||
|
for _, t := range tasks {
|
||||||
|
temp := t.(*testExecTask)
|
||||||
|
|
||||||
|
keys := make([]int, len(allDeps[temp.txIdx]))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for k := range allDeps[temp.txIdx] {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
temp.dependencies = keys
|
||||||
|
newTasks = append(newTasks, temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parallelDuration, runParallel(t, newTasks, checks, true), serialDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
testExecutorCombWithMetadata(t, totalTxs, numReads, numWrites, numNonIO, taskRunner)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,3 +80,15 @@ func (io *TxnInputOutput) recordWrite(txId int, output []WriteDescriptor) {
|
||||||
func (io *TxnInputOutput) recordAllWrite(txId int, output []WriteDescriptor) {
|
func (io *TxnInputOutput) recordAllWrite(txId int, output []WriteDescriptor) {
|
||||||
io.allOutputs[txId] = output
|
io.allOutputs[txId] = output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (io *TxnInputOutput) RecordReadAtOnce(inputs [][]ReadDescriptor) {
|
||||||
|
for ind, val := range inputs {
|
||||||
|
io.inputs[ind] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (io *TxnInputOutput) RecordAllWriteAtOnce(outputs [][]WriteDescriptor) {
|
||||||
|
for ind, val := range outputs {
|
||||||
|
io.allOutputs[ind] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,15 @@ type ExecutionTask struct {
|
||||||
totalUsedGas *uint64
|
totalUsedGas *uint64
|
||||||
receipts *types.Receipts
|
receipts *types.Receipts
|
||||||
allLogs *[]*types.Log
|
allLogs *[]*types.Log
|
||||||
|
|
||||||
|
// length of dependencies -> 2 + k (k = a whole number)
|
||||||
|
// first 2 element in dependencies -> transaction index, and flag representing if delay is allowed or not
|
||||||
|
// (0 -> delay is not allowed, 1 -> delay is allowed)
|
||||||
|
// next k elements in dependencies -> transaction indexes on which transaction i is dependent on
|
||||||
|
dependencies []int
|
||||||
|
|
||||||
|
blockContext vm.BlockContext
|
||||||
|
coinbase common.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
func (task *ExecutionTask) Execute(mvh *blockstm.MVHashMap, incarnation int) (err error) {
|
func (task *ExecutionTask) Execute(mvh *blockstm.MVHashMap, incarnation int) (err error) {
|
||||||
|
|
@ -83,9 +92,7 @@ func (task *ExecutionTask) Execute(mvh *blockstm.MVHashMap, incarnation int) (er
|
||||||
task.statedb.SetMVHashmap(mvh)
|
task.statedb.SetMVHashmap(mvh)
|
||||||
task.statedb.SetIncarnation(incarnation)
|
task.statedb.SetIncarnation(incarnation)
|
||||||
|
|
||||||
blockContext := NewEVMBlockContext(task.header, task.blockChain, nil)
|
evm := vm.NewEVM(task.blockContext, vm.TxContext{}, task.statedb, task.config, task.evmConfig)
|
||||||
|
|
||||||
evm := vm.NewEVM(blockContext, vm.TxContext{}, task.statedb, task.config, task.evmConfig)
|
|
||||||
|
|
||||||
// Create a new context to be used in the EVM environment.
|
// Create a new context to be used in the EVM environment.
|
||||||
txContext := NewEVMTxContext(task.msg)
|
txContext := NewEVMTxContext(task.msg)
|
||||||
|
|
@ -112,8 +119,8 @@ func (task *ExecutionTask) Execute(mvh *blockstm.MVHashMap, incarnation int) (er
|
||||||
|
|
||||||
reads := task.statedb.MVReadMap()
|
reads := task.statedb.MVReadMap()
|
||||||
|
|
||||||
if _, ok := reads[blockstm.NewSubpathKey(blockContext.Coinbase, state.BalancePath)]; ok {
|
if _, ok := reads[blockstm.NewSubpathKey(task.blockContext.Coinbase, state.BalancePath)]; ok {
|
||||||
log.Info("Coinbase is in MVReadMap", "address", blockContext.Coinbase)
|
log.Info("Coinbase is in MVReadMap", "address", task.blockContext.Coinbase)
|
||||||
|
|
||||||
task.shouldRerunWithoutFeeDelay = true
|
task.shouldRerunWithoutFeeDelay = true
|
||||||
}
|
}
|
||||||
|
|
@ -157,6 +164,10 @@ func (task *ExecutionTask) Hash() common.Hash {
|
||||||
return task.tx.Hash()
|
return task.tx.Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (task *ExecutionTask) Dependencies() []int {
|
||||||
|
return task.dependencies
|
||||||
|
}
|
||||||
|
|
||||||
func (task *ExecutionTask) Settle() {
|
func (task *ExecutionTask) Settle() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
|
|
@ -171,9 +182,7 @@ func (task *ExecutionTask) Settle() {
|
||||||
|
|
||||||
task.finalStateDB.Prepare(task.tx.Hash(), task.index)
|
task.finalStateDB.Prepare(task.tx.Hash(), task.index)
|
||||||
|
|
||||||
coinbase, _ := task.blockChain.Engine().Author(task.header)
|
coinbaseBalance := task.finalStateDB.GetBalance(task.coinbase)
|
||||||
|
|
||||||
coinbaseBalance := task.finalStateDB.GetBalance(coinbase)
|
|
||||||
|
|
||||||
task.finalStateDB.ApplyMVWriteSet(task.statedb.MVWriteList())
|
task.finalStateDB.ApplyMVWriteSet(task.statedb.MVWriteList())
|
||||||
|
|
||||||
|
|
@ -186,7 +195,7 @@ func (task *ExecutionTask) Settle() {
|
||||||
task.finalStateDB.AddBalance(task.result.BurntContractAddress, task.result.FeeBurnt)
|
task.finalStateDB.AddBalance(task.result.BurntContractAddress, task.result.FeeBurnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
task.finalStateDB.AddBalance(coinbase, task.result.FeeTipped)
|
task.finalStateDB.AddBalance(task.coinbase, task.result.FeeTipped)
|
||||||
output1 := new(big.Int).SetBytes(task.result.SenderInitBalance.Bytes())
|
output1 := new(big.Int).SetBytes(task.result.SenderInitBalance.Bytes())
|
||||||
output2 := new(big.Int).SetBytes(coinbaseBalance.Bytes())
|
output2 := new(big.Int).SetBytes(coinbaseBalance.Bytes())
|
||||||
|
|
||||||
|
|
@ -196,7 +205,7 @@ func (task *ExecutionTask) Settle() {
|
||||||
task.finalStateDB,
|
task.finalStateDB,
|
||||||
|
|
||||||
task.msg.From(),
|
task.msg.From(),
|
||||||
coinbase,
|
task.coinbase,
|
||||||
|
|
||||||
task.result.FeeTipped,
|
task.result.FeeTipped,
|
||||||
task.result.SenderInitBalance,
|
task.result.SenderInitBalance,
|
||||||
|
|
@ -267,6 +276,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
|
||||||
blockNumber = block.Number()
|
blockNumber = block.Number()
|
||||||
allLogs []*types.Log
|
allLogs []*types.Log
|
||||||
usedGas = new(uint64)
|
usedGas = new(uint64)
|
||||||
|
metadata bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mutate the block and state according to any hard-fork specs
|
// Mutate the block and state according to any hard-fork specs
|
||||||
|
|
@ -280,6 +290,14 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
|
||||||
|
|
||||||
coinbase, _ := p.bc.Engine().Author(header)
|
coinbase, _ := p.bc.Engine().Author(header)
|
||||||
|
|
||||||
|
deps, delayMap := GetDeps(block.Header().TxDependency)
|
||||||
|
|
||||||
|
if block.Header().TxDependency != nil {
|
||||||
|
metadata = true
|
||||||
|
}
|
||||||
|
|
||||||
|
blockContext := NewEVMBlockContext(header, p.bc, nil)
|
||||||
|
|
||||||
// Iterate over and process the individual transactions
|
// Iterate over and process the individual transactions
|
||||||
for i, tx := range block.Transactions() {
|
for i, tx := range block.Transactions() {
|
||||||
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
|
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
|
||||||
|
|
@ -290,37 +308,69 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
|
||||||
|
|
||||||
cleansdb := statedb.Copy()
|
cleansdb := statedb.Copy()
|
||||||
|
|
||||||
if msg.From() == coinbase {
|
if len(header.TxDependency) > 0 {
|
||||||
shouldDelayFeeCal = false
|
shouldDelayFeeCal = delayMap[i]
|
||||||
}
|
|
||||||
|
|
||||||
task := &ExecutionTask{
|
task := &ExecutionTask{
|
||||||
msg: msg,
|
msg: msg,
|
||||||
config: p.config,
|
config: p.config,
|
||||||
gasLimit: block.GasLimit(),
|
gasLimit: block.GasLimit(),
|
||||||
blockNumber: blockNumber,
|
blockNumber: blockNumber,
|
||||||
blockHash: blockHash,
|
blockHash: blockHash,
|
||||||
tx: tx,
|
tx: tx,
|
||||||
index: i,
|
index: i,
|
||||||
cleanStateDB: cleansdb,
|
cleanStateDB: cleansdb,
|
||||||
finalStateDB: statedb,
|
finalStateDB: statedb,
|
||||||
blockChain: p.bc,
|
blockChain: p.bc,
|
||||||
header: header,
|
header: header,
|
||||||
evmConfig: cfg,
|
evmConfig: cfg,
|
||||||
shouldDelayFeeCal: &shouldDelayFeeCal,
|
shouldDelayFeeCal: &shouldDelayFeeCal,
|
||||||
sender: msg.From(),
|
sender: msg.From(),
|
||||||
totalUsedGas: usedGas,
|
totalUsedGas: usedGas,
|
||||||
receipts: &receipts,
|
receipts: &receipts,
|
||||||
allLogs: &allLogs,
|
allLogs: &allLogs,
|
||||||
}
|
dependencies: deps[i],
|
||||||
|
blockContext: blockContext,
|
||||||
|
coinbase: coinbase,
|
||||||
|
}
|
||||||
|
|
||||||
tasks = append(tasks, task)
|
tasks = append(tasks, task)
|
||||||
|
} else {
|
||||||
|
if msg.From() == coinbase {
|
||||||
|
shouldDelayFeeCal = false
|
||||||
|
}
|
||||||
|
|
||||||
|
task := &ExecutionTask{
|
||||||
|
msg: msg,
|
||||||
|
config: p.config,
|
||||||
|
gasLimit: block.GasLimit(),
|
||||||
|
blockNumber: blockNumber,
|
||||||
|
blockHash: blockHash,
|
||||||
|
tx: tx,
|
||||||
|
index: i,
|
||||||
|
cleanStateDB: cleansdb,
|
||||||
|
finalStateDB: statedb,
|
||||||
|
blockChain: p.bc,
|
||||||
|
header: header,
|
||||||
|
evmConfig: cfg,
|
||||||
|
shouldDelayFeeCal: &shouldDelayFeeCal,
|
||||||
|
sender: msg.From(),
|
||||||
|
totalUsedGas: usedGas,
|
||||||
|
receipts: &receipts,
|
||||||
|
allLogs: &allLogs,
|
||||||
|
dependencies: nil,
|
||||||
|
blockContext: blockContext,
|
||||||
|
coinbase: coinbase,
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks = append(tasks, task)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
backupStateDB := statedb.Copy()
|
backupStateDB := statedb.Copy()
|
||||||
|
|
||||||
profile := false
|
profile := false
|
||||||
result, err := blockstm.ExecuteParallel(tasks, profile)
|
result, err := blockstm.ExecuteParallel(tasks, false, metadata)
|
||||||
|
|
||||||
if err == nil && profile {
|
if err == nil && profile {
|
||||||
_, weight := result.Deps.LongestPath(*result.Stats)
|
_, weight := result.Deps.LongestPath(*result.Stats)
|
||||||
|
|
@ -356,7 +406,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
|
||||||
t.totalUsedGas = usedGas
|
t.totalUsedGas = usedGas
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = blockstm.ExecuteParallel(tasks, false)
|
_, err = blockstm.ExecuteParallel(tasks, false, metadata)
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -372,3 +422,23 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
|
||||||
|
|
||||||
return receipts, allLogs, *usedGas, nil
|
return receipts, allLogs, *usedGas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDeps(txDependency [][]uint64) (map[int][]int, map[int]bool) {
|
||||||
|
deps := make(map[int][]int)
|
||||||
|
delayMap := make(map[int]bool)
|
||||||
|
|
||||||
|
for i := 0; i <= len(txDependency)-1; i++ {
|
||||||
|
idx := int(txDependency[i][0])
|
||||||
|
shouldDelay := txDependency[i][1] == 1
|
||||||
|
|
||||||
|
delayMap[idx] = shouldDelay
|
||||||
|
|
||||||
|
deps[idx] = []int{}
|
||||||
|
|
||||||
|
for j := 2; j <= len(txDependency[i])-1; j++ {
|
||||||
|
deps[idx] = append(deps[idx], int(txDependency[i][j]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deps, delayMap
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,10 @@ func (sdb *StateDB) SetMVHashmap(mvhm *blockstm.MVHashMap) {
|
||||||
sdb.dep = -1
|
sdb.dep = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sdb *StateDB) GetMVHashmap() *blockstm.MVHashMap {
|
||||||
|
return sdb.mvHashmap
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StateDB) MVWriteList() []blockstm.WriteDescriptor {
|
func (s *StateDB) MVWriteList() []blockstm.WriteDescriptor {
|
||||||
writes := make([]blockstm.WriteDescriptor, 0, len(s.writeMap))
|
writes := make([]blockstm.WriteDescriptor, 0, len(s.writeMap))
|
||||||
|
|
||||||
|
|
@ -227,6 +231,14 @@ func (s *StateDB) ensureWriteMap() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StateDB) ClearReadMap() {
|
||||||
|
s.readMap = make(map[blockstm.Key]blockstm.ReadDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateDB) ClearWriteMap() {
|
||||||
|
s.writeMap = make(map[blockstm.Key]blockstm.WriteDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StateDB) HadInvalidRead() bool {
|
func (s *StateDB) HadInvalidRead() bool {
|
||||||
return s.dep >= 0
|
return s.dep >= 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,12 +97,51 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
|
||||||
txContext := NewEVMTxContext(msg)
|
txContext := NewEVMTxContext(msg)
|
||||||
evm.Reset(txContext, statedb)
|
evm.Reset(txContext, statedb)
|
||||||
|
|
||||||
// Apply the transaction to the current state (included in the env).
|
var result *ExecutionResult
|
||||||
result, err := ApplyMessage(evm, msg, gp)
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
backupMVHashMap := statedb.GetMVHashmap()
|
||||||
|
|
||||||
|
// pause recording read and write
|
||||||
|
statedb.SetMVHashmap(nil)
|
||||||
|
|
||||||
|
coinbaseBalance := statedb.GetBalance(evm.Context.Coinbase)
|
||||||
|
|
||||||
|
// resume recording read and write
|
||||||
|
statedb.SetMVHashmap(backupMVHashMap)
|
||||||
|
|
||||||
|
result, err = ApplyMessageNoFeeBurnOrTip(evm, msg, gp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stop recording read and write
|
||||||
|
statedb.SetMVHashmap(nil)
|
||||||
|
|
||||||
|
if evm.ChainConfig().IsLondon(blockNumber) {
|
||||||
|
statedb.AddBalance(result.BurntContractAddress, result.FeeBurnt)
|
||||||
|
}
|
||||||
|
|
||||||
|
statedb.AddBalance(evm.Context.Coinbase, result.FeeTipped)
|
||||||
|
output1 := new(big.Int).SetBytes(result.SenderInitBalance.Bytes())
|
||||||
|
output2 := new(big.Int).SetBytes(coinbaseBalance.Bytes())
|
||||||
|
|
||||||
|
// Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559
|
||||||
|
// add transfer log
|
||||||
|
AddFeeTransferLog(
|
||||||
|
statedb,
|
||||||
|
|
||||||
|
msg.From(),
|
||||||
|
evm.Context.Coinbase,
|
||||||
|
|
||||||
|
result.FeeTipped,
|
||||||
|
result.SenderInitBalance,
|
||||||
|
coinbaseBalance,
|
||||||
|
output1.Sub(output1, result.FeeTipped),
|
||||||
|
output2.Add(output2, result.FeeTipped),
|
||||||
|
)
|
||||||
|
|
||||||
// Update the state with pending changes.
|
// Update the state with pending changes.
|
||||||
var root []byte
|
var root []byte
|
||||||
if config.IsByzantium(blockNumber) {
|
if config.IsByzantium(blockNumber) {
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,11 @@ type Header struct {
|
||||||
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
|
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
|
||||||
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
|
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
|
||||||
|
|
||||||
|
// length of TxDependency -> n (n = number of transactions in the block)
|
||||||
|
// length of TxDependency[i] -> 2 + k (k = a whole number)
|
||||||
|
// first 2 element in TxDependency[i] -> transaction index, and flag representing if delay is allowed or not
|
||||||
|
// (0 -> delay is not allowed, 1 -> delay is allowed)
|
||||||
|
// next k elements in TxDependency[i] -> transaction indexes on which transaction i is dependent on
|
||||||
TxDependency [][]uint64 `json:"txDependency" rlp:"optional"`
|
TxDependency [][]uint64 `json:"txDependency" rlp:"optional"`
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
114
miner/worker.go
114
miner/worker.go
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/blockstm"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
|
@ -81,6 +82,10 @@ const (
|
||||||
|
|
||||||
// staleThreshold is the maximum depth of the acceptable stale block.
|
// staleThreshold is the maximum depth of the acceptable stale block.
|
||||||
staleThreshold = 7
|
staleThreshold = 7
|
||||||
|
|
||||||
|
// TODO: will be handled (and made mandatory) in a hardfork event
|
||||||
|
// when true, will get the transaction dependencies for parallel execution, also set in `state_processor.go`
|
||||||
|
EnableMVHashMap = true
|
||||||
)
|
)
|
||||||
|
|
||||||
// environment is the worker's current environment and holds all
|
// environment is the worker's current environment and holds all
|
||||||
|
|
@ -918,7 +923,50 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
|
||||||
}
|
}
|
||||||
var coalescedLogs []*types.Log
|
var coalescedLogs []*types.Log
|
||||||
|
|
||||||
|
var depsMVReadList [][]blockstm.ReadDescriptor
|
||||||
|
|
||||||
|
var depsMVFullWriteList [][]blockstm.WriteDescriptor
|
||||||
|
|
||||||
|
var mvReadMapList []map[blockstm.Key]blockstm.ReadDescriptor
|
||||||
|
|
||||||
|
var deps map[int]map[int]bool
|
||||||
|
|
||||||
|
chDeps := make(chan blockstm.TxDep)
|
||||||
|
|
||||||
|
var count int
|
||||||
|
|
||||||
|
var depsWg sync.WaitGroup
|
||||||
|
|
||||||
|
// create and add empty mvHashMap in statedb
|
||||||
|
if EnableMVHashMap {
|
||||||
|
depsMVReadList = [][]blockstm.ReadDescriptor{}
|
||||||
|
|
||||||
|
depsMVFullWriteList = [][]blockstm.WriteDescriptor{}
|
||||||
|
|
||||||
|
mvReadMapList = []map[blockstm.Key]blockstm.ReadDescriptor{}
|
||||||
|
|
||||||
|
deps = map[int]map[int]bool{}
|
||||||
|
|
||||||
|
chDeps = make(chan blockstm.TxDep)
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
depsWg.Add(1)
|
||||||
|
|
||||||
|
go func(chDeps chan blockstm.TxDep) {
|
||||||
|
for t := range chDeps {
|
||||||
|
deps = blockstm.UpdateDeps(deps, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
depsWg.Done()
|
||||||
|
}(chDeps)
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if EnableMVHashMap {
|
||||||
|
env.state.AddEmptyMVHashMap()
|
||||||
|
}
|
||||||
|
|
||||||
// In the following three cases, we will interrupt the execution of the transaction.
|
// In the following three cases, we will interrupt the execution of the transaction.
|
||||||
// (1) new head block event arrival, the interrupt signal is 1
|
// (1) new head block event arrival, the interrupt signal is 1
|
||||||
// (2) worker start or restart, the interrupt signal is 1
|
// (2) worker start or restart, the interrupt signal is 1
|
||||||
|
|
@ -986,7 +1034,23 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
|
||||||
// Everything ok, collect the logs and shift in the next transaction from the same account
|
// Everything ok, collect the logs and shift in the next transaction from the same account
|
||||||
coalescedLogs = append(coalescedLogs, logs...)
|
coalescedLogs = append(coalescedLogs, logs...)
|
||||||
env.tcount++
|
env.tcount++
|
||||||
txs.Shift()
|
|
||||||
|
if EnableMVHashMap {
|
||||||
|
depsMVReadList = append(depsMVReadList, env.state.MVReadList())
|
||||||
|
depsMVFullWriteList = append(depsMVFullWriteList, env.state.MVFullWriteList())
|
||||||
|
mvReadMapList = append(mvReadMapList, env.state.MVReadMap())
|
||||||
|
|
||||||
|
temp := blockstm.TxDep{
|
||||||
|
Index: env.tcount - 1,
|
||||||
|
ReadList: depsMVReadList[count],
|
||||||
|
FullWriteList: depsMVFullWriteList,
|
||||||
|
}
|
||||||
|
|
||||||
|
chDeps <- temp
|
||||||
|
count++
|
||||||
|
|
||||||
|
txs.Shift()
|
||||||
|
}
|
||||||
|
|
||||||
case errors.Is(err, core.ErrTxTypeNotSupported):
|
case errors.Is(err, core.ErrTxTypeNotSupported):
|
||||||
// Pop the unsupported transaction without shifting in the next from the account
|
// Pop the unsupported transaction without shifting in the next from the account
|
||||||
|
|
@ -999,6 +1063,54 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
|
||||||
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
||||||
txs.Shift()
|
txs.Shift()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if EnableMVHashMap {
|
||||||
|
env.state.ClearReadMap()
|
||||||
|
env.state.ClearWriteMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:nestif
|
||||||
|
if EnableMVHashMap {
|
||||||
|
close(chDeps)
|
||||||
|
depsWg.Wait()
|
||||||
|
|
||||||
|
if len(mvReadMapList) > 0 {
|
||||||
|
tempDeps := make([][]uint64, len(mvReadMapList))
|
||||||
|
|
||||||
|
// adding for txIdx = 0
|
||||||
|
tempDeps[0] = []uint64{uint64(0)}
|
||||||
|
tempDeps[0] = append(tempDeps[0], 1)
|
||||||
|
|
||||||
|
for j := range deps[0] {
|
||||||
|
tempDeps[0] = append(tempDeps[0], uint64(j))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i <= len(mvReadMapList)-1; i++ {
|
||||||
|
tempDeps[i] = []uint64{uint64(i)}
|
||||||
|
|
||||||
|
reads := mvReadMapList[i-1]
|
||||||
|
|
||||||
|
_, ok1 := reads[blockstm.NewSubpathKey(env.coinbase, state.BalancePath)]
|
||||||
|
_, ok2 := reads[blockstm.NewSubpathKey(common.HexToAddress(w.chainConfig.Bor.CalculateBurntContract(env.header.Number.Uint64())), state.BalancePath)]
|
||||||
|
|
||||||
|
if ok1 || ok2 {
|
||||||
|
// 0 -> delay is not allowed
|
||||||
|
tempDeps[i] = append(tempDeps[i], 0)
|
||||||
|
} else {
|
||||||
|
// 1 -> delay is allowed
|
||||||
|
tempDeps[i] = append(tempDeps[i], 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := range deps[i] {
|
||||||
|
tempDeps[i] = append(tempDeps[i], uint64(j))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env.header.TxDependency = tempDeps
|
||||||
|
} else {
|
||||||
|
env.header.TxDependency = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !w.isRunning() && len(coalescedLogs) > 0 {
|
if !w.isRunning() && len(coalescedLogs) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -714,3 +714,115 @@ func BenchmarkBorMining(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// uses core.NewParallelBlockChain to use the dependencies present in the block header
|
||||||
|
func BenchmarkBorMiningBlockSTMMetadata(b *testing.B) {
|
||||||
|
chainConfig := params.BorUnittestChainConfig
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(b)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
ethAPIMock := api.NewMockCaller(ctrl)
|
||||||
|
ethAPIMock.EXPECT().Call(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
|
||||||
|
|
||||||
|
spanner := bor.NewMockSpanner(ctrl)
|
||||||
|
spanner.EXPECT().GetCurrentValidators(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*valset.Validator{
|
||||||
|
{
|
||||||
|
ID: 0,
|
||||||
|
Address: TestBankAddress,
|
||||||
|
VotingPower: 100,
|
||||||
|
ProposerPriority: 0,
|
||||||
|
},
|
||||||
|
}, nil).AnyTimes()
|
||||||
|
|
||||||
|
heimdallClientMock := mocks.NewMockIHeimdallClient(ctrl)
|
||||||
|
heimdallClientMock.EXPECT().Close().Times(1)
|
||||||
|
|
||||||
|
contractMock := bor.NewMockGenesisContract(ctrl)
|
||||||
|
|
||||||
|
db, _, _ := NewDBForFakes(b)
|
||||||
|
|
||||||
|
engine := NewFakeBor(b, db, chainConfig, ethAPIMock, spanner, heimdallClientMock, contractMock)
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
chainConfig.LondonBlock = big.NewInt(0)
|
||||||
|
|
||||||
|
w, back, _ := NewTestWorker(b, chainConfig, engine, db, 0)
|
||||||
|
defer w.close()
|
||||||
|
|
||||||
|
// This test chain imports the mined blocks.
|
||||||
|
db2 := rawdb.NewMemoryDatabase()
|
||||||
|
back.Genesis.MustCommit(db2)
|
||||||
|
|
||||||
|
chain, _ := core.NewParallelBlockChain(db2, nil, back.chain.Config(), engine, vm.Config{}, nil, nil, nil)
|
||||||
|
defer chain.Stop()
|
||||||
|
|
||||||
|
// Ignore empty commit here for less noise.
|
||||||
|
w.skipSealHook = func(task *task) bool {
|
||||||
|
return len(task.receipts) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// fulfill tx pool
|
||||||
|
const (
|
||||||
|
totalGas = testGas + params.TxGas
|
||||||
|
totalBlocks = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
txInBlock := int(back.Genesis.GasLimit/totalGas) + 1
|
||||||
|
|
||||||
|
// a bit risky
|
||||||
|
for i := 0; i < 2*totalBlocks*txInBlock; i++ {
|
||||||
|
err = back.txPool.AddLocal(back.newRandomTx(true))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal("while adding a local transaction", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = back.txPool.AddLocal(back.newRandomTx(false))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal("while adding a remote transaction", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for mined blocks.
|
||||||
|
sub := w.mux.Subscribe(core.NewMinedBlockEvent{})
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
prev := uint64(time.Now().Unix())
|
||||||
|
|
||||||
|
// Start mining!
|
||||||
|
w.start()
|
||||||
|
|
||||||
|
blockPeriod, ok := back.Genesis.Config.Bor.Period["0"]
|
||||||
|
if !ok {
|
||||||
|
blockPeriod = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < totalBlocks; i++ {
|
||||||
|
select {
|
||||||
|
case ev := <-sub.Chan():
|
||||||
|
block := ev.Data.(core.NewMinedBlockEvent).Block
|
||||||
|
|
||||||
|
if _, err := chain.InsertChain([]*types.Block{block}); err != nil {
|
||||||
|
b.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for dependencies
|
||||||
|
deps := block.TxDependency()
|
||||||
|
for i := 1; i < block.Transactions().Len(); i++ {
|
||||||
|
if deps[i][0] != uint64(i) || deps[i][1] != uint64(0) || deps[i][2] != uint64(i-1) || len(deps[i]) != 3 {
|
||||||
|
b.Fatalf("wrong dependency")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Log("block", block.NumberU64(), "time", block.Time()-prev, "txs", block.Transactions().Len(), "gasUsed", block.GasUsed(), "gasLimit", block.GasLimit())
|
||||||
|
|
||||||
|
prev = block.Time()
|
||||||
|
case <-time.After(time.Duration(blockPeriod) * time.Second):
|
||||||
|
b.Fatalf("timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue