Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ values.
| `--computation-reporting` | `FLOW_COMPUTATIONREPORTING` | `false` | Enable computation reporting for Cadence scripts & transactions |
| `--checkpoint-dir` | `FLOW_CHECKPOINTDIR` | '' | Checkpoint directory to load the emulator state from, if starting the emulator from a checkpoint |
| `--state-hash` | `FLOW_STATEHASH` | '' | State hash of the checkpoint, if starting the emulator from a checkpoint |
| `--num-accounts` | `FLOW_NUMACCOUNTS` | `0` | Precreate and fund this many accounts at startup (mints 1000.0 FLOW to each) |

## Running the emulator with the Flow CLI

Expand Down
2 changes: 2 additions & 0 deletions cmd/emulator/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type Config struct {
ScheduledTransactionsEnabled bool `default:"true" flag:"scheduled-transactions" info:"enable Cadence scheduled transactions"`
SetupEVMEnabled bool `default:"true" flag:"setup-evm" info:"enable EVM setup for the emulator, this will deploy the EVM contracts"`
SetupVMBridgeEnabled bool `default:"true" flag:"setup-vm-bridge" info:"enable VM Bridge setup for the emulator, this will deploy the VM Bridge contracts"`
NumAccounts int `default:"0" flag:"num-accounts" info:"number of precreated accounts at startup"`
}

const EnvPrefix = "FLOW"
Expand Down Expand Up @@ -225,6 +226,7 @@ func Cmd(config StartConfig) *cobra.Command {
ScheduledTransactionsEnabled: conf.ScheduledTransactionsEnabled,
SetupEVMEnabled: conf.SetupEVMEnabled,
SetupVMBridgeEnabled: conf.SetupVMBridgeEnabled,
NumAccounts: conf.NumAccounts,
}

emu := server.NewEmulatorServer(logger, serverConf)
Expand Down
1 change: 1 addition & 0 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ values.
| `--redis-url` | `FLOW_REDIS_URL` | '' | Redis-server URL for persisting redis storage backend ( `redis://[[username:]password@]host[:port][/database]` ) |
| `--start-block-height` | `FLOW_STARTBLOCKHEIGHT` | `0` | Start block height to use when starting the network using 'testnet' or 'mainnet' as the chain-id |
| `--rpc-host` | `FLOW_RPCHOST` | '' | RPC host (access node) to query for previous state when starting the network using 'testnet' or 'mainnet' as the chain-id |
| `--num-accounts` | `FLOW_NUMACCOUNTS` | `0` | Precreate and fund this many accounts at startup (mints 1000.0 FLOW to each) |

## Running the emulator with the Flow CLI

