/
workload.go
179 lines (153 loc) · 5.3 KB
/
workload.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
package workload
import (
"context"
"fmt"
"math/rand"
"time"
flag "github.com/spf13/pflag"
"google.golang.org/grpc"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
"github.com/oasisprotocol/oasis-core/go/common/entity"
"github.com/oasisprotocol/oasis-core/go/common/logging"
"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"
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
)
const (
maxSubmissionRetryElapsedTime = 120 * time.Second
fundAccountAmount = 10000000000
)
// ByName is the registry of workloads that you can access with `--workload <name>` on the command line.
var ByName = map[string]Workload{
NameCommission: Commission,
NameDelegation: Delegation,
NameOversized: Oversized,
NameParallel: Parallel,
NameQueries: Queries,
NameRegistration: Registration,
NameRuntime: Runtime,
NameTransfer: Transfer,
NameGovernance: Governance,
}
// Flags has the workload flags.
var Flags = flag.NewFlagSet("", flag.ContinueOnError)
// Workload is a DRBG-backed schedule of transactions.
type Workload interface {
// NeedsFunds should return true if the workload requires funding.
NeedsFunds() bool
// Run executes the workload.
// If `gracefulExit`'s deadline passes, it is not an error.
// Return `nil` after any short-ish amount of time in that case.
// Prefer to do at least one "iteration" even so.
Run(
gracefulExit context.Context,
rng *rand.Rand,
conn *grpc.ClientConn,
cnsc consensus.ClientBackend,
sm consensus.SubmissionManager,
fundingAccount signature.Signer,
validatorEntities []signature.Signer,
) error
}
// BaseWorkload provides common methods for a workload.
type BaseWorkload struct {
// Logger is the logger for the workload.
Logger *logging.Logger
cc consensus.ClientBackend
sm consensus.SubmissionManager
fundingAccount signature.Signer
}
// Init initializes the base workload.
func (bw *BaseWorkload) Init(
cc consensus.ClientBackend,
sm consensus.SubmissionManager,
fundingAccount signature.Signer,
) {
bw.cc = cc
bw.sm = sm
bw.fundingAccount = fundingAccount
}
// Consensus returns the consensus client backend.
func (bw *BaseWorkload) Consensus() consensus.ClientBackend {
return bw.cc
}
// GasPrice returns the configured consensus gas price.
func (bw *BaseWorkload) GasPrice() uint64 {
// NOTE: This cannot fail as workloads use static price discovery.
gasPrice, _ := bw.sm.PriceDiscovery().GasPrice(context.Background())
return gasPrice.ToBigInt().Uint64()
}
// FundSignAndSubmitTx funds the caller to cover transaction fees, signs the transaction and submits
// it to the consensus layer.
func (bw *BaseWorkload) FundSignAndSubmitTx(ctx context.Context, caller signature.Signer, tx *transaction.Transaction) error {
// Estimate fee.
if err := bw.sm.EstimateGasAndSetFee(ctx, caller, tx); err != nil {
return fmt.Errorf("failed to estimate fee: %w", err)
}
// Fund caller to cover transaction fees.
callerAddr := staking.NewAddress(caller.Public())
if err := bw.TransferFundsQty(ctx, bw.fundingAccount, callerAddr, &tx.Fee.Amount); err != nil {
return fmt.Errorf("account funding failure: %w", err)
}
bw.Logger.Debug("submitting transaction",
"tx", tx,
"tx_caller", caller.Public(),
)
submitCtx, cancel := context.WithTimeout(ctx, maxSubmissionRetryElapsedTime)
defer cancel()
if err := bw.sm.SignAndSubmitTx(submitCtx, caller, tx); err != nil {
bw.Logger.Error("failed to submit transaction",
"err", err,
"tx", tx,
"tx_caller", caller.Public(),
)
return fmt.Errorf("failed to submit transaction: %w", err)
}
return nil
}
// TransferFunds transfers funds from one account to the other.
func (bw *BaseWorkload) TransferFunds(ctx context.Context, from signature.Signer, to staking.Address, amount uint64) error {
return bw.TransferFundsQty(ctx, from, to, quantity.NewFromUint64(amount))
}
// TransferFundsQty transfers funds from one account to the other, taking a Quantity amount.
func (bw *BaseWorkload) TransferFundsQty(ctx context.Context, from signature.Signer, to staking.Address, amount *quantity.Quantity) error {
tx := staking.NewTransferTx(0, nil, &staking.Transfer{
To: to,
Amount: *amount,
})
submitCtx, cancel := context.WithTimeout(ctx, maxSubmissionRetryElapsedTime)
defer cancel()
if err := bw.sm.SignAndSubmitTx(submitCtx, from, tx); err != nil {
bw.Logger.Error("failed to submit transaction",
"err", err,
"tx", tx,
"tx_caller", from.Public(),
)
return fmt.Errorf("failed to submit transaction: %w", err)
}
return nil
}
// NewBaseWorkload creates a new BaseWorkload.
func NewBaseWorkload(name string) BaseWorkload {
return BaseWorkload{
Logger: logging.GetLogger("cmd/txsource/workload/" + name),
}
}
// FundAccountFromTestEntity funds an account from test entity.
func FundAccountFromTestEntity(
ctx context.Context,
cc consensus.ClientBackend,
sm consensus.SubmissionManager,
to signature.Signer,
) error {
_, testEntitySigner, _ := entity.TestEntity()
toAddr := staking.NewAddress(to.Public())
bw := NewBaseWorkload("funding")
bw.Init(cc, sm, testEntitySigner)
return bw.TransferFunds(ctx, testEntitySigner, toAddr, fundAccountAmount)
}
func init() {
Flags.AddFlagSet(QueriesFlags)
Flags.AddFlagSet(RuntimeFlags)
}