-
Notifications
You must be signed in to change notification settings - Fork 79
/
policy.go
351 lines (307 loc) · 11.4 KB
/
policy.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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
package native
import (
"fmt"
"math/big"
"sort"
"sync"
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
const (
policyContractID = -7
defaultExecFeeFactor = interop.DefaultBaseExecFee
defaultFeePerByte = 1000
defaultMaxVerificationGas = 1_50000000
// DefaultStoragePrice is the price to pay for 1 byte of storage.
DefaultStoragePrice = 100000
// maxExecFeeFactor is the maximum allowed execution fee factor.
maxExecFeeFactor = 100
// maxFeePerByte is the maximum allowed fee per byte value.
maxFeePerByte = 100_000_000
// maxStoragePrice is the maximum allowed price for a byte of storage.
maxStoragePrice = 10000000
// blockedAccountPrefix is a prefix used to store blocked account.
blockedAccountPrefix = 15
)
var (
// execFeeFactorKey is a key used to store execution fee factor.
execFeeFactorKey = []byte{18}
// feePerByteKey is a key used to store the minimum fee per byte for
// transaction.
feePerByteKey = []byte{10}
// storagePriceKey is a key used to store storage price.
storagePriceKey = []byte{19}
)
// Policy represents Policy native contract.
type Policy struct {
interop.ContractMD
NEO *NEO
lock sync.RWMutex
// isValid defies whether cached values were changed during the current
// consensus iteration. If false, these values will be updated after
// blockchain DAO persisting. If true, we can safely use cached values.
isValid bool
execFeeFactor uint32
feePerByte int64
maxVerificationGas int64
storagePrice uint32
blockedAccounts []util.Uint160
}
var _ interop.Contract = (*Policy)(nil)
// newPolicy returns Policy native contract.
func newPolicy() *Policy {
p := &Policy{ContractMD: *interop.NewContractMD(nativenames.Policy, policyContractID)}
defer p.UpdateHash()
desc := newDescriptor("getFeePerByte", smartcontract.IntegerType)
md := newMethodAndPrice(p.getFeePerByte, 1<<15, callflag.ReadStates)
p.AddMethod(md, desc)
desc = newDescriptor("isBlocked", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(p.isBlocked, 1<<15, callflag.ReadStates)
p.AddMethod(md, desc)
desc = newDescriptor("getExecFeeFactor", smartcontract.IntegerType)
md = newMethodAndPrice(p.getExecFeeFactor, 1<<15, callflag.ReadStates)
p.AddMethod(md, desc)
desc = newDescriptor("setExecFeeFactor", smartcontract.VoidType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setExecFeeFactor, 1<<15, callflag.States)
p.AddMethod(md, desc)
desc = newDescriptor("getStoragePrice", smartcontract.IntegerType)
md = newMethodAndPrice(p.getStoragePrice, 1<<15, callflag.ReadStates)
p.AddMethod(md, desc)
desc = newDescriptor("setStoragePrice", smartcontract.VoidType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setStoragePrice, 1<<15, callflag.States)
p.AddMethod(md, desc)
desc = newDescriptor("setFeePerByte", smartcontract.VoidType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setFeePerByte, 1<<15, callflag.States)
p.AddMethod(md, desc)
desc = newDescriptor("blockAccount", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(p.blockAccount, 1<<15, callflag.States)
p.AddMethod(md, desc)
desc = newDescriptor("unblockAccount", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(p.unblockAccount, 1<<15, callflag.States)
p.AddMethod(md, desc)
return p
}
// Metadata implements Contract interface.
func (p *Policy) Metadata() *interop.ContractMD {
return &p.ContractMD
}
// Initialize initializes Policy native contract and implements Contract interface.
func (p *Policy) Initialize(ic *interop.Context) error {
setIntWithKey(p.ID, ic.DAO, feePerByteKey, defaultFeePerByte)
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, defaultExecFeeFactor)
setIntWithKey(p.ID, ic.DAO, storagePriceKey, DefaultStoragePrice)
p.isValid = true
p.execFeeFactor = defaultExecFeeFactor
p.feePerByte = defaultFeePerByte
p.maxVerificationGas = defaultMaxVerificationGas
p.storagePrice = DefaultStoragePrice
p.blockedAccounts = make([]util.Uint160, 0)
return nil
}
// OnPersist implements Contract interface.
func (p *Policy) OnPersist(ic *interop.Context) error {
return nil
}
// PostPersist implements Contract interface.
func (p *Policy) PostPersist(ic *interop.Context) error {
p.lock.Lock()
defer p.lock.Unlock()
if p.isValid {
return nil
}
p.execFeeFactor = uint32(getIntWithKey(p.ID, ic.DAO, execFeeFactorKey))
p.feePerByte = getIntWithKey(p.ID, ic.DAO, feePerByteKey)
p.maxVerificationGas = defaultMaxVerificationGas
p.storagePrice = uint32(getIntWithKey(p.ID, ic.DAO, storagePriceKey))
p.blockedAccounts = make([]util.Uint160, 0)
siArr, err := ic.DAO.GetStorageItemsWithPrefix(p.ID, []byte{blockedAccountPrefix})
if err != nil {
return fmt.Errorf("failed to get blocked accounts from storage: %w", err)
}
for _, kv := range siArr {
hash, err := util.Uint160DecodeBytesBE([]byte(kv.Key))
if err != nil {
return fmt.Errorf("failed to decode blocked account hash: %w", err)
}
p.blockedAccounts = append(p.blockedAccounts, hash)
}
// blockedAccounts should be sorted by account BE bytes, but GetStorageItemsWithPrefix
// returns values sorted by key (which is account's BE bytes), so don't need to sort
// one more time.
p.isValid = true
return nil
}
// getFeePerByte is Policy contract method and returns required transaction's fee
// per byte.
func (p *Policy) getFeePerByte(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(big.NewInt(p.GetFeePerByteInternal(ic.DAO)))
}
// GetFeePerByteInternal returns required transaction's fee per byte.
func (p *Policy) GetFeePerByteInternal(dao *dao.Simple) int64 {
p.lock.RLock()
defer p.lock.RUnlock()
if p.isValid {
return p.feePerByte
}
return getIntWithKey(p.ID, dao, feePerByteKey)
}
// GetMaxVerificationGas returns maximum gas allowed to be burned during verificaion.
func (p *Policy) GetMaxVerificationGas(_ *dao.Simple) int64 {
if p.isValid {
return p.maxVerificationGas
}
return defaultMaxVerificationGas
}
func (p *Policy) getExecFeeFactor(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(big.NewInt(int64(p.GetExecFeeFactorInternal(ic.DAO))))
}
// GetExecFeeFactorInternal returns current execution fee factor.
func (p *Policy) GetExecFeeFactorInternal(d *dao.Simple) int64 {
p.lock.RLock()
defer p.lock.RUnlock()
if p.isValid {
return int64(p.execFeeFactor)
}
return getIntWithKey(p.ID, d, execFeeFactorKey)
}
func (p *Policy) setExecFeeFactor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
value := toUint32(args[0])
if value <= 0 || maxExecFeeFactor < value {
panic(fmt.Errorf("ExecFeeFactor must be between 0 and %d", maxExecFeeFactor))
}
if !p.NEO.checkCommittee(ic) {
panic("invalid committee signature")
}
p.lock.Lock()
defer p.lock.Unlock()
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, int64(value))
p.isValid = false
return stackitem.Null{}
}
// isBlocked is Policy contract method and checks whether provided account is blocked.
func (p *Policy) isBlocked(ic *interop.Context, args []stackitem.Item) stackitem.Item {
hash := toUint160(args[0])
return stackitem.NewBool(p.IsBlockedInternal(ic.DAO, hash))
}
// IsBlockedInternal checks whether provided account is blocked.
func (p *Policy) IsBlockedInternal(dao *dao.Simple, hash util.Uint160) bool {
p.lock.RLock()
defer p.lock.RUnlock()
if p.isValid {
length := len(p.blockedAccounts)
i := sort.Search(length, func(i int) bool {
return !p.blockedAccounts[i].Less(hash)
})
if length != 0 && i != length && p.blockedAccounts[i].Equals(hash) {
return true
}
return false
}
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
return dao.GetStorageItem(p.ID, key) != nil
}
func (p *Policy) getStoragePrice(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(big.NewInt(p.GetStoragePriceInternal(ic.DAO)))
}
// GetStoragePriceInternal returns current execution fee factor.
func (p *Policy) GetStoragePriceInternal(d *dao.Simple) int64 {
p.lock.RLock()
defer p.lock.RUnlock()
if p.isValid {
return int64(p.storagePrice)
}
return getIntWithKey(p.ID, d, storagePriceKey)
}
func (p *Policy) setStoragePrice(ic *interop.Context, args []stackitem.Item) stackitem.Item {
value := toUint32(args[0])
if value <= 0 || maxStoragePrice < value {
panic(fmt.Errorf("StoragePrice must be between 0 and %d", maxStoragePrice))
}
if !p.NEO.checkCommittee(ic) {
panic("invalid committee signature")
}
p.lock.Lock()
defer p.lock.Unlock()
setIntWithKey(p.ID, ic.DAO, storagePriceKey, int64(value))
p.isValid = false
return stackitem.Null{}
}
// setFeePerByte is Policy contract method and sets transaction's fee per byte.
func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stackitem.Item {
value := toBigInt(args[0]).Int64()
if value < 0 || value > maxFeePerByte {
panic(fmt.Errorf("FeePerByte shouldn't be negative or greater than %d", maxFeePerByte))
}
if !p.NEO.checkCommittee(ic) {
panic("invalid committee signature")
}
p.lock.Lock()
defer p.lock.Unlock()
setIntWithKey(p.ID, ic.DAO, feePerByteKey, value)
p.isValid = false
return stackitem.Null{}
}
// blockAccount is Policy contract method and adds given account hash to the list
// of blocked accounts.
func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
if !p.NEO.checkCommittee(ic) {
panic("invalid committee signature")
}
hash := toUint160(args[0])
for i := range ic.Natives {
if ic.Natives[i].Metadata().Hash == hash {
panic("cannot block native contract")
}
}
if p.IsBlockedInternal(ic.DAO, hash) {
return stackitem.NewBool(false)
}
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
p.lock.Lock()
defer p.lock.Unlock()
ic.DAO.PutStorageItem(p.ID, key, state.StorageItem{})
p.isValid = false
return stackitem.NewBool(true)
}
// unblockAccount is Policy contract method and removes given account hash from
// the list of blocked accounts.
func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
if !p.NEO.checkCommittee(ic) {
panic("invalid committee signature")
}
hash := toUint160(args[0])
if !p.IsBlockedInternal(ic.DAO, hash) {
return stackitem.NewBool(false)
}
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
p.lock.Lock()
defer p.lock.Unlock()
ic.DAO.DeleteStorageItem(p.ID, key)
p.isValid = false
return stackitem.NewBool(true)
}
// CheckPolicy checks whether transaction conforms to current policy restrictions
// like not being signed by blocked account or not exceeding block-level system
// fee limit.
func (p *Policy) CheckPolicy(d *dao.Simple, tx *transaction.Transaction) error {
for _, signer := range tx.Signers {
if p.IsBlockedInternal(d, signer.Account) {
return fmt.Errorf("account %s is blocked", signer.Account.StringLE())
}
}
return nil
}