-
Notifications
You must be signed in to change notification settings - Fork 107
/
min_transact_balance.go
256 lines (226 loc) · 8.03 KB
/
min_transact_balance.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
package e2e
import (
"context"
"fmt"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
memorySigner "github.com/oasisprotocol/oasis-core/go/common/crypto/signature/signers/memory"
"github.com/oasisprotocol/oasis-core/go/common/entity"
"github.com/oasisprotocol/oasis-core/go/common/quantity"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
"github.com/oasisprotocol/oasis-core/go/consensus/api/transaction"
tmbeacon "github.com/oasisprotocol/oasis-core/go/consensus/tendermint/beacon"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/scenario"
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
)
var (
MinTransactBalance scenario.Scenario = &minTransactBalanceImpl{
Scenario: *NewScenario("min-transact-balance"),
}
a1Signer = memorySigner.NewTestSigner("e2e/min-transact-balance: a1")
a1Addr = staking.NewAddress(a1Signer.Public())
)
type minTransactBalanceImpl struct {
Scenario
}
func (mtb *minTransactBalanceImpl) signAndSubmitTx(ctx context.Context, signer signature.Signer, tx *transaction.Transaction) error {
signed, err := transaction.Sign(signer, tx)
if err != nil {
return fmt.Errorf("sign: %w", err)
}
if mtb.Net.Controller().Consensus.SubmitTx(ctx, signed) != nil {
return fmt.Errorf("submit tx: %w", err)
}
return nil
}
func (mtb *minTransactBalanceImpl) signAndSubmitTxShouldFail(ctx context.Context, signer signature.Signer, tx *transaction.Transaction) error {
signed, err := transaction.Sign(signer, tx)
if err != nil {
return fmt.Errorf("sign: %w", err)
}
err = mtb.Net.Controller().Consensus.SubmitTx(ctx, signed)
if err == nil {
return fmt.Errorf("transaction succeeded but should not have")
}
mtb.Logger.Info("transaction failed as expected",
"err", err,
)
return nil
}
func (mtb *minTransactBalanceImpl) fundFromTestEntity(ctx context.Context, nonce uint64, to staking.Address, amount uint64) error {
_, teSigner, err := entity.TestEntity()
if err != nil {
return fmt.Errorf("test entity: %w", err)
}
if err = mtb.signAndSubmitTx(ctx, teSigner, staking.NewTransferTx(nonce, &transaction.Fee{
Gas: 1300,
}, &staking.Transfer{
To: to,
Amount: *quantity.NewFromUint64(amount),
})); err != nil {
return err
}
return nil
}
func (mtb *minTransactBalanceImpl) getAccountAndCheckNonce(ctx context.Context, addr staking.Address, expected uint64) (*staking.Account, error) {
query := staking.OwnerQuery{
Owner: addr,
Height: consensus.HeightLatest,
}
acct, err := mtb.Net.Controller().Staking.Account(ctx, &query)
if err != nil {
return nil, fmt.Errorf("account: %w", err)
}
return acct, nil
}
func (mtb *minTransactBalanceImpl) Clone() scenario.Scenario {
return &minTransactBalanceImpl{
Scenario: mtb.Scenario.Clone(),
}
}
func (mtb *minTransactBalanceImpl) Fixture() (*oasis.NetworkFixture, error) {
f, err := mtb.Scenario.Fixture()
if err != nil {
return nil, err
}
beaconSignerAddr := staking.NewAddress(tmbeacon.TestSigner.Public())
f.Network.StakingGenesis = &staking.Genesis{
Parameters: staking.ConsensusParameters{
MinTransactBalance: *quantity.NewFromUint64(1000),
},
TotalSupply: *quantity.NewFromUint64(1000),
Ledger: map[staking.Address]*staking.Account{
beaconSignerAddr: {
General: staking.GeneralAccount{
Balance: *quantity.NewFromUint64(1000),
},
},
},
}
// Use mock epoch so we can test node re-registration.
f.Network.SetMockEpoch()
return f, nil
}
func (mtb *minTransactBalanceImpl) Run(ctx context.Context, childEnv *env.Env) error {
// Start the network
if err := mtb.Net.Start(); err != nil {
return err
}
mtb.Logger.Info("waiting for network to come up")
if err := mtb.Net.Controller().WaitNodesRegistered(ctx, 3); err != nil {
return fmt.Errorf("WaitNodesRegistered: %w", err)
}
var teNonce uint64
for i, validator := range mtb.Net.Validators() {
mtb.Logger.Info("funding validator",
"validator_index", i,
)
identity, err := validator.LoadIdentity()
if err != nil {
return fmt.Errorf("funding validator %d LoadIdentity: %w", i, err)
}
nodeAddr := staking.NewAddress(identity.NodeSigner.Public())
if err = mtb.fundFromTestEntity(ctx, teNonce, nodeAddr, 1000); err != nil {
return fmt.Errorf("funding validator %d: %w", i, err)
}
teNonce++
}
// Advance epoch to make sure node can re-register.
mtb.Logger.Info("moving to epoch 1")
if err := mtb.Net.Controller().SetEpoch(ctx, 1); err != nil {
return fmt.Errorf("SetEpoch 1: %w", err)
}
// In the genesis file, nodes are registered to expire at epoch 1, which
// should make it impossible to elect validators for epoch 2 if they never
// re-register successfully.
mtb.Logger.Info("moving to epoch 2")
if err := mtb.Net.Controller().SetEpoch(ctx, 2); err != nil {
return fmt.Errorf("SetEpoch 2: %w", err)
}
mtb.Logger.Info("epoch transitions succeeded")
// Start with no account.
mtb.Logger.Info("checking nonce")
var a1Nonce uint64
if _, err := mtb.getAccountAndCheckNonce(ctx, a1Addr, a1Nonce); err != nil {
return fmt.Errorf("a1 before burn below min: %w", err)
}
// Try a transaction with no balance.
mtb.Logger.Info("burning below min")
if err := mtb.signAndSubmitTxShouldFail(ctx, a1Signer, staking.NewBurnTx(a1Nonce, &transaction.Fee{
Gas: 1300,
}, &staking.Burn{
Amount: *quantity.NewFromUint64(0),
})); err != nil {
return fmt.Errorf("burn below min: %w", err)
}
// Account should not be created as a result.
mtb.Logger.Info("checking nonce")
if _, err := mtb.getAccountAndCheckNonce(ctx, a1Addr, a1Nonce); err != nil {
return fmt.Errorf("a1 after burn below min: %w", err)
}
// Bring account up to minimum balance.
mtb.Logger.Info("bringing account up to min")
if err := mtb.fundFromTestEntity(ctx, teNonce, a1Addr, 1000); err != nil {
return fmt.Errorf("bringup: %w", err)
}
teNonce++
// Try a transaction at minimum balance.
mtb.Logger.Info("burning at min")
if err := mtb.signAndSubmitTx(ctx, a1Signer, staking.NewBurnTx(a1Nonce, &transaction.Fee{
Gas: 1300,
}, &staking.Burn{
Amount: *quantity.NewFromUint64(0),
})); err != nil {
return fmt.Errorf("burn at min: %w", err)
}
a1Nonce++
// Nonce should go up.
mtb.Logger.Info("checking nonce")
if _, err := mtb.getAccountAndCheckNonce(ctx, a1Addr, a1Nonce); err != nil {
return fmt.Errorf("a1 after burn at min: %w", err)
}
// Try a transaction with fee that would bring balance below minimum.
mtb.Logger.Info("burning with fee that would bring an account below min")
if err := mtb.signAndSubmitTxShouldFail(ctx, a1Signer, staking.NewBurnTx(a1Nonce, &transaction.Fee{
Gas: 1300,
Amount: *quantity.NewFromUint64(1),
}, &staking.Burn{
Amount: *quantity.NewFromUint64(0),
})); err != nil {
return fmt.Errorf("burn with fee: %w", err)
}
// Nonce should stay the same.
mtb.Logger.Info("checking nonce")
if _, err := mtb.getAccountAndCheckNonce(ctx, a1Addr, a1Nonce); err != nil {
return fmt.Errorf("a1 after burn with fee: %w", err)
}
// Bring up the balance some more.
mtb.Logger.Info("brining account up above min")
if err := mtb.fundFromTestEntity(ctx, teNonce, a1Addr, 1); err != nil {
return fmt.Errorf("extra: %w", err)
}
// Try a transaction that fails.
mtb.Logger.Info("burning in a way that would fail")
if err := mtb.signAndSubmitTxShouldFail(ctx, a1Signer, staking.NewBurnTx(a1Nonce, &transaction.Fee{
Gas: 1300,
Amount: *quantity.NewFromUint64(1),
}, &staking.Burn{
Amount: *quantity.NewFromUint64(2),
})); err != nil {
return fmt.Errorf("burn that fails: %w", err)
}
a1Nonce++
// Nonce should go up.
mtb.Logger.Info("checking nonce")
a1Acct, err := mtb.getAccountAndCheckNonce(ctx, a1Addr, a1Nonce)
if err != nil {
return fmt.Errorf("a1 after burn that fails: %w", err)
}
// Failed transaction should have no effect, but fee should be taken.
balanceRef := quantity.NewFromUint64(1000)
if a1Acct.General.Balance.Cmp(balanceRef) != 0 {
return fmt.Errorf("a1 after burn that fails wrong balance %v, expected %v", a1Acct.General.Balance, balanceRef)
}
return nil
}