Skip to content

Commit 1994f4b

Browse files
author
ZhangTao1596
committed
core: fix nspcc-dev#2845 system fee refund attribute
1 parent b56dff2 commit 1994f4b

File tree

8 files changed

+118
-28
lines changed

8 files changed

+118
-28
lines changed

pkg/core/blockchain.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
14181418
cache = bc.dao.GetPrivate()
14191419
aerCache = bc.dao.GetPrivate()
14201420
appExecResults = make([]*state.AppExecResult, 0, 2+len(block.Transactions))
1421+
txesConsumed = make(map[util.Uint256]int64, len(block.Transactions))
14211422
aerchan = make(chan *state.AppExecResult, len(block.Transactions)/8) // Tested 8 and 4 with no practical difference, but feel free to test more and tune.
14221423
aerdone = make(chan error)
14231424
)
@@ -1500,7 +1501,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
15001501
close(aerdone)
15011502
}()
15021503
_ = cache.GetItemCtx() // Prime serialization context cache (it'll be reused by upper layer DAOs).
1503-
aer, v, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist, nil)
1504+
aer, v, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist, nil, nil)
15041505
if err != nil {
15051506
// Release goroutines, don't care about errors, we already have one.
15061507
close(aerchan)
@@ -1533,6 +1534,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
15331534
zap.Error(err))
15341535
faultException = err.Error()
15351536
}
1537+
txesConsumed[tx.Hash()] = v.GasConsumed()
15361538
aer := &state.AppExecResult{
15371539
Container: tx.Hash(),
15381540
Execution: state.Execution{
@@ -1548,7 +1550,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
15481550
aerchan <- aer
15491551
}
15501552

1551-
aer, _, err = bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist, v)
1553+
aer, _, err = bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist, v, txesConsumed)
15521554
if err != nil {
15531555
// Release goroutines, don't care about errors, we already have one.
15541556
close(aerchan)
@@ -1683,13 +1685,14 @@ func (bc *Blockchain) IsExtensibleAllowed(u util.Uint160) bool {
16831685
return n < len(us)
16841686
}
16851687

1686-
func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.Simple, trig trigger.Type, v *vm.VM) (*state.AppExecResult, *vm.VM, error) {
1688+
func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.Simple, trig trigger.Type, v *vm.VM, txesConsumed map[util.Uint256]int64) (*state.AppExecResult, *vm.VM, error) {
16871689
systemInterop := bc.newInteropContext(trig, cache, block, nil)
16881690
if v == nil {
16891691
v = systemInterop.SpawnVM()
16901692
} else {
16911693
systemInterop.ReuseVM(v)
16921694
}
1695+
systemInterop.TxesConsumed = txesConsumed
16931696
v.LoadScriptWithFlags(script, callflag.All)
16941697
if err := systemInterop.Exec(); err != nil {
16951698
return nil, v, fmt.Errorf("VM has failed: %w", err)
@@ -2248,6 +2251,11 @@ func (bc *Blockchain) FeePerByte() int64 {
22482251
return bc.contracts.Policy.GetFeePerByteInternal(bc.dao)
22492252
}
22502253

2254+
// GasRefundFee returns extra fee for system fee refundable transaction
2255+
func (bc *Blockchain) SystemFeeRefundCost() int64 {
2256+
return bc.contracts.Policy.GetSystemFeeRefundCostInternal(bc.dao)
2257+
}
2258+
22512259
// GetMemPool returns the memory pool of the blockchain.
22522260
func (bc *Blockchain) GetMemPool() *mempool.Pool {
22532261
return bc.memPool
@@ -2367,6 +2375,9 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
23672375
needNetworkFee += (int64(na.NKeys) + 1) * bc.contracts.Notary.GetNotaryServiceFeePerKey(bc.dao)
23682376
}
23692377
}
2378+
if len(t.GetAttributes(transaction.RefundableSystemFeeT)) > 0 {
2379+
needNetworkFee += bc.SystemFeeRefundCost()
2380+
}
23702381
netFee := t.NetworkFee - needNetworkFee
23712382
if netFee < 0 {
23722383
return fmt.Errorf("%w: net fee is %v, need %v", ErrTxSmallNetworkFee, t.NetworkFee, needNetworkFee)
@@ -2480,6 +2491,11 @@ func (bc *Blockchain) verifyTxAttributes(d *dao.Simple, tx *transaction.Transact
24802491
if !tx.HasSigner(bc.contracts.Notary.Hash) {
24812492
return fmt.Errorf("%w: NotaryAssisted attribute was found, but transaction is not signed by the Notary native contract", ErrInvalidAttribute)
24822493
}
2494+
case transaction.RefundableSystemFeeT:
2495+
state := bc.GetContractState(tx.Sender())
2496+
if state != nil {
2497+
return fmt.Errorf("%w: RefundableSystemFee attribute was found, but transaction sender is contract", ErrInvalidAttribute)
2498+
}
24832499
default:
24842500
if !bc.config.ReservedAttributes && attrType >= transaction.ReservedLowerBound && attrType <= transaction.ReservedUpperBound {
24852501
return fmt.Errorf("%w: attribute of reserved type was found, but ReservedAttributes are disabled", ErrInvalidAttribute)

pkg/core/interop/context.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,23 @@ type Ledger interface {
4545

4646
// Context represents context in which interops are executed.
4747
type Context struct {
48-
Chain Ledger
49-
Container hash.Hashable
50-
Network uint32
51-
Hardforks map[string]uint32
52-
Natives []Contract
53-
Trigger trigger.Type
54-
Block *block.Block
55-
NonceData [16]byte
56-
Tx *transaction.Transaction
57-
DAO *dao.Simple
58-
Notifications []state.NotificationEvent
59-
Log *zap.Logger
60-
VM *vm.VM
61-
Functions []Function
62-
Invocations map[util.Uint160]int
48+
Chain Ledger
49+
Container hash.Hashable
50+
Network uint32
51+
Hardforks map[string]uint32
52+
Natives []Contract
53+
Trigger trigger.Type
54+
Block *block.Block
55+
NonceData [16]byte
56+
Tx *transaction.Transaction
57+
DAO *dao.Simple
58+
Notifications []state.NotificationEvent
59+
Log *zap.Logger
60+
VM *vm.VM
61+
Functions []Function
62+
Invocations map[util.Uint160]int
63+
TxesConsumed map[util.Uint256]int64
64+
6365
cancelFuncs []context.CancelFunc
6466
getContract func(*dao.Simple, util.Uint160) (*state.Contract, error)
6567
baseExecFee int64

pkg/core/native/native_gas.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ func (g *GAS) OnPersist(ic *interop.Context) error {
129129

130130
// PostPersist implements the Contract interface.
131131
func (g *GAS) PostPersist(ic *interop.Context) error {
132+
for _, tx := range ic.Block.Transactions {
133+
attrs := tx.GetAttributes(transaction.RefundableSystemFeeT)
134+
if len(attrs) != 0 {
135+
consumed := ic.TxesConsumed[tx.Hash()]
136+
if consumed < tx.SystemFee {
137+
g.mint(ic, tx.Sender(), big.NewInt(tx.SystemFee-consumed), false)
138+
}
139+
}
140+
}
132141
return nil
133142
}
134143

pkg/core/native/policy.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import (
2121
const (
2222
policyContractID = -7
2323

24-
defaultExecFeeFactor = interop.DefaultBaseExecFee
25-
defaultFeePerByte = 1000
26-
defaultMaxVerificationGas = 1_50000000
24+
defaultExecFeeFactor = interop.DefaultBaseExecFee
25+
defaultFeePerByte = 1000
26+
defaultMaxVerificationGas = 1_50000000
27+
defaultSystemFeeRefundCost = 0_10000000
2728
// DefaultStoragePrice is the price to pay for 1 byte of storage.
2829
DefaultStoragePrice = 100000
2930

@@ -33,7 +34,8 @@ const (
3334
maxFeePerByte = 100_000_000
3435
// maxStoragePrice is the maximum allowed price for a byte of storage.
3536
maxStoragePrice = 10000000
36-
37+
// maxSystemFeeRefundCost is the maximun allowed extra fee for gas refundable transaction
38+
maxSystemFeeRefundCost = 1_00000000
3739
// blockedAccountPrefix is a prefix used to store blocked account.
3840
blockedAccountPrefix = 15
3941
)
@@ -46,6 +48,8 @@ var (
4648
feePerByteKey = []byte{10}
4749
// storagePriceKey is a key used to store storage price.
4850
storagePriceKey = []byte{19}
51+
// systemFeeRefundCostKey is a key usesd to store gas refund fee
52+
systemFeeRefundCostKey = []byte{20}
4953
)
5054

5155
// Policy represents Policy native contract.
@@ -59,6 +63,7 @@ type PolicyCache struct {
5963
feePerByte int64
6064
maxVerificationGas int64
6165
storagePrice uint32
66+
systemFeeRefundCost int64
6267
blockedAccounts []util.Uint160
6368
}
6469

@@ -127,6 +132,11 @@ func newPolicy() *Policy {
127132
md = newMethodAndPrice(p.unblockAccount, 1<<15, callflag.States)
128133
p.AddMethod(md, desc)
129134

135+
desc = newDescriptor("setSystemFeeRefundCost", smartcontract.VoidType,
136+
manifest.NewParameter("value", smartcontract.IntegerType))
137+
md = newMethodAndPrice(p.setSystemFeeRefundCost, 1<<15, callflag.States)
138+
p.AddMethod(md, desc)
139+
130140
return p
131141
}
132142

@@ -140,12 +150,14 @@ func (p *Policy) Initialize(ic *interop.Context) error {
140150
setIntWithKey(p.ID, ic.DAO, feePerByteKey, defaultFeePerByte)
141151
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, defaultExecFeeFactor)
142152
setIntWithKey(p.ID, ic.DAO, storagePriceKey, DefaultStoragePrice)
153+
setIntWithKey(p.ID, ic.DAO, systemFeeRefundCostKey, defaultSystemFeeRefundCost)
143154

144155
cache := &PolicyCache{
145156
execFeeFactor: defaultExecFeeFactor,
146157
feePerByte: defaultFeePerByte,
147158
maxVerificationGas: defaultMaxVerificationGas,
148159
storagePrice: DefaultStoragePrice,
160+
systemFeeRefundCost: defaultSystemFeeRefundCost,
149161
blockedAccounts: make([]util.Uint160, 0),
150162
}
151163
ic.DAO.SetCache(p.ID, cache)
@@ -168,6 +180,7 @@ func (p *Policy) fillCacheFromDAO(cache *PolicyCache, d *dao.Simple) error {
168180
cache.feePerByte = getIntWithKey(p.ID, d, feePerByteKey)
169181
cache.maxVerificationGas = defaultMaxVerificationGas
170182
cache.storagePrice = uint32(getIntWithKey(p.ID, d, storagePriceKey))
183+
cache.systemFeeRefundCost = getIntWithKey(p.ID, d, systemFeeRefundCostKey)
171184

172185
cache.blockedAccounts = make([]util.Uint160, 0)
173186
var fErr error
@@ -354,6 +367,26 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac
354367
return stackitem.NewBool(true)
355368
}
356369

370+
func (p *Policy) GetSystemFeeRefundCostInternal(d *dao.Simple) int64 {
371+
cache := d.GetROCache(p.ID).(*PolicyCache)
372+
return cache.systemFeeRefundCost
373+
}
374+
375+
// setSystemFeeRefundCost is a Policy contract method that set extra network fee for gas refundable transaction.
376+
func (p *Policy) setSystemFeeRefundCost(ic *interop.Context, args []stackitem.Item) stackitem.Item {
377+
value := toBigInt(args[0]).Int64()
378+
if value < 0 || value > maxSystemFeeRefundCost {
379+
panic(fmt.Errorf("SystemFeeRefundCost shouldn't be negative or greater than %d", maxSystemFeeRefundCost))
380+
}
381+
if !p.NEO.checkCommittee(ic) {
382+
panic("invalid committee signature")
383+
}
384+
setIntWithKey(p.ID, ic.DAO, systemFeeRefundCostKey, value)
385+
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
386+
cache.systemFeeRefundCost = value
387+
return stackitem.Null{}
388+
}
389+
357390
// CheckPolicy checks whether a transaction conforms to the current policy restrictions,
358391
// like not being signed by a blocked account or not exceeding the block-level system
359392
// fee limit.

pkg/core/transaction/attribute.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ func (attr *Attribute) DecodeBinary(br *io.BinReader) {
4141
attr.Value = new(Conflicts)
4242
case NotaryAssistedT:
4343
attr.Value = new(NotaryAssisted)
44+
case RefundableSystemFeeT:
45+
attr.Value = new(RefundableSystemFee)
4446
default:
4547
if t >= ReservedLowerBound && t <= ReservedUpperBound {
4648
attr.Value = new(Reserved)
@@ -57,7 +59,7 @@ func (attr *Attribute) EncodeBinary(bw *io.BinWriter) {
5759
bw.WriteB(byte(attr.Type))
5860
switch t := attr.Type; t {
5961
case HighPriority:
60-
case OracleResponseT, NotValidBeforeT, ConflictsT, NotaryAssistedT:
62+
case OracleResponseT, NotValidBeforeT, ConflictsT, NotaryAssistedT, RefundableSystemFeeT:
6163
attr.Value.EncodeBinary(bw)
6264
default:
6365
if t >= ReservedLowerBound && t <= ReservedUpperBound {
@@ -102,6 +104,9 @@ func (attr *Attribute) UnmarshalJSON(data []byte) error {
102104
case NotaryAssistedT.String():
103105
attr.Type = NotaryAssistedT
104106
attr.Value = new(NotaryAssisted)
107+
case RefundableSystemFeeT.String():
108+
attr.Type = RefundableSystemFeeT
109+
attr.Value = new(RefundableSystemFee)
105110
default:
106111
return errors.New("wrong Type")
107112
}

pkg/core/transaction/attrtype.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ const (
1414

1515
// List of valid attribute types.
1616
const (
17-
HighPriority AttrType = 1
18-
OracleResponseT AttrType = 0x11 // OracleResponse
19-
NotValidBeforeT AttrType = 0x20 // NotValidBefore
20-
ConflictsT AttrType = 0x21 // Conflicts
21-
NotaryAssistedT AttrType = 0x22 // NotaryAssisted
17+
HighPriority AttrType = 1
18+
OracleResponseT AttrType = 0x11 // OracleResponse
19+
NotValidBeforeT AttrType = 0x20 // NotValidBefore
20+
ConflictsT AttrType = 0x21 // Conflicts
21+
NotaryAssistedT AttrType = 0x22 // NotaryAssisted
22+
RefundableSystemFeeT AttrType = 0x30 // RefundableSystemFee
2223
)
2324

2425
func (a AttrType) allowMultiple() bool {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package transaction
2+
3+
import (
4+
"github.com/nspcc-dev/neo-go/pkg/io"
5+
)
6+
7+
// Conflicts represents attribute for refund gas transaction.
8+
type RefundableSystemFee struct {
9+
}
10+
11+
// DecodeBinary implements the io.Serializable interface.
12+
func (c *RefundableSystemFee) DecodeBinary(br *io.BinReader) {
13+
}
14+
15+
// EncodeBinary implements the io.Serializable interface.
16+
func (c *RefundableSystemFee) EncodeBinary(w *io.BinWriter) {
17+
}
18+
19+
func (c *RefundableSystemFee) toJSONMap(m map[string]interface{}) {
20+
}

pkg/services/rpcsrv/server.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ type (
6767
CalculateClaimable(h util.Uint160, endHeight uint32) (*big.Int, error)
6868
CurrentBlockHash() util.Uint256
6969
FeePerByte() int64
70+
SystemFeeRefundCost() int64
7071
ForEachNEP11Transfer(acc util.Uint160, newestTimestamp uint64, f func(*state.NEP11Transfer) (bool, error)) error
7172
ForEachNEP17Transfer(acc util.Uint160, newestTimestamp uint64, f func(*state.NEP17Transfer) (bool, error)) error
7273
GetAppExecResults(util.Uint256, trigger.Type) ([]state.AppExecResult, error)
@@ -847,6 +848,9 @@ func (s *Server) calculateNetworkFee(reqParams params.Params) (interface{}, *neo
847848
netFee += (int64(na.NKeys) + 1) * s.chain.GetNotaryServiceFeePerKey()
848849
}
849850
}
851+
if len(tx.GetAttributes(transaction.RefundableSystemFeeT)) > 0 {
852+
netFee += s.chain.SystemFeeRefundCost()
853+
}
850854
fee := s.chain.FeePerByte()
851855
netFee += int64(size) * fee
852856
return result.NetworkFee{Value: netFee}, nil

0 commit comments

Comments
 (0)