@@ -36,6 +36,12 @@ import (
36
36
"github.com/ethereum/go-ethereum/trie/triestate"
37
37
)
38
38
39
+ const (
40
+ // storageDeleteLimit denotes the highest permissible memory allocation
41
+ // employed for contract storage deletion.
42
+ storageDeleteLimit = 512 * 1024 * 1024
43
+ )
44
+
39
45
type revision struct {
40
46
id int
41
47
journalIndex int
@@ -983,59 +989,130 @@ func (s *StateDB) clearJournalAndRefund() {
983
989
s .validRevisions = s .validRevisions [:0 ] // Snapshots can be created without journal entries
984
990
}
985
991
986
- // deleteStorage iterates the storage trie belongs to the account and mark all
987
- // slots inside as deleted.
988
- func (s * StateDB ) deleteStorage (addr common.Address , addrHash common.Hash , root common.Hash ) (bool , map [common .Hash ][]byte , * trienode.NodeSet , error ) {
989
- start := time .Now ()
992
+ // fastDeleteStorage is the function that efficiently deletes the storage trie
993
+ // of a specific account. It leverages the associated state snapshot for fast
994
+ // storage iteration and constructs trie node deletion markers by creating
995
+ // stack trie with iterated slots.
996
+ func (s * StateDB ) fastDeleteStorage (addrHash common.Hash , root common.Hash ) (bool , common.StorageSize , map [common.Hash ][]byte , * trienode.NodeSet , error ) {
997
+ iter , err := s .snaps .StorageIterator (s .originalRoot , addrHash , common.Hash {})
998
+ if err != nil {
999
+ return false , 0 , nil , nil , err
1000
+ }
1001
+ defer iter .Release ()
1002
+
1003
+ var (
1004
+ size common.StorageSize
1005
+ nodes = trienode .NewNodeSet (addrHash )
1006
+ slots = make (map [common.Hash ][]byte )
1007
+ )
1008
+ stack := trie .NewStackTrie (func (owner common.Hash , path []byte , hash common.Hash , blob []byte ) {
1009
+ nodes .AddNode (path , trienode .NewDeleted ())
1010
+ size += common .StorageSize (len (path ))
1011
+ })
1012
+ for iter .Next () {
1013
+ if size > storageDeleteLimit {
1014
+ return true , size , nil , nil , nil
1015
+ }
1016
+ slot := common .CopyBytes (iter .Slot ())
1017
+ if iter .Error () != nil { // error might occur after Slot function
1018
+ return false , 0 , nil , nil , err
1019
+ }
1020
+ size += common .StorageSize (common .HashLength + len (slot ))
1021
+ slots [iter .Hash ()] = slot
1022
+
1023
+ if err := stack .Update (iter .Hash ().Bytes (), slot ); err != nil {
1024
+ return false , 0 , nil , nil , err
1025
+ }
1026
+ }
1027
+ if iter .Error () != nil { // error might occur during iteration
1028
+ return false , 0 , nil , nil , err
1029
+ }
1030
+ if stack .Hash () != root {
1031
+ return false , 0 , nil , nil , fmt .Errorf ("snapshot is not matched, exp %x, got %x" , root , stack .Hash ())
1032
+ }
1033
+ return false , size , slots , nodes , nil
1034
+ }
1035
+
1036
+ // slowDeleteStorage serves as a less-efficient alternative to "fastDeleteStorage,"
1037
+ // employed when the associated state snapshot is not available. It iterates the
1038
+ // storage slots along with all internal trie nodes via trie directly.
1039
+ func (s * StateDB ) slowDeleteStorage (addr common.Address , addrHash common.Hash , root common.Hash ) (bool , common.StorageSize , map [common.Hash ][]byte , * trienode.NodeSet , error ) {
990
1040
tr , err := s .db .OpenStorageTrie (s .originalRoot , addr , root )
991
1041
if err != nil {
992
- return false , nil , nil , fmt .Errorf ("failed to open storage trie, err: %w" , err )
1042
+ return false , 0 , nil , nil , fmt .Errorf ("failed to open storage trie, err: %w" , err )
993
1043
}
994
1044
it , err := tr .NodeIterator (nil )
995
1045
if err != nil {
996
- return false , nil , nil , fmt .Errorf ("failed to open storage iterator, err: %w" , err )
1046
+ return false , 0 , nil , nil , fmt .Errorf ("failed to open storage iterator, err: %w" , err )
997
1047
}
998
1048
var (
999
- set = trienode .NewNodeSet (addrHash )
1000
- slots = make (map [common.Hash ][]byte )
1001
- stateSize common.StorageSize
1002
- nodeSize common.StorageSize
1049
+ size common.StorageSize
1050
+ nodes = trienode .NewNodeSet (addrHash )
1051
+ slots = make (map [common.Hash ][]byte )
1003
1052
)
1004
1053
for it .Next (true ) {
1005
- // arbitrary stateSize limit, make it configurable
1006
- if stateSize + nodeSize > 512 * 1024 * 1024 {
1007
- log .Info ("Skip large storage deletion" , "address" , addr .Hex (), "states" , stateSize , "nodes" , nodeSize )
1008
- if metrics .EnabledExpensive {
1009
- slotDeletionSkip .Inc (1 )
1010
- }
1011
- return true , nil , nil , nil
1054
+ if size > storageDeleteLimit {
1055
+ return true , size , nil , nil , nil
1012
1056
}
1013
1057
if it .Leaf () {
1014
1058
slots [common .BytesToHash (it .LeafKey ())] = common .CopyBytes (it .LeafBlob ())
1015
- stateSize += common .StorageSize (common .HashLength + len (it .LeafBlob ()))
1059
+ size += common .StorageSize (common .HashLength + len (it .LeafBlob ()))
1016
1060
continue
1017
1061
}
1018
1062
if it .Hash () == (common.Hash {}) {
1019
1063
continue
1020
1064
}
1021
- nodeSize += common .StorageSize (len (it .Path ()))
1022
- set .AddNode (it .Path (), trienode .NewDeleted ())
1065
+ size += common .StorageSize (len (it .Path ()))
1066
+ nodes .AddNode (it .Path (), trienode .NewDeleted ())
1023
1067
}
1024
1068
if err := it .Error (); err != nil {
1069
+ return false , 0 , nil , nil , err
1070
+ }
1071
+ return false , size , slots , nodes , nil
1072
+ }
1073
+
1074
+ // deleteStorage is designed to delete the storage trie of a designated account.
1075
+ // It could potentially be terminated if the storage size is excessively large,
1076
+ // potentially leading to an out-of-memory panic. The function will make an attempt
1077
+ // to utilize an efficient strategy if the associated state snapshot is reachable;
1078
+ // otherwise, it will resort to a less-efficient approach.
1079
+ func (s * StateDB ) deleteStorage (addr common.Address , addrHash common.Hash , root common.Hash ) (bool , map [common .Hash ][]byte , * trienode.NodeSet , error ) {
1080
+ var (
1081
+ start = time .Now ()
1082
+ err error
1083
+ aborted bool
1084
+ size common.StorageSize
1085
+ slots map [common.Hash ][]byte
1086
+ nodes * trienode.NodeSet
1087
+ )
1088
+ // The fast approach can be failed if the snapshot is not fully
1089
+ // generated, or it's internally corrupted. Fallback to the slow
1090
+ // one just in case.
1091
+ if s .snap != nil {
1092
+ aborted , size , slots , nodes , err = s .fastDeleteStorage (addrHash , root )
1093
+ }
1094
+ if s .snap == nil || err != nil {
1095
+ aborted , size , slots , nodes , err = s .slowDeleteStorage (addr , addrHash , root )
1096
+ }
1097
+ if err != nil {
1025
1098
return false , nil , nil , err
1026
1099
}
1027
1100
if metrics .EnabledExpensive {
1028
- if int64 ( len ( slots )) > slotDeletionMaxCount . Value () {
1029
- slotDeletionMaxCount . Update ( int64 ( len ( slots )) )
1101
+ if aborted {
1102
+ slotDeletionSkip . Inc ( 1 )
1030
1103
}
1031
- if int64 (stateSize + nodeSize ) > slotDeletionMaxSize .Value () {
1032
- slotDeletionMaxSize .Update (int64 (stateSize + nodeSize ))
1104
+ n := int64 (len (slots ))
1105
+ if n > slotDeletionMaxCount .Value () {
1106
+ slotDeletionMaxCount .Update (n )
1107
+ }
1108
+ if int64 (size ) > slotDeletionMaxSize .Value () {
1109
+ slotDeletionMaxSize .Update (int64 (size ))
1033
1110
}
1034
1111
slotDeletionTimer .UpdateSince (start )
1035
- slotDeletionCount .Mark (int64 ( len ( slots )) )
1036
- slotDeletionSize .Mark (int64 (stateSize + nodeSize ))
1112
+ slotDeletionCount .Mark (n )
1113
+ slotDeletionSize .Mark (int64 (size ))
1037
1114
}
1038
- return false , slots , set , nil
1115
+ return aborted , slots , nodes , nil
1039
1116
}
1040
1117
1041
1118
// handleDestruction processes all destruction markers and deletes the account
@@ -1063,7 +1140,13 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root
1063
1140
// In case (d), **original** account along with its storages should be deleted,
1064
1141
// with their values be tracked as original value.
1065
1142
func (s * StateDB ) handleDestruction (nodes * trienode.MergedNodeSet ) (map [common.Address ]struct {}, error ) {
1143
+ // Short circuit if geth is running with hash mode. This procedure can consume
1144
+ // considerable time and storage deletion isn't supported in hash mode, thus
1145
+ // preemptively avoiding unnecessary expenses.
1066
1146
incomplete := make (map [common.Address ]struct {})
1147
+ if s .db .TrieDB ().Scheme () == rawdb .HashScheme {
1148
+ return incomplete , nil
1149
+ }
1067
1150
for addr , prev := range s .stateObjectsDestruct {
1068
1151
// The original account was non-existing, and it's marked as destructed
1069
1152
// in the scope of block. It can be case (a) or (b).
0 commit comments