-
Notifications
You must be signed in to change notification settings - Fork 115
/
Copy pathgas.go
159 lines (133 loc) · 4.74 KB
/
gas.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
package state
import (
"fmt"
"math"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
"github.com/oasisprotocol/oasis-core/go/common/quantity"
"github.com/oasisprotocol/oasis-core/go/consensus/api/transaction"
abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api"
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
)
// feeAccumulatorKey is the block context key.
type feeAccumulatorKey struct{}
func (fak feeAccumulatorKey) NewDefault() interface{} {
return &feeAccumulator{}
}
// feeAccumulator is the per-block fee accumulator that gets all fees paid
// in a block.
type feeAccumulator struct {
balance quantity.Quantity
}
// AuthenticateAndPayFees authenticates the message signer and makes sure that
// any gas fees are paid.
//
// This method transfers the fees to the per-block fee accumulator which is
// persisted at the end of the block.
func AuthenticateAndPayFees(
ctx *abciAPI.Context,
signer signature.PublicKey,
nonce uint64,
fee *transaction.Fee,
) error {
state := NewMutableState(ctx.State())
if ctx.IsSimulation() {
// If this is a simulation, the caller can use any amount of gas (as we usually want to
// estimate the amount of gas needed).
ctx.SetGasAccountant(abciAPI.NewGasAccountant(transaction.Gas(math.MaxUint64)))
return nil
}
// Convert signer's public key to account address.
addr := staking.NewAddress(signer)
if addr.IsReserved() {
return fmt.Errorf("using reserved account address %s is prohibited", addr)
}
// Fetch account and make sure the nonce is correct.
account, err := state.Account(ctx, addr)
if err != nil {
return fmt.Errorf("failed to fetch account state: %w", err)
}
if account.General.Nonce != nonce {
logger.Error("invalid account nonce",
"account_addr", addr,
"account_nonce", account.General.Nonce,
"nonce", nonce,
)
return transaction.ErrInvalidNonce
}
if fee == nil {
fee = &transaction.Fee{}
}
// Account must have enough to pay fee and maintain minimum balance.
needed := fee.Amount.Clone()
params, err := state.ConsensusParameters(ctx)
if err != nil {
return fmt.Errorf("failed to fetch staking consensus parameters: %w", err)
}
if err = needed.Add(¶ms.MinTransactBalance); err != nil {
return fmt.Errorf("adding MinTransactBalance to fee: %w", err)
}
// Check against minimum balance plus fee.
if account.General.Balance.Cmp(needed) < 0 {
logger.Error("account balance too low",
"account_addr", addr,
"account_balance", account.General.Balance,
"min_transact_balance", params.MinTransactBalance,
"fee_amount", fee.Amount,
)
return staking.ErrBalanceTooLow
}
if ctx.IsCheckOnly() {
// Configure gas accountant on the context so that we can report gas wanted.
ctx.SetGasAccountant(abciAPI.NewGasAccountant(fee.Gas))
// Check fee against minimum gas price if in CheckTx. Always accept own transactions.
// NOTE: This is non-deterministic as it is derived from the local validator
// configuration, but as long as it is only done in CheckTx, this is ok.
if !ctx.AppState().OwnTxSignerAddress().Equal(addr) {
callerGasPrice := fee.GasPrice()
if fee.Gas > 0 && callerGasPrice.Cmp(ctx.AppState().MinGasPrice()) < 0 {
return transaction.ErrGasPriceTooLow
}
}
return nil
}
// Transfer fee to per-block fee accumulator.
feeAcc := ctx.BlockContext().Get(feeAccumulatorKey{}).(*feeAccumulator)
if err = quantity.Move(&feeAcc.balance, &account.General.Balance, &fee.Amount); err != nil {
return fmt.Errorf("staking: failed to pay fees: %w", err)
}
account.General.Nonce++
if err := state.SetAccount(ctx, addr, account); err != nil {
return fmt.Errorf("failed to set account: %w", err)
}
// Emit transfer event if fee is non-zero.
if !fee.Amount.IsZero() {
ctx.EmitEvent(abciAPI.NewEventBuilder(AppName).TypedAttribute(&staking.TransferEvent{
From: addr,
To: staking.FeeAccumulatorAddress,
Amount: fee.Amount,
}))
}
// Configure gas accountant on the context.
ctx.SetGasAccountant(abciAPI.NewCompositeGasAccountant(
abciAPI.NewGasAccountant(fee.Gas),
ctx.BlockContext().GasAccountant,
))
return nil
}
// BlockFees returns the accumulated fee balance for the current block.
func BlockFees(ctx *abciAPI.Context) quantity.Quantity {
// Fetch accumulated fees in the current block.
return ctx.BlockContext().Get(feeAccumulatorKey{}).(*feeAccumulator).balance
}
// proposerKey is the block context key.
type proposerKey struct{}
func (pk proposerKey) NewDefault() interface{} {
var empty *signature.PublicKey
return empty
}
func SetBlockProposer(ctx *abciAPI.Context, p *signature.PublicKey) {
ctx.BlockContext().Set(proposerKey{}, p)
}
func BlockProposer(ctx *abciAPI.Context) *signature.PublicKey {
return ctx.BlockContext().Get(proposerKey{}).(*signature.PublicKey)
}