-
Notifications
You must be signed in to change notification settings - Fork 4
/
change_log.go
307 lines (275 loc) · 8.07 KB
/
change_log.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
package types
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/LemoFoundationLtd/lemochain-go/common"
"github.com/LemoFoundationLtd/lemochain-go/common/crypto/sha3"
"github.com/LemoFoundationLtd/lemochain-go/common/hexutil"
"github.com/LemoFoundationLtd/lemochain-go/common/log"
"github.com/LemoFoundationLtd/lemochain-go/common/rlp"
"io"
"math/big"
"reflect"
"strings"
)
var (
ErrUnknownChangeLogType = errors.New("unknown change log type")
// ErrWrongChangeLogVersion is returned by the ChangeLog Undo/Redo if account has an unexpected version
ErrWrongChangeLogVersion = errors.New("the version of change log and account is not match")
ErrAlreadyRedo = errors.New("the change log's version is lower than account's. maybe it has redid")
ErrWrongChangeLogData = errors.New("change log data is incorrect")
)
// ChangeLogProcessor is used to access account, and the intermediate data generated by transactions.
// It is implemented by account.Manager
type ChangeLogProcessor interface {
GetAccount(addr common.Address) AccountAccessor
GetNextVersion(logType ChangeLogType, addr common.Address) uint32
PushEvent(event *Event)
PopEvent() error
}
type ChangeLogType uint32
type changeLogDecoder func(*rlp.Stream) (interface{}, error)
type changeLogDoFunc func(*ChangeLog, ChangeLogProcessor) error
type logConfig struct {
TypeName string
NewValDecoder changeLogDecoder
ExtraDecoder changeLogDecoder
Redo changeLogDoFunc
Undo changeLogDoFunc
}
// logConfigs define how the log type map to action functions
var logConfigs = make(map[ChangeLogType]logConfig)
func RegisterChangeLog(logType ChangeLogType, TypeName string, newValDecoder, extraDecoder changeLogDecoder, redo, undo changeLogDoFunc) {
logConfigs[logType] = logConfig{TypeName, newValDecoder, extraDecoder, redo, undo}
}
func (t ChangeLogType) String() string {
config, ok := logConfigs[t]
if ok {
return config.TypeName
}
return fmt.Sprintf("ChangeLogType(%d)", t)
}
type ChangeLog struct {
LogType ChangeLogType `json:"type" gencodec:"required"`
Address common.Address `json:"address" gencodec:"required"`
// The No. of ChangeLog in an account
Version uint32 `json:"version" gencodec:"required"`
// data pointer. Their content type depend on specific NewXXXLog function
OldVal interface{} `json:"-"` // It's used for undo. So no need to save or send to others
NewVal interface{} `json:"newValue"`
Extra interface{} `json:"extra"`
}
type rlpChangeLog struct {
LogType ChangeLogType
Address common.Address
Version uint32
NewVal interface{}
Extra interface{}
}
// Hash returns the keccak256 hash of its RLP encoding.
func (c *ChangeLog) Hash() (h common.Hash) {
hw := sha3.NewKeccak256()
// this will call EncodeRLP
rlp.Encode(hw, c)
hw.Sum(h[:0])
return h
}
// EncodeRLP implements rlp.Encoder.
func (c *ChangeLog) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, rlpChangeLog{
LogType: c.LogType,
Address: c.Address,
Version: c.Version,
NewVal: c.NewVal,
Extra: c.Extra,
})
}
// DecodeRLP implements rlp.Decoder.
func (c *ChangeLog) DecodeRLP(s *rlp.Stream) (err error) {
if _, err = s.List(); err != nil {
return err
}
if err = s.Decode(&c.LogType); err != nil {
return err
}
if err = s.Decode(&c.Address); err != nil {
return err
}
if err = s.Decode(&c.Version); err != nil {
return err
}
// decode the interface{}
config, ok := logConfigs[c.LogType]
if !ok {
log.Errorf("unexpected LogType %T", c.LogType)
return ErrUnknownChangeLogType
}
if c.NewVal, err = config.NewValDecoder(s); err != nil {
return err
}
if c.Extra, err = config.ExtraDecoder(s); err != nil {
return err
}
// This error means there are some data need to be decoded
err = s.ListEnd()
return err
}
// MarshalJSON marshals as JSON.
func (c ChangeLog) MarshalJSON() ([]byte, error) {
type jsonChangeLog struct {
LogType hexutil.Uint32 `json:"type" gencodec:"required"`
Address common.Address `json:"address" gencodec:"required"`
Version hexutil.Uint32 `json:"version" gencodec:"required"`
NewVal string `json:"newValue"`
Extra string `json:"extra"`
}
var enc jsonChangeLog
enc.LogType = hexutil.Uint32(c.LogType)
enc.Address = c.Address
enc.Version = hexutil.Uint32(c.Version)
if c.NewVal != nil {
NewVal, err := rlp.EncodeToBytes(c.NewVal)
if err != nil {
return nil, err
}
enc.NewVal = common.ToHex(NewVal)
}
if c.Extra != nil {
Extra, err := rlp.EncodeToBytes(c.Extra)
if err != nil {
return nil, err
}
enc.Extra = common.ToHex(Extra)
}
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (c *ChangeLog) UnmarshalJSON(input []byte) error {
type jsonChangeLog struct {
LogType *hexutil.Uint32 `json:"type" gencodec:"required"`
Address *common.Address `json:"address" gencodec:"required"`
Version *hexutil.Uint32 `json:"version" gencodec:"required"`
NewVal string `json:"newValue"`
Extra string `json:"extra"`
}
var dec jsonChangeLog
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.LogType == nil {
return errors.New("missing required field 'type' for ChangeLog")
}
c.LogType = ChangeLogType(*dec.LogType)
if dec.Address == nil {
return errors.New("missing required field 'address' for ChangeLog")
}
c.Address = *dec.Address
if dec.Version == nil {
return errors.New("missing required field 'version' for ChangeLog")
}
c.Version = uint32(*dec.Version)
// decode the interface{}
config, ok := logConfigs[c.LogType]
if !ok {
log.Errorf("unexpected LogType %T", c.LogType)
return ErrUnknownChangeLogType
}
var err error
if dec.NewVal != "" {
r := bytes.NewReader(common.FromHex(dec.NewVal))
s := rlp.NewStream(r, uint64(len(dec.NewVal)))
if c.NewVal, err = config.NewValDecoder(s); err != nil {
return err
}
}
if dec.Extra != "" {
r := bytes.NewReader(common.FromHex(dec.Extra))
s := rlp.NewStream(r, uint64(len(dec.Extra)))
if c.Extra, err = config.ExtraDecoder(s); err != nil {
return err
}
}
return nil
}
func (c *ChangeLog) Copy() *ChangeLog {
cpy := *c
return &cpy
}
var bigIntType = reflect.TypeOf(big.Int{})
func formatIfIsBigInt(v interface{}) interface{} {
result := v
if reflect.TypeOf(result) == bigIntType {
i := v.(big.Int)
result = (&i).Text(10)
}
return result
}
func (c *ChangeLog) String() string {
set := []string{
fmt.Sprintf("Account: %s", c.Address.String()),
fmt.Sprintf("Version: %d", c.Version),
}
if c.OldVal != nil {
set = append(set, fmt.Sprintf("OldVal: %v", formatIfIsBigInt(c.OldVal)))
}
if c.NewVal != nil {
set = append(set, fmt.Sprintf("NewVal: %v", formatIfIsBigInt(c.NewVal)))
}
if c.Extra != nil {
set = append(set, fmt.Sprintf("Extra: %v", formatIfIsBigInt(c.Extra)))
}
return fmt.Sprintf("%s{%s}", c.LogType, strings.Join(set, ", "))
}
type ChangeLogSlice []*ChangeLog
func (c ChangeLogSlice) Len() int {
return len(c)
}
func (c ChangeLogSlice) Less(i, j int) bool {
return c[i].Version < c[j].Version
}
func (c ChangeLogSlice) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
func (c ChangeLogSlice) Search(version uint32) int {
for i, value := range c {
if value.Version == version {
return i
}
}
return -1
}
// FindByType find the first same type change log.
func (c ChangeLogSlice) FindByType(target *ChangeLog) *ChangeLog {
for _, item := range c {
if item.LogType == target.LogType {
return item
}
}
return nil
}
// Undo reverts the change. Its behavior depends on ChangeLog.ChangeLogType
func (c *ChangeLog) Undo(processor ChangeLogProcessor) error {
config, ok := logConfigs[c.LogType]
if !ok {
log.Errorf("unexpected LogType %T", c.LogType)
return ErrUnknownChangeLogType
}
if err := config.Undo(c, processor); err != nil {
return err
}
return nil
}
// Redo reply the change for light client. Its behavior depends on ChangeLog.ChangeLogType
func (c *ChangeLog) Redo(processor ChangeLogProcessor) error {
config, ok := logConfigs[c.LogType]
if !ok {
log.Errorf("unexpected LogType %T", c.LogType)
return ErrUnknownChangeLogType
}
if err := config.Redo(c, processor); err != nil {
return err
}
return nil
}