@@ -17,14 +17,21 @@ const (
17
17
prevStateFileName = "prev_app_state.json"
18
18
)
19
19
20
- // State is the application state.
21
- type State struct {
22
- sync. RWMutex
20
+ // Intermediate type used exclusively in serialization/deserialization of
21
+ // State, such that State need not expose any of its internal values publicly.
22
+ type serializedState struct {
23
23
Height uint64
24
24
Values map [string ]string
25
25
Hash []byte
26
+ }
27
+
28
+ // State is the application state.
29
+ type State struct {
30
+ sync.RWMutex
31
+ height uint64
32
+ values map [string ]string
33
+ hash []byte
26
34
27
- // private fields aren't marshaled to disk.
28
35
currentFile string
29
36
// app saves current and previous state for rollback functionality
30
37
previousFile string
@@ -35,12 +42,12 @@ type State struct {
35
42
// NewState creates a new state.
36
43
func NewState (dir string , persistInterval uint64 ) (* State , error ) {
37
44
state := & State {
38
- Values : make (map [string ]string ),
45
+ values : make (map [string ]string ),
39
46
currentFile : filepath .Join (dir , stateFileName ),
40
47
previousFile : filepath .Join (dir , prevStateFileName ),
41
48
persistInterval : persistInterval ,
42
49
}
43
- state .Hash = hashItems (state .Values , state .Height )
50
+ state .hash = hashItems (state .values , state .height )
44
51
err := state .load ()
45
52
switch {
46
53
case errors .Is (err , os .ErrNotExist ):
@@ -66,8 +73,7 @@ func (s *State) load() error {
66
73
return fmt .Errorf ("failed to read state from %q: %w" , s .currentFile , err )
67
74
}
68
75
}
69
- err = json .Unmarshal (bz , s )
70
- if err != nil {
76
+ if err := json .Unmarshal (bz , s ); err != nil {
71
77
return fmt .Errorf ("invalid state data in %q: %w" , s .currentFile , err )
72
78
}
73
79
return nil
@@ -97,11 +103,39 @@ func (s *State) save() error {
97
103
return os .Rename (newFile , s .currentFile )
98
104
}
99
105
106
+ // GetHash provides a thread-safe way of accessing a copy of the current state
107
+ // hash.
108
+ func (s * State ) GetHash () []byte {
109
+ s .RLock ()
110
+ defer s .RUnlock ()
111
+ hash := make ([]byte , len (s .hash ))
112
+ copy (hash , s .hash )
113
+ return hash
114
+ }
115
+
116
+ // Info returns both the height and hash simultaneously, and is used in the
117
+ // ABCI Info call.
118
+ func (s * State ) Info () (uint64 , []byte ) {
119
+ s .RLock ()
120
+ defer s .RUnlock ()
121
+ height := s .height
122
+ hash := make ([]byte , len (s .hash ))
123
+ copy (hash , s .hash )
124
+ return height , hash
125
+ }
126
+
100
127
// Export exports key/value pairs as JSON, used for state sync snapshots.
101
- func (s * State ) Export () ([]byte , error ) {
128
+ // Additionally returns the current height and hash of the state.
129
+ func (s * State ) Export () ([]byte , uint64 , []byte , error ) {
102
130
s .RLock ()
103
131
defer s .RUnlock ()
104
- return json .Marshal (s .Values )
132
+ bz , err := json .Marshal (s .values )
133
+ if err != nil {
134
+ return nil , 0 , nil , err
135
+ }
136
+ height := s .height
137
+ stateHash := hashItems (s .values , height )
138
+ return bz , height , stateHash , nil
105
139
}
106
140
107
141
// Import imports key/value pairs from JSON bytes, used for InitChain.AppStateBytes and
@@ -114,71 +148,100 @@ func (s *State) Import(height uint64, jsonBytes []byte) error {
114
148
if err != nil {
115
149
return fmt .Errorf ("failed to decode imported JSON data: %w" , err )
116
150
}
117
- s .Height = height
118
- s .Values = values
119
- s .Hash = hashItems (values , height )
151
+ s .height = height
152
+ s .values = values
153
+ s .hash = hashItems (values , height )
120
154
return s .save ()
121
155
}
122
156
123
157
// Get fetches a value. A missing value is returned as an empty string.
124
158
func (s * State ) Get (key string ) string {
125
159
s .RLock ()
126
160
defer s .RUnlock ()
127
- return s .Values [key ]
161
+ return s .values [key ]
128
162
}
129
163
130
164
// Set sets a value. Setting an empty value is equivalent to deleting it.
131
165
func (s * State ) Set (key , value string ) {
132
166
s .Lock ()
133
167
defer s .Unlock ()
134
168
if value == "" {
135
- delete (s .Values , key )
169
+ delete (s .values , key )
136
170
} else {
137
- s .Values [key ] = value
171
+ s .values [key ] = value
138
172
}
139
173
}
140
174
175
+ // Query is used in the ABCI Query call, and provides both the current height
176
+ // and the value associated with the given key.
177
+ func (s * State ) Query (key string ) (string , uint64 ) {
178
+ s .RLock ()
179
+ defer s .RUnlock ()
180
+ height := s .height
181
+ value := s .values [key ]
182
+ return value , height
183
+ }
184
+
141
185
// Finalize is called after applying a block, updating the height and returning the new app_hash
142
186
func (s * State ) Finalize () []byte {
143
187
s .Lock ()
144
188
defer s .Unlock ()
145
189
switch {
146
- case s .Height > 0 :
147
- s .Height ++
190
+ case s .height > 0 :
191
+ s .height ++
148
192
case s .initialHeight > 0 :
149
- s .Height = s .initialHeight
193
+ s .height = s .initialHeight
150
194
default :
151
- s .Height = 1
195
+ s .height = 1
152
196
}
153
- s .Hash = hashItems (s .Values , s .Height )
154
- return s .Hash
197
+ s .hash = hashItems (s .values , s .height )
198
+ return s .hash
155
199
}
156
200
157
201
// Commit commits the current state.
158
202
func (s * State ) Commit () (uint64 , error ) {
159
203
s .Lock ()
160
204
defer s .Unlock ()
161
- if s .persistInterval > 0 && s .Height % s .persistInterval == 0 {
205
+ if s .persistInterval > 0 && s .height % s .persistInterval == 0 {
162
206
err := s .save ()
163
207
if err != nil {
164
208
return 0 , err
165
209
}
166
210
}
167
- return s .Height , nil
211
+ return s .height , nil
168
212
}
169
213
170
214
func (s * State ) Rollback () error {
171
215
bz , err := os .ReadFile (s .previousFile )
172
216
if err != nil {
173
217
return fmt .Errorf ("failed to read state from %q: %w" , s .previousFile , err )
174
218
}
175
- err = json .Unmarshal (bz , s )
176
- if err != nil {
219
+ if err := json .Unmarshal (bz , s ); err != nil {
177
220
return fmt .Errorf ("invalid state data in %q: %w" , s .previousFile , err )
178
221
}
179
222
return nil
180
223
}
181
224
225
+ func (s * State ) UnmarshalJSON (b []byte ) error {
226
+ var ss serializedState
227
+ if err := json .Unmarshal (b , & ss ); err != nil {
228
+ return err
229
+ }
230
+ s .height = ss .Height
231
+ s .values = ss .Values
232
+ s .hash = ss .Hash
233
+ return nil
234
+ }
235
+
236
+ func (s * State ) MarshalJSON () ([]byte , error ) {
237
+ ss := & serializedState {
238
+ Height : s .height ,
239
+ Values : s .values ,
240
+ Hash : s .hash ,
241
+ }
242
+ return json .Marshal (ss )
243
+ }
244
+
182
245
// hashItems hashes a set of key/value items.
183
246
func hashItems (items map [string ]string , height uint64 ) []byte {
184
247
keys := make ([]string , 0 , len (items ))
0 commit comments