Expand Down
161 changes: 161 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ import (

"github.com/onflow/cadence"
"github.com/onflow/cadence/runtime"
"github.com/onflow/cadence/stdlib"
flowsdk "github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/onflow/flow-go-sdk/templates"
"github.com/onflow/flow-go/fvm/systemcontracts"
flowgo "github.com/onflow/flow-go/model/flow"
"github.com/psiemens/graceland"
"github.com/rs/zerolog"

"github.com/onflow/flow-emulator/adapters"
"github.com/onflow/flow-emulator/convert"
"github.com/onflow/flow-emulator/emulator"
"github.com/onflow/flow-emulator/server/access"
"github.com/onflow/flow-emulator/server/debugger"
Expand Down Expand Up @@ -155,6 +159,8 @@ type Config struct {
SetupEVMEnabled bool
// SetupVMBridgeEnabled enables the VM bridge setup for the emulator, defaults to true.
SetupVMBridgeEnabled bool
// NumAccounts specifies how many accounts to precreate and fund at startup.
NumAccounts int
}

type listener interface {
Expand Down Expand Up @@ -210,6 +216,161 @@ func NewEmulatorServer(logger *zerolog.Logger, conf *Config) *EmulatorServer {
}
}

// Precreate accounts if requested
if conf.NumAccounts > 0 {
// Funding amount (fixed, not configurable)
fundAmount := "1000.0"

// Fetch system contract addresses
env := systemcontracts.SystemContractsForChain(chain.ChainID()).AsTemplateEnv()

logger.Info().Msg("\nAvailable Accounts\n==================")

serviceKey := emulatedBlockchain.ServiceKey()
servicePrivHex := fmt.Sprintf("0x%X", serviceKey.PrivateKey.Encode())

for i := 0; i < conf.NumAccounts; i++ {
// Create account using the same key as the service account
latestBlock, err := emulatedBlockchain.GetLatestBlock()
if err != nil {
logger.Error().Err(err).Msg("❗ Failed to get latest block for account creation")
continue
}
createTx, err := templates.CreateAccount(
[]*flowsdk.AccountKey{serviceKey.AccountKey()},
nil,
serviceKey.Address,
)
if err != nil {
logger.Error().Err(err).Msg("❗ Failed to build account creation transaction")
continue
}
createTx.
SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit).
SetReferenceBlockID(flowsdk.Identifier(latestBlock.ID())).
SetProposalKey(serviceKey.Address, serviceKey.Index, serviceKey.SequenceNumber).
SetPayer(serviceKey.Address)

signer, err := serviceKey.Signer()
if err != nil {
logger.Error().Err(err).Msg("❗ Failed to get service key signer for account creation")
continue
}
if err := createTx.SignEnvelope(serviceKey.Address, serviceKey.Index, signer); err != nil {
logger.Error().Err(err).Msg("❗ Failed to sign account creation transaction")
continue
}

if err := emulatedBlockchain.AddTransaction(*convert.SDKTransactionToFlow(*createTx)); err != nil {
logger.Error().Err(err).Msg("❗ Failed to submit account creation transaction")
continue
}
_, results, err := emulatedBlockchain.ExecuteAndCommitBlock()
if err != nil || len(results) == 0 || !results[len(results)-1].Succeeded() {
logger.Error().Err(err).Msg("❗ Failed to execute account creation transaction")
continue
}
if _, err := emulatedBlockchain.CommitBlock(); err != nil {
logger.Error().Err(err).Msg("❗ Failed to commit block after account creation")
continue
}

// Extract new account address from events
var newAddr flowsdk.Address
last := results[len(results)-1]
for _, ev := range last.Events {
if ev.Type == flowsdk.EventAccountCreated {
addrFieldValue := cadence.SearchFieldByName(ev.Value, stdlib.AccountEventAddressParameter.Identifier)
if cadenceAddr, ok := addrFieldValue.(cadence.Address); ok {
newAddr = flowsdk.Address(cadenceAddr)
break
}
}
}
if newAddr == (flowsdk.Address{}) {
logger.Error().Msg("❗ Could not determine new account address from events")
continue
}

// Fund account by minting and depositing FLOW from service account
txCode := fmt.Sprintf(`
import FungibleToken from %[1]s
import FlowToken from %[2]s

transaction(recipient: Address, amount: UFix64) {
prepare(acct: auth(Storage, Capabilities, FungibleToken.Withdraw) &Account) {
let adminRef = acct.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin)
?? panic("missing FlowToken admin")
let minter <- adminRef.createNewMinter(allowedAmount: amount)
let minted <- minter.mintTokens(amount: amount)
let receiver = getAccount(recipient).capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!
.borrow()
?? panic("missing FlowToken receiver")
receiver.deposit(from: <-minted)
destroy minter
}
}
`, env.FungibleTokenAddress, env.FlowTokenAddress)

latestBlock, err = emulatedBlockchain.GetLatestBlock()
if err != nil {
logger.Error().Err(err).Msg("❗ Failed to get latest block for funding")
continue
}
fundVal, err := cadence.NewUFix64(fundAmount)
if err != nil {
logger.Error().Err(err).Msg("❗ Failed to parse funding amount")
continue
}
fundTx := flowsdk.NewTransaction().
SetScript([]byte(txCode)).
SetReferenceBlockID(flowsdk.Identifier(latestBlock.ID())).
SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit).
SetProposalKey(serviceKey.Address, serviceKey.Index, serviceKey.SequenceNumber).
SetPayer(serviceKey.Address)

if err := fundTx.AddArgument(cadence.NewAddress(newAddr)); err != nil {
logger.Error().Err(err).Msg("❗ Failed to add recipient argument")
continue
}
if err := fundTx.AddArgument(fundVal); err != nil {
logger.Error().Err(err).Msg("❗ Failed to add amount argument")
continue
}

signer, err = serviceKey.Signer()
if err != nil {
logger.Error().Err(err).Msg("❗ Failed to get service key signer for funding")
continue
}
if err := fundTx.SignEnvelope(serviceKey.Address, serviceKey.Index, signer); err != nil {
logger.Error().Err(err).Msg("❗ Failed to sign funding transaction")
continue
}

if err := emulatedBlockchain.AddTransaction(*convert.SDKTransactionToFlow(*fundTx)); err != nil {
logger.Error().Err(err).Msg("❗ Failed to submit funding transaction")
continue
}
_, results, err = emulatedBlockchain.ExecuteAndCommitBlock()
if err != nil || len(results) == 0 || !results[len(results)-1].Succeeded() {
logger.Error().Err(err).Msg("❗ Failed to execute funding transaction")
continue
}
if _, err := emulatedBlockchain.CommitBlock(); err != nil {
logger.Error().Err(err).Msg("❗ Failed to commit block after funding")
continue
}

logger.Info().Msgf("(%d) 0x%s (%.18s FLOW)", i, newAddr.Hex(), fundAmount)
}

