Remove dependency when a transaction is reverted

If a transaction is reverted, its write records should be excluded from
dependency calculation.
This commit is contained in:
Jerry 2023-05-11 09:09:01 -07:00
parent c39c66fb9c
commit f2c48fed0b
No known key found for this signature in database
GPG key ID: 5B33FA23CB103211
6 changed files with 58 additions and 81 deletions

View file

@ -556,11 +556,14 @@ func (pe *ParallelExecutor) Step(res *ExecResult) (result ParallelExecutionResul
var allDeps map[int]map[int]bool var allDeps map[int]map[int]bool
var deps DAG
if pe.profile { if pe.profile {
allDeps = GetDep(*pe.lastTxIO) allDeps = GetDep(*pe.lastTxIO)
deps = BuildDAG(*pe.lastTxIO)
} }
return ParallelExecutionResult{pe.lastTxIO, &pe.stats, nil, allDeps}, err return ParallelExecutionResult{pe.lastTxIO, &pe.stats, &deps, allDeps}, err
} }
// Send the next immediate pending transaction to be executed // Send the next immediate pending transaction to be executed

View file

@ -269,6 +269,13 @@ func ValidateVersion(txIdx int, lastInputOutput *TxnInputOutput, versionedData *
mvResult := versionedData.Read(rd.Path, txIdx) mvResult := versionedData.Read(rd.Path, txIdx)
switch mvResult.Status() { switch mvResult.Status() {
case MVReadResultDone: case MVReadResultDone:
// Having a write record for a path in MVHashmap doesn't necessarily mean there is a conflict, because MVHashmap
// is a superset of the actual write set.
// Check if the write record is actually in write set. If not, skip the key.
if mvResult.depIdx >= 0 && !lastInputOutput.HasWritten(mvResult.depIdx, rd.Path) {
continue
}
valid = rd.Kind == ReadKindMap && rd.V == Version{ valid = rd.Kind == ReadKindMap && rd.V == Version{
TxnIndex: mvResult.depIdx, TxnIndex: mvResult.depIdx,
Incarnation: mvResult.incarnation, Incarnation: mvResult.incarnation,

View file

@ -46,6 +46,7 @@ func (txo TxnOutput) hasNewWrite(cmpSet []WriteDescriptor) bool {
type TxnInputOutput struct { type TxnInputOutput struct {
inputs []TxnInput inputs []TxnInput
outputs []TxnOutput // write sets that should be checked during validation outputs []TxnOutput // write sets that should be checked during validation
outputsSet []map[Key]struct{}
allOutputs []TxnOutput // entire write sets in MVHashMap. allOutputs should always be a parent set of outputs allOutputs []TxnOutput // entire write sets in MVHashMap. allOutputs should always be a parent set of outputs
} }
@ -61,10 +62,16 @@ func (io *TxnInputOutput) AllWriteSet(txnIdx int) []WriteDescriptor {
return io.allOutputs[txnIdx] return io.allOutputs[txnIdx]
} }
func (io *TxnInputOutput) HasWritten(txnIdx int, k Key) bool {
_, ok := io.outputsSet[txnIdx][k]
return ok
}
func MakeTxnInputOutput(numTx int) *TxnInputOutput { func MakeTxnInputOutput(numTx int) *TxnInputOutput {
return &TxnInputOutput{ return &TxnInputOutput{
inputs: make([]TxnInput, numTx), inputs: make([]TxnInput, numTx),
outputs: make([]TxnOutput, numTx), outputs: make([]TxnOutput, numTx),
outputsSet: make([]map[Key]struct{}, numTx),
allOutputs: make([]TxnOutput, numTx), allOutputs: make([]TxnOutput, numTx),
} }
} }
@ -75,6 +82,11 @@ func (io *TxnInputOutput) recordRead(txId int, input []ReadDescriptor) {
func (io *TxnInputOutput) recordWrite(txId int, output []WriteDescriptor) { func (io *TxnInputOutput) recordWrite(txId int, output []WriteDescriptor) {
io.outputs[txId] = output io.outputs[txId] = output
io.outputsSet[txId] = make(map[Key]struct{}, len(output))
for _, v := range output {
io.outputsSet[txId][v.Path] = struct{}{}
}
} }
func (io *TxnInputOutput) recordAllWrite(txId int, output []WriteDescriptor) { func (io *TxnInputOutput) recordAllWrite(txId int, output []WriteDescriptor) {

View file

@ -178,7 +178,7 @@ func (task *ExecutionTask) Settle() {
coinbaseBalance := task.finalStateDB.GetBalance(task.coinbase) coinbaseBalance := task.finalStateDB.GetBalance(task.coinbase)
task.finalStateDB.ApplyMVWriteSet(task.statedb.MVWriteList()) task.finalStateDB.ApplyMVWriteSet(task.statedb.MVFullWriteList())
for _, l := range task.statedb.GetLogs(task.tx.Hash(), task.blockHash) { for _, l := range task.statedb.GetLogs(task.tx.Hash(), task.blockHash) {
task.finalStateDB.AddLog(l) task.finalStateDB.AddLog(l)
@ -290,11 +290,6 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
if block.Header().TxDependency != nil { if block.Header().TxDependency != nil {
metadata = true metadata = true
if !VerifyDeps(deps) {
metadata = false
deps = GetDeps([][]uint64{})
}
} }
blockContext := NewEVMBlockContext(header, p.bc, nil) blockContext := NewEVMBlockContext(header, p.bc, nil)
@ -369,9 +364,9 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
backupStateDB := statedb.Copy() backupStateDB := statedb.Copy()
profile := false profile := false
result, err := blockstm.ExecuteParallel(tasks, false, metadata, interruptCtx) result, err := blockstm.ExecuteParallel(tasks, profile, metadata, interruptCtx)
if err == nil && profile { if err == nil && profile && result.Deps != nil {
_, weight := result.Deps.LongestPath(*result.Stats) _, weight := result.Deps.LongestPath(*result.Stats)
serialWeight := uint64(0) serialWeight := uint64(0)
@ -381,10 +376,6 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
} }
parallelizabilityTimer.Update(time.Duration(serialWeight * 100 / weight)) parallelizabilityTimer.Update(time.Duration(serialWeight * 100 / weight))
log.Info("Parallelizability", "Average (%)", parallelizabilityTimer.Mean())
log.Info("Parallelizability", "Histogram (%)", parallelizabilityTimer.Percentiles([]float64{0.001, 0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99, 0.999, 0.9999}))
} }
for _, task := range tasks { for _, task := range tasks {
@ -436,20 +427,3 @@ func GetDeps(txDependency [][]uint64) map[int][]int {
return deps return deps
} }
// returns true if dependencies are correct
func VerifyDeps(deps map[int][]int) bool {
// number of transactions in the block
n := len(deps)
// Handle out-of-range and circular dependency problem
for tx, val := range deps {
for depTx := range val {
if depTx >= n || depTx < tx {
return false
}
}
}
return true
}

View file

@ -144,7 +144,7 @@ type (
func (ch createObjectChange) revert(s *StateDB) { func (ch createObjectChange) revert(s *StateDB) {
delete(s.stateObjects, *ch.account) delete(s.stateObjects, *ch.account)
delete(s.stateObjectsDirty, *ch.account) delete(s.stateObjectsDirty, *ch.account)
MVWrite(s, blockstm.NewAddressKey(*ch.account)) RevertWrite(s, blockstm.NewAddressKey(*ch.account))
} }
func (ch createObjectChange) dirtied() *common.Address { func (ch createObjectChange) dirtied() *common.Address {
@ -153,7 +153,7 @@ func (ch createObjectChange) dirtied() *common.Address {
func (ch resetObjectChange) revert(s *StateDB) { func (ch resetObjectChange) revert(s *StateDB) {
s.setStateObject(ch.prev) s.setStateObject(ch.prev)
MVWrite(s, blockstm.NewAddressKey(ch.prev.address)) RevertWrite(s, blockstm.NewAddressKey(ch.prev.address))
if !ch.prevdestruct && s.snap != nil { if !ch.prevdestruct && s.snap != nil {
delete(s.snapDestructs, ch.prev.addrHash) delete(s.snapDestructs, ch.prev.addrHash)
} }
@ -168,8 +168,8 @@ func (ch suicideChange) revert(s *StateDB) {
if obj != nil { if obj != nil {
obj.suicided = ch.prev obj.suicided = ch.prev
obj.setBalance(ch.prevbalance) obj.setBalance(ch.prevbalance)
MVWrite(s, blockstm.NewSubpathKey(*ch.account, SuicidePath)) RevertWrite(s, blockstm.NewSubpathKey(*ch.account, SuicidePath))
MVWrite(s, blockstm.NewSubpathKey(*ch.account, BalancePath)) RevertWrite(s, blockstm.NewSubpathKey(*ch.account, BalancePath))
} }
} }
@ -188,7 +188,7 @@ func (ch touchChange) dirtied() *common.Address {
func (ch balanceChange) revert(s *StateDB) { func (ch balanceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setBalance(ch.prev) s.getStateObject(*ch.account).setBalance(ch.prev)
MVWrite(s, blockstm.NewSubpathKey(*ch.account, BalancePath)) RevertWrite(s, blockstm.NewSubpathKey(*ch.account, BalancePath))
} }
func (ch balanceChange) dirtied() *common.Address { func (ch balanceChange) dirtied() *common.Address {
@ -197,7 +197,7 @@ func (ch balanceChange) dirtied() *common.Address {
func (ch nonceChange) revert(s *StateDB) { func (ch nonceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setNonce(ch.prev) s.getStateObject(*ch.account).setNonce(ch.prev)
MVWrite(s, blockstm.NewSubpathKey(*ch.account, NoncePath)) RevertWrite(s, blockstm.NewSubpathKey(*ch.account, NoncePath))
} }
func (ch nonceChange) dirtied() *common.Address { func (ch nonceChange) dirtied() *common.Address {
@ -206,7 +206,7 @@ func (ch nonceChange) dirtied() *common.Address {
func (ch codeChange) revert(s *StateDB) { func (ch codeChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
MVWrite(s, blockstm.NewSubpathKey(*ch.account, CodePath)) RevertWrite(s, blockstm.NewSubpathKey(*ch.account, CodePath))
} }
func (ch codeChange) dirtied() *common.Address { func (ch codeChange) dirtied() *common.Address {
@ -215,7 +215,7 @@ func (ch codeChange) dirtied() *common.Address {
func (ch storageChange) revert(s *StateDB) { func (ch storageChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
MVWrite(s, blockstm.NewStateKey(*ch.account, ch.key)) RevertWrite(s, blockstm.NewStateKey(*ch.account, ch.key))
} }
func (ch storageChange) dirtied() *common.Address { func (ch storageChange) dirtied() *common.Address {

View file

@ -81,12 +81,12 @@ type StateDB struct {
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
// Block-stm related fields // Block-stm related fields
mvHashmap *blockstm.MVHashMap mvHashmap *blockstm.MVHashMap
incarnation int incarnation int
readMap map[blockstm.Key]blockstm.ReadDescriptor readMap map[blockstm.Key]blockstm.ReadDescriptor
writeMap map[blockstm.Key]blockstm.WriteDescriptor writeMap map[blockstm.Key]blockstm.WriteDescriptor
newStateObjects map[common.Address]struct{} revertedKeys map[blockstm.Key]struct{}
dep int dep int
// DB error. // DB error.
// State objects are used by the consensus core and VM which are // State objects are used by the consensus core and VM which are
@ -147,7 +147,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
stateObjects: make(map[common.Address]*stateObject), stateObjects: make(map[common.Address]*stateObject),
stateObjectsPending: make(map[common.Address]struct{}), stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}), stateObjectsDirty: make(map[common.Address]struct{}),
newStateObjects: make(map[common.Address]struct{}), revertedKeys: make(map[blockstm.Key]struct{}),
logs: make(map[common.Hash][]*types.Log), logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte), preimages: make(map[common.Hash][]byte),
journal: newJournal(), journal: newJournal(),
@ -187,7 +187,7 @@ func (s *StateDB) MVWriteList() []blockstm.WriteDescriptor {
writes := make([]blockstm.WriteDescriptor, 0, len(s.writeMap)) writes := make([]blockstm.WriteDescriptor, 0, len(s.writeMap))
for _, v := range s.writeMap { for _, v := range s.writeMap {
if !v.Path.IsAddress() { if _, ok := s.revertedKeys[v.Path]; !ok {
writes = append(writes, v) writes = append(writes, v)
} }
} }
@ -212,7 +212,7 @@ func (s *StateDB) MVReadMap() map[blockstm.Key]blockstm.ReadDescriptor {
func (s *StateDB) MVReadList() []blockstm.ReadDescriptor { func (s *StateDB) MVReadList() []blockstm.ReadDescriptor {
reads := make([]blockstm.ReadDescriptor, 0, len(s.readMap)) reads := make([]blockstm.ReadDescriptor, 0, len(s.readMap))
for _, v := range s.readMap { for _, v := range s.MVReadMap() {
reads = append(reads, v) reads = append(reads, v)
} }
@ -268,6 +268,14 @@ func MVRead[T any](s *StateDB, k blockstm.Key, defaultV T, readStorage func(s *S
} }
} }
if !k.IsAddress() {
// If we are reading subpath from a deleted account, return default value instead of reading from MVHashmap
addr := k.GetAddress()
if s.getStateObject(addr) == nil {
return defaultV
}
}
res := s.mvHashmap.Read(k, s.txIndex) res := s.mvHashmap.Read(k, s.txIndex)
var rd blockstm.ReadDescriptor var rd blockstm.ReadDescriptor
@ -320,6 +328,10 @@ func MVWrite(s *StateDB, k blockstm.Key) {
} }
} }
func RevertWrite(s *StateDB, k blockstm.Key) {
s.revertedKeys[k] = struct{}{}
}
func MVWritten(s *StateDB, k blockstm.Key) bool { func MVWritten(s *StateDB, k blockstm.Key) bool {
if s.mvHashmap == nil || s.writeMap == nil { if s.mvHashmap == nil || s.writeMap == nil {
return false return false
@ -349,6 +361,8 @@ func (sw *StateDB) ApplyMVWriteSet(writes []blockstm.WriteDescriptor) {
stateKey := path.GetStateKey() stateKey := path.GetStateKey()
state := sr.GetState(addr, stateKey) state := sr.GetState(addr, stateKey)
sw.SetState(addr, stateKey, state) sw.SetState(addr, stateKey, state)
} else if path.IsAddress() {
continue
} else { } else {
addr := path.GetAddress() addr := path.GetAddress()
switch path.GetSubpath() { switch path.GetSubpath() {
@ -543,10 +557,6 @@ const SuicidePath = 4
// GetBalance retrieves the balance from the given address or 0 if object not found // GetBalance retrieves the balance from the given address or 0 if object not found
func (s *StateDB) GetBalance(addr common.Address) *big.Int { func (s *StateDB) GetBalance(addr common.Address) *big.Int {
if s.getStateObject(addr) == nil {
return common.Big0
}
return MVRead(s, blockstm.NewSubpathKey(addr, BalancePath), common.Big0, func(s *StateDB) *big.Int { return MVRead(s, blockstm.NewSubpathKey(addr, BalancePath), common.Big0, func(s *StateDB) *big.Int {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
@ -558,10 +568,6 @@ func (s *StateDB) GetBalance(addr common.Address) *big.Int {
} }
func (s *StateDB) GetNonce(addr common.Address) uint64 { func (s *StateDB) GetNonce(addr common.Address) uint64 {
if s.getStateObject(addr) == nil {
return 0
}
return MVRead(s, blockstm.NewSubpathKey(addr, NoncePath), 0, func(s *StateDB) uint64 { return MVRead(s, blockstm.NewSubpathKey(addr, NoncePath), 0, func(s *StateDB) uint64 {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
@ -585,10 +591,6 @@ func (s *StateDB) Version() blockstm.Version {
} }
func (s *StateDB) GetCode(addr common.Address) []byte { func (s *StateDB) GetCode(addr common.Address) []byte {
if s.getStateObject(addr) == nil {
return nil
}
return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), nil, func(s *StateDB) []byte { return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), nil, func(s *StateDB) []byte {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
@ -599,10 +601,6 @@ func (s *StateDB) GetCode(addr common.Address) []byte {
} }
func (s *StateDB) GetCodeSize(addr common.Address) int { func (s *StateDB) GetCodeSize(addr common.Address) int {
if s.getStateObject(addr) == nil {
return 0
}
return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), 0, func(s *StateDB) int { return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), 0, func(s *StateDB) int {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
@ -613,10 +611,6 @@ func (s *StateDB) GetCodeSize(addr common.Address) int {
} }
func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
if s.getStateObject(addr) == nil {
return common.Hash{}
}
return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), common.Hash{}, func(s *StateDB) common.Hash { return MVRead(s, blockstm.NewSubpathKey(addr, CodePath), common.Hash{}, func(s *StateDB) common.Hash {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject == nil { if stateObject == nil {
@ -628,10 +622,6 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
// GetState retrieves a value from the given account's storage trie. // GetState retrieves a value from the given account's storage trie.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
if s.getStateObject(addr) == nil {
return common.Hash{}
}
return MVRead(s, blockstm.NewStateKey(addr, hash), common.Hash{}, func(s *StateDB) common.Hash { return MVRead(s, blockstm.NewStateKey(addr, hash), common.Hash{}, func(s *StateDB) common.Hash {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
@ -666,10 +656,6 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte,
// GetCommittedState retrieves a value from the given account's committed storage trie. // GetCommittedState retrieves a value from the given account's committed storage trie.
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
if s.getStateObject(addr) == nil {
return common.Hash{}
}
return MVRead(s, blockstm.NewStateKey(addr, hash), common.Hash{}, func(s *StateDB) common.Hash { return MVRead(s, blockstm.NewStateKey(addr, hash), common.Hash{}, func(s *StateDB) common.Hash {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
@ -697,10 +683,6 @@ func (s *StateDB) StorageTrie(addr common.Address) Trie {
} }
func (s *StateDB) HasSuicided(addr common.Address) bool { func (s *StateDB) HasSuicided(addr common.Address) bool {
if s.getStateObject(addr) == nil {
return false
}
return MVRead(s, blockstm.NewSubpathKey(addr, SuicidePath), false, func(s *StateDB) bool { return MVRead(s, blockstm.NewSubpathKey(addr, SuicidePath), false, func(s *StateDB) bool {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
@ -982,7 +964,6 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct})
} }
s.setStateObject(newobj) s.setStateObject(newobj)
s.newStateObjects[addr] = struct{}{}
MVWrite(s, blockstm.NewAddressKey(addr)) MVWrite(s, blockstm.NewAddressKey(addr))
if prev != nil && !prev.deleted { if prev != nil && !prev.deleted {
@ -1048,7 +1029,7 @@ func (s *StateDB) Copy() *StateDB {
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
newStateObjects: make(map[common.Address]struct{}, len(s.newStateObjects)), revertedKeys: make(map[blockstm.Key]struct{}),
refund: s.refund, refund: s.refund,
logs: make(map[common.Hash][]*types.Log, len(s.logs)), logs: make(map[common.Hash][]*types.Log, len(s.logs)),
logSize: s.logSize, logSize: s.logSize,