Repository: iotexproject/iotex-core
Pull request #4178 (2024-03-12 06:27:23)

[evm] panic on unrecoverable error
# Description
1. revert to the same snapshot version again should cause a panic(). make it the same behavior as in ethereum
2. panic on unrecoverable errors (like store state/account fail)

Fixes #(issue)

## Type of change
Please delete options that are not relevant.
- [x] Bug fix (non-breaking change which fixes an issue)
- [] New feature (non-breaking change which adds functionality)
- [] Code refactor or improvement
- [] Breaking change (fix or feature that would cause a new or changed behavior of existing functionality)
- [] This change requires a documentation update

# How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
- [] make test
- [] fullsync
- [] Other test (please specify)

**Test Configuration**:
- Firmware version:
- Hardware:
- Toolchain:
- SDK:

# Checklist:
- [] My code follows the style guidelines of this project
- [] I have performed a self-review of my code
- [] I have commented my code, particularly in hard-to-understand areas
- [] I have made corresponding changes to the documentation
- [] My changes generate no new warnings
- [] I have added tests that prove my fix is effective or that my feature works
- [] New and existing unit tests pass locally with my changes
- [] Any dependent changes have been merged and published in downstream modules


sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4178) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [5.5% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4178&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4178)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4178?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject) Report
Attention: Patch coverage is `81.81818%` with `2 lines` in your changes are missing coverage. Please review.
> Project coverage is 76.21%. Comparing base [(`e1f0636`)](https://app.codecov.io/gh/iotexproject/iotex-core/commit/e1f0636b27ca54fa57da8cadb3034431143833fa?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject) to head [(`6e1f529`)](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4178?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject).
> Report is 199 commits behind head on master.

| [Files](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4178?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject) | Patch % | Lines |
|---|---|---|
| [action/protocol/execution/evm/evm.go](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4178?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject#diff-YWN0aW9uL3Byb3RvY29sL2V4ZWN1dGlvbi9ldm0vZXZtLmdv) | 0.00% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4178?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #4178      +/-   ##
==========================================
+ Coverage   75.38%   76.21%   +0.83%     
==========================================
  Files         303      340      +37     
  Lines       25923    29133    +3210     
==========================================
+ Hits        19541    22203    +2662     
- Misses       5360     5830     +470     
- Partials     1022     1100      +78     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4178?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject).

sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4178) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [7.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4178&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4178)


sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4178) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [7.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4178&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4178)



Patch:
diff --git a/action/protocol/context.go b/action/protocol/context.go
index 26c1bb0eb..8cedeec6d 100644
--- a/action/protocol/context.go
+++ b/action/protocol/context.go
@@ -120,6 +120,7 @@ type (
 		DisableDelegateEndorsement              bool
 		RefactorFreshAccountConversion          bool
 		SuicideTxLogMismatchPanic               bool
+		PanicUnrecoverableError                 bool
 	}
 
 	// FeatureWithHeightCtx provides feature check functions.
@@ -265,6 +266,7 @@ func WithFeatureCtx(ctx context.Context) context.Context {
 			DisableDelegateEndorsement:              !g.IsTsunami(height),
 			RefactorFreshAccountConversion:          g.IsTsunami(height),
 			SuicideTxLogMismatchPanic:               g.IsToBeEnabled(height),
+			PanicUnrecoverableError:                 g.IsToBeEnabled(height),
 		},
 	)
 }