logger.Info().Msg("\nPrivate Keys\n==================")
for i := 0; i < conf.NumAccounts; i++ {
logger.Info().Msgf("(%d) %s", i, servicePrivHex)
}
}

accessAdapter := adapters.NewAccessAdapter(logger, emulatedBlockchain)
livenessTicker := utils.NewLivenessTicker(conf.LivenessCheckTolerance)
grpcServer := access.NewGRPCServer(logger, emulatedBlockchain, accessAdapter, chain, conf.Host, conf.GRPCPort, conf.GRPCDebug)
Expand Down
51 changes: 51 additions & 0 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"time"

"github.com/onflow/cadence"
"github.com/onflow/cadence/stdlib"
"github.com/onflow/flow-emulator/convert"
flowsdk "github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/templates"
Expand Down Expand Up @@ -322,3 +323,53 @@ func TestScheduledCallback_IncrementsCounter(t *testing.T) {
require.NoError(t, res.Error)
require.Equal(t, cadence.NewInt(1), res.Value)
}

func TestPrecreateAccounts_KeysMatchServiceKey(t *testing.T) {
logger := zerolog.Nop()
conf := &Config{NumAccounts: 3}
server := NewEmulatorServer(&logger, conf)
require.NotNil(t, server)
defer server.Stop()

serviceKey := server.Emulator().ServiceKey()
servicePub := serviceKey.AccountKey().PublicKey.Encode()
serviceAddr := serviceKey.Address

latest, err := server.Emulator().GetLatestBlock()
require.NoError(t, err)

blockEvents, err := server.Emulator().GetEventsForHeightRange(flowsdk.EventAccountCreated, 0, latest.Height)
require.NoError(t, err)

var createdAddrs []flowsdk.Address
for _, be := range blockEvents {
// Convert flow-go events to SDK events to inspect the Cadence payload
sdkEvents, err := convert.FlowEventsToSDK(be.Events)
require.NoError(t, err)
for _, ev := range sdkEvents {
if ev.Type != flowsdk.EventAccountCreated {
continue
}
addrFieldValue := cadence.SearchFieldByName(ev.Value, stdlib.AccountEventAddressParameter.Identifier)
cadAddr, ok := addrFieldValue.(cadence.Address)
if !ok {
continue
}
addr := flowsdk.Address(cadAddr)
if addr == serviceAddr {
continue
}
createdAddrs = append(createdAddrs, addr)
}
}

// Validate created accounts have the expected key
for _, addr := range createdAddrs {
flowAddr := convert.SDKAddressToFlow(addr)
acct, err := server.Emulator().GetAccount(flowAddr)
require.NoError(t, err)
require.NotEmpty(t, acct.Keys)
gotPub := acct.Keys[0].PublicKey.Encode()
require.Equal(t, servicePub, gotPub)
}
}
Loading