-
Notifications
You must be signed in to change notification settings - Fork 44
/
start.go
309 lines (270 loc) · 13.5 KB
/
start.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
/*
* Flow Emulator
*
* Copyright 2019 Dapper Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package start
import (
"encoding/hex"
"fmt"
"log"
"os"
"strings"
"time"
"github.com/onflow/cadence"
sdk "github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/onflow/flow-go/fvm"
flowgo "github.com/onflow/flow-go/model/flow"
"github.com/psiemens/sconfig"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/onflow/flow-emulator/server"
)
type Config struct {
Port int `default:"3569" flag:"port,p" info:"port to run RPC server"`
DebuggerPort int `default:"2345" flag:"debugger-port" info:"port to run the Debugger (Debug Adapter Protocol)"`
RestPort int `default:"8888" flag:"rest-port" info:"port to run the REST API"`
AdminPort int `default:"8080" flag:"admin-port" info:"port to run the admin API"`
Verbose bool `default:"false" flag:"verbose,v" info:"enable verbose logging"`
LogFormat string `default:"text" flag:"log-format" info:"logging output format. Valid values (text, JSON)"`
BlockTime time.Duration `flag:"block-time,b" info:"time between sealed blocks, e.g. '300ms', '-1.5h' or '2h45m'. Valid units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'"`
ServicePrivateKey string `flag:"service-priv-key" info:"service account private key"`
ServicePublicKey string `flag:"service-pub-key" info:"service account public key"`
ServiceKeySigAlgo string `default:"ECDSA_P256" flag:"service-sig-algo" info:"service account key signature algorithm"`
ServiceKeyHashAlgo string `default:"SHA3_256" flag:"service-hash-algo" info:"service account key hash algorithm"`
Init bool `default:"false" flag:"init" info:"whether to initialize a new account profile"`
GRPCDebug bool `default:"false" flag:"grpc-debug" info:"enable gRPC server reflection for debugging with grpc_cli"`
RESTDebug bool `default:"false" flag:"rest-debug" info:"enable REST API debugging output"`
Persist bool `default:"false" flag:"persist" info:"enable persistent storage"`
Snapshot bool `default:"false" flag:"snapshot" info:"enable snapshots for emulator"`
DBPath string `default:"./flowdb" flag:"dbpath" info:"path to database directory"`
SimpleAddresses bool `default:"false" flag:"simple-addresses" info:"use sequential addresses starting with 0x01"`
TokenSupply string `default:"1000000000.0" flag:"token-supply" info:"initial FLOW token supply"`
TransactionExpiry int `default:"10" flag:"transaction-expiry" info:"transaction expiry, measured in blocks"`
StorageLimitEnabled bool `default:"true" flag:"storage-limit" info:"enable account storage limit"`
StorageMBPerFLOW string `flag:"storage-per-flow" info:"the MB amount of storage capacity an account has per 1 FLOW token it has. e.g. '100.0'. The default is taken from the current version of flow-go"`
MinimumAccountBalance string `flag:"min-account-balance" info:"The minimum account balance of an account. This is also the cost of creating one account. e.g. '0.001'. The default is taken from the current version of flow-go"`
TransactionFeesEnabled bool `default:"false" flag:"transaction-fees" info:"enable transaction fees"`
TransactionMaxGasLimit int `default:"9999" flag:"transaction-max-gas-limit" info:"maximum gas limit for transactions"`
ScriptGasLimit int `default:"100000" flag:"script-gas-limit" info:"gas limit for scripts"`
Contracts bool `default:"false" flag:"contracts" info:"deploy common contracts when emulator starts"`
ContractRemovalEnabled bool `default:"true" flag:"contract-removal" info:"allow removal of already deployed contracts, used for updating during development"`
SkipTxValidation bool `default:"false" flag:"skip-tx-validation" info:"skip verification of transaction signatures and sequence numbers"`
Host string `default:"" flag:"host" info:"host to listen on for emulator GRPC/REST/Admin servers (default: all interfaces)"`
ChainID string `default:"emulator" flag:"chain-id" info:"chain to emulate for address generation. Valid values are: 'emulator', 'testnet', 'mainnet'"`
RedisURL string `default:"" flag:"redis-url" info:"redis-server URL for persisting redis storage backend ( redis://[[username:]password@]host[:port][/database] ) "`
SqliteURL string `default:"" flag:"sqlite-url" info:"sqlite db URL for persisting sqlite storage backend "`
CoverageReportingEnabled bool `default:"false" flag:"coverage-reporting" info:"enable Cadence code coverage reporting"`
LegacyContractUpgradeEnabled bool `default:"false" flag:"legacy-upgrade" info:"enable Cadence legacy contract upgrade"`
StartBlockHeight uint64 `default:"0" flag:"start-block-height" info:"block height to start the emulator at. only valid when forking Mainnet or Testnet"`
RPCHost string `default:"" flag:"rpc-host" info:"rpc host to query when forking Mainnet or Testnet"`
CheckpointPath string `default:"" flag:"checkpoint-dir" info:"checkpoint directory to load the emulator state from"`
StateHash string `default:"" flag:"state-hash" info:"state hash of the checkpoint to load the emulator state from"`
ComputationReportingEnabled bool `default:"false" flag:"computation-reporting" info:"enable Cadence computation reporting"`
}
const EnvPrefix = "FLOW"
var conf Config
type serviceKeyFunc func(
init bool,
sigAlgo crypto.SignatureAlgorithm,
hashAlgo crypto.HashAlgorithm,
) (crypto.PrivateKey, crypto.SignatureAlgorithm, crypto.HashAlgorithm)
func Cmd(getServiceKey serviceKeyFunc) *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "Starts the Flow emulator server",
Run: func(cmd *cobra.Command, args []string) {
var (
servicePrivateKey crypto.PrivateKey
servicePublicKey crypto.PublicKey
serviceKeySigAlgo crypto.SignatureAlgorithm
serviceKeyHashAlgo crypto.HashAlgorithm
err error
)
serviceKeySigAlgo = crypto.StringToSignatureAlgorithm(conf.ServiceKeySigAlgo)
serviceKeyHashAlgo = crypto.StringToHashAlgorithm(conf.ServiceKeyHashAlgo)
logger := initLogger(conf.Verbose)
if conf.ServicePublicKey != "" {
logger.Warn().Msg("❗ Providing '--public-key' is deprecated, provide the '--private-key' only.")
}
if conf.ServicePrivateKey != "" {
checkKeyAlgorithms(serviceKeySigAlgo, serviceKeyHashAlgo)
servicePrivateKey, err = crypto.DecodePrivateKeyHex(serviceKeySigAlgo, conf.ServicePrivateKey)
if err != nil {
Exit(1, err.Error())
}
servicePublicKey = servicePrivateKey.PublicKey()
} else { // if we don't provide any config values use the serviceKeyFunc to obtain the key
servicePrivateKey, serviceKeySigAlgo, serviceKeyHashAlgo = getServiceKey(
conf.Init,
serviceKeySigAlgo,
serviceKeyHashAlgo,
)
servicePublicKey = servicePrivateKey.PublicKey()
}
flowChainID, err := getSDKChainID(conf.ChainID)
if err != nil {
Exit(1, err.Error())
}
if conf.StartBlockHeight > 0 && flowChainID != flowgo.Mainnet && flowChainID != flowgo.Testnet {
Exit(1, "❗ --start-block-height is only valid when forking Mainnet or Testnet")
}
if (flowChainID == flowgo.Mainnet || flowChainID == flowgo.Testnet) && conf.RPCHost == "" {
Exit(1, "❗ --rpc-host must be provided when forking Mainnet or Testnet")
}
serviceAddress := sdk.ServiceAddress(sdk.ChainID(flowChainID))
if conf.SimpleAddresses {
serviceAddress = sdk.HexToAddress("0x1")
}
serviceFields := map[string]any{
"serviceAddress": serviceAddress.Hex(),
"servicePubKey": hex.EncodeToString(servicePublicKey.Encode()),
"serviceSigAlgo": serviceKeySigAlgo.String(),
"serviceHashAlgo": serviceKeyHashAlgo.String(),
}
if servicePrivateKey != nil {
serviceFields["servicePrivKey"] = hex.EncodeToString(servicePrivateKey.Encode())
}
logger.Info().Fields(serviceFields).Msgf("⚙️ Using service account 0x%s", serviceAddress.Hex())
minimumStorageReservation := fvm.DefaultMinimumStorageReservation
if conf.MinimumAccountBalance != "" {
minimumStorageReservation = parseCadenceUFix64(conf.MinimumAccountBalance, "min-account-balance")
}
storageMBPerFLOW := fvm.DefaultStorageMBPerFLOW
if conf.StorageMBPerFLOW != "" {
storageMBPerFLOW = parseCadenceUFix64(conf.StorageMBPerFLOW, "storage-per-flow")
}
serverConf := &server.Config{
GRPCPort: conf.Port,
GRPCDebug: conf.GRPCDebug,
AdminPort: conf.AdminPort,
DebuggerPort: conf.DebuggerPort,
RESTPort: conf.RestPort,
RESTDebug: conf.RESTDebug,
// TODO: allow headers to be parsed from environment
HTTPHeaders: nil,
BlockTime: conf.BlockTime,
ServicePublicKey: servicePublicKey,
ServicePrivateKey: servicePrivateKey,
ServiceKeySigAlgo: serviceKeySigAlgo,
ServiceKeyHashAlgo: serviceKeyHashAlgo,
Persist: conf.Persist,
Snapshot: conf.Snapshot,
DBPath: conf.DBPath,
GenesisTokenSupply: parseCadenceUFix64(conf.TokenSupply, "token-supply"),
TransactionMaxGasLimit: uint64(conf.TransactionMaxGasLimit),
ScriptGasLimit: uint64(conf.ScriptGasLimit),
TransactionExpiry: uint(conf.TransactionExpiry),
StorageLimitEnabled: conf.StorageLimitEnabled,
StorageMBPerFLOW: storageMBPerFLOW,
MinimumStorageReservation: minimumStorageReservation,
TransactionFeesEnabled: conf.TransactionFeesEnabled,
WithContracts: conf.Contracts,
SkipTransactionValidation: conf.SkipTxValidation,
SimpleAddressesEnabled: conf.SimpleAddresses,
Host: conf.Host,
ChainID: flowChainID,
RedisURL: conf.RedisURL,
ContractRemovalEnabled: conf.ContractRemovalEnabled,
SqliteURL: conf.SqliteURL,
CoverageReportingEnabled: conf.CoverageReportingEnabled,
LegacyContractUpgradeEnabled: conf.LegacyContractUpgradeEnabled,
StartBlockHeight: conf.StartBlockHeight,
RPCHost: conf.RPCHost,
CheckpointPath: conf.CheckpointPath,
StateHash: conf.StateHash,
ComputationReportingEnabled: conf.ComputationReportingEnabled,
}
emu := server.NewEmulatorServer(logger, serverConf)
if emu != nil {
emu.Start()
} else {
Exit(-1, "")
}
},
}
initConfig(cmd)
return cmd
}
func initLogger(verbose bool) *zerolog.Logger {
level := zerolog.InfoLevel
if verbose {
level = zerolog.DebugLevel
}
zerolog.MessageFieldName = "msg"
switch strings.ToLower(conf.LogFormat) {
case "json":
logger := zerolog.New(os.Stdout).With().Timestamp().Logger().Level(level)
return &logger
default:
writer := zerolog.ConsoleWriter{Out: os.Stdout}
writer.FormatMessage = func(i interface{}) string {
if i == nil {
return ""
}
return fmt.Sprintf("%-44s", i)
}
logger := zerolog.New(writer).With().Timestamp().Logger().Level(level)
return &logger
}
}
func initConfig(cmd *cobra.Command) {
err := sconfig.New(&conf).
FromEnvironment(EnvPrefix).
BindFlags(cmd.PersistentFlags()).
Parse()
if err != nil {
log.Fatal(err)
}
}
func Exit(code int, msg string) {
fmt.Fprintln(os.Stderr, msg)
os.Exit(code)
}
func parseCadenceUFix64(value string, valueName string) cadence.UFix64 {
tokenSupply, err := cadence.NewUFix64(value)
if err != nil {
Exit(
1,
fmt.Sprintf(
"Failed to parse %s from value `%s` as an unsigned 64-bit fixed-point number: %s",
valueName,
conf.TokenSupply,
err.Error()),
)
}
return tokenSupply
}
func getSDKChainID(chainID string) (flowgo.ChainID, error) {
switch chainID {
case "emulator":
return flowgo.Emulator, nil
case "testnet":
return flowgo.Testnet, nil
case "mainnet":
return flowgo.Mainnet, nil
default:
return "", fmt.Errorf("invalid ChainID %s, valid values are: emulator, testnet, mainnet", chainID)
}
}
func checkKeyAlgorithms(sigAlgo crypto.SignatureAlgorithm, hashAlgo crypto.HashAlgorithm) {
if sigAlgo == crypto.UnknownSignatureAlgorithm {
Exit(1, "Must specify service key signature algorithm (e.g. --service-sig-algo=ECDSA_P256)")
}
if hashAlgo == crypto.UnknownHashAlgorithm {
Exit(1, "Must specify service key hash algorithm (e.g. --service-hash-algo=SHA3_256)")
}
}