diff --git a/action/protocol/execution/evm/evm.go b/action/protocol/execution/evm/evm.go
index fca42d025..275d9202d 100644
--- a/action/protocol/execution/evm/evm.go
+++ b/action/protocol/execution/evm/evm.go
@@ -411,6 +411,9 @@ func prepareStateDB(ctx context.Context, sm protocol.StateManager) (*StateDBAdap
 	if featureCtx.SuicideTxLogMismatchPanic {
 		opts = append(opts, SuicideTxLogMismatchPanicOption())
 	}
+	if featureCtx.PanicUnrecoverableError {
+		opts = append(opts, PanicUnrecoverableErrorOption())
+	}
 
 	return NewStateDBAdapter(
 		sm,
diff --git a/action/protocol/execution/evm/evmstatedbadapter.go b/action/protocol/execution/evm/evmstatedbadapter.go
index 37709d5e0..2a86218cb 100644
--- a/action/protocol/execution/evm/evmstatedbadapter.go
+++ b/action/protocol/execution/evm/evmstatedbadapter.go
@@ -73,6 +73,7 @@ type (
 		manualCorrectGasRefund     bool
 		suicideTxLogMismatchPanic  bool
 		zeroNonceForFreshAccount   bool
+		panicUnrecoverableError    bool
 	}
 )
 
@@ -162,6 +163,14 @@ func ZeroNonceForFreshAccountOption() StateDBAdapterOption {
 	}
 }
 
+// PanicUnrecoverableErrorOption set panicUnrecoverableError as true
+func PanicUnrecoverableErrorOption() StateDBAdapterOption {
+	return func(adapter *StateDBAdapter) error {
+		adapter.panicUnrecoverableError = true
+		return nil
+	}
+}
+
 // NewStateDBAdapter creates a new state db with iotex blockchain
 func NewStateDBAdapter(
 	sm protocol.StateManager,
@@ -206,6 +215,18 @@ func (stateDB *StateDBAdapter) logError(err error) {
 	}
 }
 
+func (stateDB *StateDBAdapter) assertError(err error, msg string, fields ...zap.Field) bool {
+	if err == nil {
+		return false
+	}
+	if stateDB.panicUnrecoverableError {
+		log.L().Panic(msg, fields...)
+	}
+	log.L().Error(msg, fields...)
+	stateDB.logError(err)
+	return true
+}
+
 // Error returns the first stored error during evm contract execution
 func (stateDB *StateDBAdapter) Error() error {
 	return stateDB.err
@@ -221,14 +242,11 @@ func (stateDB *StateDBAdapter) accountCreationOpts() []state.AccountCreationOpti
 // CreateAccount creates an account in iotx blockchain
 func (stateDB *StateDBAdapter) CreateAccount(evmAddr common.Address) {
 	addr, err := address.FromBytes(evmAddr.Bytes())
-	if err != nil {
-		log.L().Error("Failed to convert evm address.", zap.Error(err))
+	if stateDB.assertError(err, "Failed to convert evm address.", zap.Error(err)) {
 		return
 	}
 	_, err = accountutil.LoadOrCreateAccount(stateDB.sm, addr, stateDB.accountCreationOpts()...)
-	if err != nil {
-		log.L().Error("Failed to create account.", zap.Error(err))
-		stateDB.logError(err)
+	if stateDB.assertError(err, "Failed to create account.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 		return
 	}
 	log.L().Debug("Called CreateAccount.", log.Hex("addrHash", evmAddr[:]))
@@ -243,24 +261,20 @@ func (stateDB *StateDBAdapter) SubBalance(evmAddr common.Address, a256 *uint256.
 	// stateDB.GetBalance(evmAddr)
 	log.L().Debug(fmt.Sprintf("SubBalance %v from %s", amount, evmAddr.Hex()))
 	addr, err := address.FromBytes(evmAddr.Bytes())
-	if err != nil {
-		log.L().Error("Failed to convert evm address.", zap.Error(err))
+	if stateDB.assertError(err, "Failed to convert evm address.", zap.Error(err)) {
 		return
 	}
 	state, err := stateDB.accountState(evmAddr)
-	if err != nil {
-		log.L().Error("Failed to sub balance.", zap.Error(err))
-		stateDB.logError(err)
+	if stateDB.assertError(err, "Failed to get account.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 		return
 	}
-	if err := state.SubBalance(amount); err != nil {
-		log.L().Error("Failed to sub balance.", zap.Error(err))
-		stateDB.logError(err)
+	err = state.SubBalance(amount)
+	if stateDB.assertError(err, "Failed to sub balance.", zap.Error(err), zap.String("amount", amount.String())) {
 		return
 	}
-	if err := accountutil.StoreAccount(stateDB.sm, addr, state); err != nil {
-		log.L().Error("Failed to update pending account changes to trie.", zap.Error(err))
-		stateDB.logError(err)
+	err = accountutil.StoreAccount(stateDB.sm, addr, state)
+	if stateDB.assertError(err, "Failed to store account.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
+		return
 	}
 }
 
@@ -275,8 +289,7 @@ func (stateDB *StateDBAdapter) AddBalance(evmAddr common.Address, a256 *uint256.
 	log.L().Debug(fmt.Sprintf("AddBalance %v to %s", amount, evmAddr.Hex()))
 
 	addr, err := address.FromBytes(evmAddr.Bytes())
-	if err != nil {
-		log.L().Error("Failed to convert evm address.", zap.Error(err))
+	if stateDB.assertError(err, "Failed to convert evm address.", zap.Error(err)) {
 		return
 	}
 	var (
@@ -287,20 +300,17 @@ func (stateDB *StateDBAdapter) AddBalance(evmAddr common.Address, a256 *uint256.
 		state = contract.SelfState()
 	} else {
 		state, err = accountutil.LoadOrCreateAccount(stateDB.sm, addr, stateDB.accountCreationOpts()...)
-		if err != nil {
-			log.L().Error("Failed to add balance.", log.Hex("addrHash", evmAddr[:]))
-			stateDB.logError(err)
+		if stateDB.assertError(err, "Failed to get account.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 			return
 		}
 	}
-	if err := state.AddBalance(amount); err != nil {
-		log.L().Error("failed to add balance", zap.Error(err), zap.String("amount", amount.String()))
-		stateDB.logError(err)
+	err = state.AddBalance(amount)
+	if stateDB.assertError(err, "Failed to add balance.", zap.Error(err), zap.String("amount", amount.String())) {
 		return
 	}
-	if err := accountutil.StoreAccount(stateDB.sm, addr, state); err != nil {
-		log.L().Error("Failed to update pending account changes to trie.", zap.Error(err))
-		stateDB.logError(err)
+	err = accountutil.StoreAccount(stateDB.sm, addr, state)
+	if stateDB.assertError(err, "Failed to store account.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
+		return
 	} else {
 		// keep a record of latest add balance
 		stateDB.lastAddBalanceAddr = addr.String()
@@ -311,8 +321,7 @@ func (stateDB *StateDBAdapter) AddBalance(evmAddr common.Address, a256 *uint256.
 // GetBalance gets the balance of account
 func (stateDB *StateDBAdapter) GetBalance(evmAddr common.Address) *uint256.Int {
 	state, err := stateDB.accountState(evmAddr)
-	if err != nil {
-		log.L().Error("Failed to get balance.", zap.Error(err))
+	if stateDB.assertError(err, "Failed to get balance.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 		return common.U2560
 	}
 	log.L().Debug(fmt.Sprintf("Balance of %s is %v", evmAddr.Hex(), state.Balance))
@@ -323,8 +332,7 @@ func (stateDB *StateDBAdapter) GetBalance(evmAddr common.Address) *uint256.Int {
 // IsNewAccount returns true if this is a new account
 func (stateDB *StateDBAdapter) IsNewAccount(evmAddr common.Address) bool {
 	state, err := stateDB.accountState(evmAddr)
-	if err != nil {
-		log.L().Error("failed to load account.", zap.Error(err), zap.String("address", evmAddr.Hex()))
+	if stateDB.assertError(err, "Failed to get account.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 		return false
 	}
 
@@ -341,8 +349,12 @@ func (stateDB *StateDBAdapter) GetNonce(evmAddr common.Address) uint64 {
 	}
 	state, err := stateDB.accountState(evmAddr)
 	if err != nil {
-		log.L().Error("Failed to get nonce.", zap.Error(err))
-		// stateDB.logError(err)
+		if stateDB.panicUnrecoverableError {
+			log.L().Panic("Failed to get nonce.", zap.Error(err))
+		} else {
+			log.L().Error("Failed to get nonce.", zap.Error(err))
+			stateDB.logError(err)
+		}
 	} else {
 		if stateDB.zeroNonceForFreshAccount {
 			pendingNonce = state.PendingNonceConsideringFreshAccount()
@@ -366,14 +378,11 @@ func (stateDB *StateDBAdapter) GetNonce(evmAddr common.Address) uint64 {
 // SetNonce sets the nonce of account
 func (stateDB *StateDBAdapter) SetNonce(evmAddr common.Address, nonce uint64) {
 	addr, err := address.FromBytes(evmAddr.Bytes())
-	if err != nil {
-		log.L().Error("Failed to convert evm address.", zap.Error(err))
+	if stateDB.assertError(err, "Failed to convert evm address.", zap.Error(err)) {
 		return
 	}
 	s, err := stateDB.accountState(evmAddr)
-	if err != nil {
-		log.L().Error("Failed to set nonce.", zap.Error(err))
-		// stateDB.logError(err)
+	if stateDB.assertError(err, "Failed to get account.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 		return
 	}
 	if !stateDB.useConfirmedNonce {
@@ -391,10 +400,8 @@ func (stateDB *StateDBAdapter) SetNonce(evmAddr common.Address, nonce uint64) {
 			stateDB.logError(err)
 		}
 	}
-	if err := accountutil.StoreAccount(stateDB.sm, addr, s); err != nil {
-		log.L().Error("Failed to store account.", zap.Error(err))
-		stateDB.logError(err)
-	}
+	err = accountutil.StoreAccount(stateDB.sm, addr, s)
+	stateDB.assertError(err, "Failed to store account.", zap.Error(err), zap.String("address", evmAddr.Hex()))
 }
 
 // SubRefund subtracts refund
@@ -427,20 +434,18 @@ func (stateDB *StateDBAdapter) SelfDestruct(evmAddr common.Address) {
 		return
 	}
 	s, err := stateDB.accountState(evmAddr)
-	if err != nil {
-		log.L().Debug("Failed to get account.", zap.String("address", evmAddr.Hex()))
+	if stateDB.assertError(err, "Failed to get account.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 		return
 	}
 	// clears the account balance
 	actBalance := new(big.Int).Set(s.Balance)
-	if err := s.SubBalance(s.Balance); err != nil {
-		log.L().Debug("failed to clear balance", zap.Error(err), zap.String("address", evmAddr.Hex()))
+	err = s.SubBalance(s.Balance)
+	if stateDB.assertError(err, "Failed to clear balance.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 		return
 	}
 	addrHash := hash.BytesToHash160(evmAddr.Bytes())
-	if _, err := stateDB.sm.PutState(s, protocol.LegacyKeyOption(addrHash)); err != nil {
-		log.L().Error("Failed to kill contract.", zap.Error(err))
-		stateDB.logError(err)
+	_, err = stateDB.sm.PutState(s, protocol.LegacyKeyOption(addrHash))
+	if stateDB.assertError(err, "Failed to kill contract.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 		return
 	}
 	// To ensure data consistency, generate this log after the hard-fork
@@ -496,8 +501,7 @@ func (stateDB *StateDBAdapter) Selfdestruct6780(evmAddr common.Address) {
 // Exist checks the existence of an address
 func (stateDB *StateDBAdapter) Exist(evmAddr common.Address) bool {
 	addr, err := address.FromBytes(evmAddr.Bytes())
-	if err != nil {
-		log.L().Error("Failed to convert evm address.", zap.Error(err))
+	if stateDB.assertError(err, "Failed to convert evm address.", zap.Error(err)) {
 		return false
 	}
 	log.L().Debug("Check existence.", zap.String("address", addr.String()), log.Hex("addrHash", evmAddr[:]))
@@ -506,7 +510,10 @@ func (stateDB *StateDBAdapter) Exist(evmAddr common.Address) bool {
 		return true
 	}
 	recorded, err := accountutil.Recorded(stateDB.sm, addr)
-	if !recorded || err != nil {
+	if stateDB.assertError(err, "Account does not exist.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
+		return false
+	}
+	if !recorded {
 		log.L().Debug("Account does not exist.", zap.String("address", addr.String()))
 		return false
 	}
@@ -575,7 +582,7 @@ func (stateDB *StateDBAdapter) AddSlotToAccessList(addr common.Address, slot com
 func (stateDB *StateDBAdapter) Empty(evmAddr common.Address) bool {
 	log.L().Debug("Check whether the contract is empty.")
 	s, err := stateDB.accountState(evmAddr)
-	if err != nil {
+	if stateDB.assertError(err, "Failed to get account.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 		return true
 	}
 	// TODO: delete hash.ZeroHash256
@@ -586,16 +593,17 @@ func (stateDB *StateDBAdapter) Empty(evmAddr common.Address) bool {
 
 // RevertToSnapshot reverts the state factory to the state at a given snapshot
 func (stateDB *StateDBAdapter) RevertToSnapshot(snapshot int) {
-	if err := stateDB.sm.Revert(snapshot); err != nil {
-		err := errors.New("unexpected error: state manager's Revert() failed")
-		log.L().Error("Failed to revert to snapshot.", zap.Error(err))
-		stateDB.logError(err)
+	ds, ok := stateDB.selfDestructedSnapshot[snapshot]
+	if !ok && stateDB.panicUnrecoverableError {
+		log.L().Panic("Failed to revert to snapshot.", zap.Int("snapshot", snapshot))
+	}
+	err := stateDB.sm.Revert(snapshot)
+	if stateDB.assertError(err, "state manager's Revert() failed.", zap.Error(err), zap.Int("snapshot", snapshot)) {
 		return
 	}
-	ds, ok := stateDB.selfDestructedSnapshot[snapshot]
 	if !ok {
 		// this should not happen, b/c we save the SelfDestruct accounts on a successful return of Snapshot(), but check anyway
-		log.L().Error("Failed to get snapshot.", zap.Int("snapshot", snapshot))
+		log.L().Error("Failed to revert to snapshot.", zap.Int("snapshot", snapshot))
 		return
 	}
 	// restore gas refund
@@ -757,8 +765,7 @@ func (stateDB *StateDBAdapter) Snapshot() int {
 func (stateDB *StateDBAdapter) AddLog(evmLog *types.Log) {
 	log.L().Debug("Called AddLog.", zap.Any("log", evmLog))
 	addr, err := address.FromBytes(evmLog.Address.Bytes())
-	if err != nil {
-		log.L().Error("Failed to convert evm address.", zap.Error(err))
+	if stateDB.assertError(err, "Failed to convert evm address.", zap.Error(err)) {
 		return
 	}
 	var topics []hash.Hash256
@@ -816,13 +823,11 @@ func (stateDB *StateDBAdapter) AddPreimage(hash common.Hash, preimage []byte) {
 // ForEachStorage loops each storage
 func (stateDB *StateDBAdapter) ForEachStorage(addr common.Address, cb func(common.Hash, common.Hash) bool) error {
 	ctt, err := stateDB.getContract(hash.BytesToHash160(addr[:]))
-	if err != nil {
-		// stateDB.err = err
+	if stateDB.assertError(err, "Failed to get contract.", zap.Error(err), zap.String("address", addr.Hex())) {
 		return err
 	}
 	iter, err := ctt.Iterator()
-	if err != nil {
-		// stateDB.err = err
+	if stateDB.assertError(err, "Failed to get Iterator.", zap.Error(err), zap.String("address", addr.Hex())) {
 		return err
 	}
 
@@ -832,7 +837,7 @@ func (stateDB *StateDBAdapter) ForEachStorage(addr common.Address, cb func(commo
 			// hit the end of the iterator, exit now
 			return nil
 		}
-		if err != nil {
+		if stateDB.assertError(err, "Failed to get next storage.", zap.Error(err), zap.String("address", addr.Hex())) {
 			return err
 		}
 		ckey := common.Hash{}
@@ -871,10 +876,7 @@ func (stateDB *StateDBAdapter) GetCodeHash(evmAddr common.Address) common.Hash {
 		return codeHash
 	}
 	account, err := accountutil.LoadAccountByHash160(stateDB.sm, addr, stateDB.accountCreationOpts()...)
-	if err != nil {
-		log.L().Error("Failed to get code hash.", zap.Error(err))
-		// TODO (zhi) not all err should be logged
-		// stateDB.logError(err)
+	if stateDB.assertError(err, "Failed to load account.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 		return codeHash
 	}
 	copy(codeHash[:], account.CodeHash)
@@ -893,8 +895,7 @@ func (stateDB *StateDBAdapter) GetCode(evmAddr common.Address) []byte {
 		return code
 	}
 	account, err := accountutil.LoadAccountByHash160(stateDB.sm, addr, stateDB.accountCreationOpts()...)
-	if err != nil {
-		log.L().Error("Failed to load account state for address.", log.Hex("addrHash", addr[:]))
+	if stateDB.assertError(err, "Failed to load account.", zap.Error(err), zap.String("address", evmAddr.Hex())) {
 		return nil
 	}
 	var code protocol.SerializableBytes
@@ -909,9 +910,6 @@ func (stateDB *StateDBAdapter) GetCode(evmAddr common.Address) []byte {
 // GetCodeSize gets the code size saved in hash
 func (stateDB *StateDBAdapter) GetCodeSize(evmAddr common.Address) int {
 	code := stateDB.GetCode(evmAddr)
-	if code == nil {
-		return 0
-	}
 	log.L().Debug("Called GetCodeSize.", log.Hex("addrHash", evmAddr[:]))
 	return len(code)
 }
@@ -974,11 +972,8 @@ func (stateDB *StateDBAdapter) SetState(evmAddr common.Address, k, v common.Hash
 		return
 	}
 	log.L().Debug("Called SetState", log.Hex("addrHash", evmAddr[:]), log.Hex("k", k[:]))
-	if err := contract.SetState(hash.BytesToHash256(k[:]), v[:]); err != nil {
-		log.L().Error("Failed to set state.", zap.Error(err), log.Hex("addrHash", addr[:]))
-		stateDB.logError(err)
-		return
-	}
+	err = contract.SetState(hash.BytesToHash256(k[:]), v[:])
+	stateDB.assertError(err, "Failed to set state.", zap.Error(err), zap.String("address", evmAddr.Hex()))
 }
 
 // CommitContracts commits contract code to db and update pending contract account changes to trie
@@ -992,7 +987,7 @@ func (stateDB *StateDBAdapter) CommitContracts() error {
 	for _, addrStr := range addrStrs {
 		var addr hash.Hash160
 		addrBytes, err := hex.DecodeString(addrStr)
-		if err != nil {
+		if stateDB.assertError(err, "failed to decode address hash", zap.Error(err), zap.String("address", addrStr)) {
 			return errors.Wrap(err, "failed to decode address hash")
 		}
 		copy(addr[:], addrBytes)
@@ -1001,15 +996,15 @@ func (stateDB *StateDBAdapter) CommitContracts() error {
 			continue
 		}
 		contract := stateDB.cachedContract[addr]
-		if err := contract.Commit(); err != nil {
-			stateDB.logError(err)
+		err = contract.Commit()
+		if stateDB.assertError(err, "failed to commit contract", zap.Error(err), zap.String("address", addrStr)) {
 			return errors.Wrap(err, "failed to commit contract")
 		}
 		state := contract.SelfState()
 		// store the account (with new storage trie root) into account trie
-		if _, err := stateDB.sm.PutState(state, protocol.LegacyKeyOption(addr)); err != nil {
-			stateDB.logError(err)
-			return errors.Wrap(err, "failed to update pending account changes to trie")
+		_, err = stateDB.sm.PutState(state, protocol.LegacyKeyOption(addr))
+		if stateDB.assertError(err, "failed to store contract", zap.Error(err), zap.String("address", addrStr)) {
+			return errors.Wrap(err, "failed to store contract")
 		}
 	}
 	// delete suicided accounts/contract
@@ -1022,12 +1017,12 @@ func (stateDB *StateDBAdapter) CommitContracts() error {
 	for _, addrStr := range addrStrs {
 		var addr hash.Hash160
 		addrBytes, err := hex.DecodeString(addrStr)
-		if err != nil {
+		if stateDB.assertError(err, "failed to decode address hash", zap.Error(err), zap.String("address", addrStr)) {
 			return errors.Wrap(err, "failed to decode address hash")
 		}
 		copy(addr[:], addrBytes)
-		if _, err := stateDB.sm.DelState(protocol.LegacyKeyOption(addr)); err != nil {
-			stateDB.logError(err)
+		_, err = stateDB.sm.DelState(protocol.LegacyKeyOption(addr))
+		if stateDB.assertError(err, "failed to delete SelfDestruct account/contract", zap.Error(err), zap.String("address", addrStr)) {
 			return errors.Wrapf(err, "failed to delete SelfDestruct account/contract %x", addr[:])
 		}
 	}
@@ -1041,15 +1036,15 @@ func (stateDB *StateDBAdapter) CommitContracts() error {
 	for _, addrStr := range addrStrs {
 		var k common.Hash
 		addrBytes, err := hex.DecodeString(addrStr)
-		if err != nil {
+		if stateDB.assertError(err, "failed to decode address hash", zap.Error(err), zap.String("address", addrStr)) {
 			return errors.Wrap(err, "failed to decode address hash")
 		}
 		copy(k[:], addrBytes)
 		v := stateDB.preimages[k]
 		h := make([]byte, len(k))
 		copy(h, k[:])
-		if _, err = stateDB.sm.PutState(v, protocol.NamespaceOption(PreimageKVNameSpace), protocol.KeyOption(h)); err != nil {
-			stateDB.logError(err)
+		_, err = stateDB.sm.PutState(v, protocol.NamespaceOption(PreimageKVNameSpace), protocol.KeyOption(h))
+		if stateDB.assertError(err, "failed to update preimage to db", zap.Error(err), zap.String("address", addrStr)) {
 			return errors.Wrap(err, "failed to update preimage to db")
 		}
 	}


Test patch:
diff --git a/action/protocol/execution/evm/evmstatedbadapter_test.go b/action/protocol/execution/evm/evmstatedbadapter_test.go
index 89ff4eab7..46bee0717 100644
--- a/action/protocol/execution/evm/evmstatedbadapter_test.go
+++ b/action/protocol/execution/evm/evmstatedbadapter_test.go
@@ -700,7 +700,7 @@ func TestSnapshotRevertAndCommit(t *testing.T) {
 }
 
 func TestClearSnapshots(t *testing.T) {
-	testClearSnapshots := func(t *testing.T, async, fixSnapshotOrder bool) {
+	testClearSnapshots := func(t *testing.T, async, fixSnapshotOrder, panicUnrecoverableError bool) {
 		require := require.New(t)
 		ctrl := gomock.NewController(t)
 
@@ -716,6 +716,9 @@ func TestClearSnapshots(t *testing.T) {
 		if fixSnapshotOrder {
 			opts = append(opts, FixSnapshotOrderOption())
 		}
+		if panicUnrecoverableError {
+			opts = append(opts, PanicUnrecoverableErrorOption())
+		}
 		stateDB, err := NewStateDBAdapter(sm, 1, hash.ZeroHash256, opts...)
 		require.NoError(err)
 
@@ -774,13 +777,20 @@ func TestClearSnapshots(t *testing.T) {
 			// log snapshot added after fixSnapshotOrder, so it is cleared and 1 remains
 			require.Equal(1, len(stateDB.logsSnapshot))
 		}
-
+		if panicUnrecoverableError {
+			require.Panics(func() { stateDB.RevertToSnapshot(1) })
+		} else {
+			stateDB.RevertToSnapshot(1)
+		}
 	}
 	t.Run("contract w/o clear snapshots", func(t *testing.T) {
-		testClearSnapshots(t, false, false)
+		testClearSnapshots(t, false, false, false)
 	})
 	t.Run("contract with clear snapshots", func(t *testing.T) {
-		testClearSnapshots(t, false, true)
+		testClearSnapshots(t, false, true, false)
+	})
+	t.Run("contract with clear snapshots and panic on duplicate revert", func(t *testing.T) {
+		testClearSnapshots(t, false, true, true)
 	})
 }