It can be used to deploy and test smart contracts.
Usage: burrow deploy [--chain=<host:port>] [--keys=<host:port>] [--mempool-signing] [--dir=<root directory>] [--output=<output file>] [--wasm] [--set=<KEY=VALUE>]... [--bin-path=<path>] [--gas=<gas>] [--jobs=<concurrent playbooks>] [--address=<address>] [--fee=<fee
>] [--amount=<amount>] [--local-abi] [--verbose] [--debug] [--timeout=<timeout>] [--list-proposals=<state> | --proposal-create| --proposal-verify | --proposal-vote] [FILE...]
Deploy and test contracts
Arguments:
FILE path to playbook file which deploy should run. if also using the --dir flag, give the relative path to playbooks file, which should be in the same directory
Options:
-c, --chain chain to be used in IP:PORT format (default "127.0.0.1:10997")
-s, --keys IP:PORT of Burrow GRPC service which jobs should or otherwise transaction submitted unsigned for mempool signing in Burrow
-p, --mempool-signing Use Burrow's own keys connection to sign transactions - means that Burrow instance must have access to input account keys. Sequence numbers are set as transactions enter the mempool so concurrent transactions can be sent from same inputs.
-i, --dir root directory of app (will use pwd by default)
-o, --output filename for playbook output file. by default, this name will reflect the playbook passed (default "deploy.output.json")
-e, --set default sets to use; operates the same way as the [set] jobs, only before the jobs file is ran (and after default address
-b, --bin-path path to the bin directory jobs should use when saving binaries after the compile process defaults to --dir + /bin (default "[dir]/bin")
-g, --gas default gas to use; can be overridden for any single job (default "1111111111")
-j, --jobs default number of concurrent playbooks to run if multiple are specified (default 1)
-a, --address default address (or account name) to use; operates the same way as the [account] job, only before the deploy file is ran
-n, --fee default fee to use (default "9999")
-m, --amount default amount to use (default "9999")
-v, --verbose verbose output
--local-abi use local ABIs rather than fetching them from burrow
--wasm Compile to WASM using solang (experimental)
-d, --debug debug level output
--proposal-verify Verify any proposal, do NOT create new proposal or vote
--proposal-vote Vote for proposal, do NOT create new proposal
--proposal-create Create new proposal
-t, --timeout Timeout to talk to the chain in seconds (default 15)
--list-proposals List proposals, either all, executed, expired, or current
In Deploy.go
Deploy -> RunPlaybooks() -> workers for cocurrent job execution -> submit playbook to workers
for i := 1; i <= args.Jobs; i++ {
go worker(workQ, resultQ, args, logger)
}
for i, playbook := range playbooks {
workQ <- playbookWork{jobNo: i, playbook: playbook}
}
func worker(playbooks <-chan playbookWork, results chan<- playbookResult, args *def.DeployArgs, logger *logging.Logger) {
client := def.NewClient(args.Chain, args.KeysService, args.MempoolSign, time.Duration(args.Timeout)*time.Second)
for playbook := range playbooks {
doWork := func(work playbookWork) (logBuf bytes.Buffer, err error) {
// block that triggers if the do.Path was NOT set
// via cli flag... or not
fname := filepath.Join(args.Path, work.playbook)
// if YAMLPath cannot be found, abort
if _, err := os.Stat(fname); os.IsNotExist(err) {
return logBuf, fmt.Errorf("could not find playbook file (%s)",
fname)
}
if args.Jobs != 1 {
logger = logging.NewLogger(log.NewLogfmtLogger(&logBuf))
}
// Load the package if it doesn't exist
script, err := loader.LoadPlaybook(fname, args, logger)
if err != nil {
return logBuf, err
}
// Load existing bin files to decode events
var abiError error
client.AllSpecs, abiError = abi.LoadPath(script.BinPath)
if err != nil {
logger.InfoMsg("failed to load ABIs for Event parsing", "path", script.BinPath, "error", abiError)
}
err = jobs.ExecutePlaybook(args, script, client, logger)
return
}
startTime := time.Now()
logBuf, err := doWork(playbook)
results <- playbookResult{
jobNo: playbook.jobNo,
log: logBuf,
err: err,
duration: time.Since(startTime),
}
}
}
Finally jobs will be done by doJobs.
doJobs is an interpretor which aactually execute the script. In util.PreProcessFields(), the variables will be substituted by the result of the executed jobs, e.g., $setValue replaced by ‘5’.
call stack:
github.com/hyperledger/burrow/deploy/jobs.doJobs at job_manager.go:136
github.com/hyperledger/burrow/deploy/jobs.ExecutePlaybook at job_manager.go:332
github.com/hyperledger/burrow/deploy.worker.func1 at run_deploy.go:63
github.com/hyperledger/burrow/deploy.worker at run_deploy.go:68
runtime.goexit at asm_amd64.s:1357
- Async stack trace
github.com/hyperledger/burrow/deploy.RunPlaybooks at run_deploy.go:97
func doJobs(playbook *def.Playbook, args *def.DeployArgs, client *def.Client, logger *logging.Logger) error {
for _, job := range playbook.Jobs {
payload, err := job.Payload()
if err != nil {
return fmt.Errorf("could not get Job payload: %v", payload)
}
err = util.PreProcessFields(payload, args, playbook, client, logger)
if err != nil {
return err
}
// Revalidate with possible replacements
err = payload.Validate()
if err != nil {
return fmt.Errorf("error validating job %s after pre-processing variables: %v", job.Name, err)
}
switch payload.(type) {
case *def.Proposal:
announce(job.Name, "Proposal", logger)
job.Result, err = ProposalJob(job.Proposal, args, playbook, client, logger)
// Meta Job
case *def.Meta:
announce(job.Name, "Meta", logger)
metaPlaybook := job.Meta.Playbook
if metaPlaybook.Account == "" {
metaPlaybook.Account = playbook.Account
}
err = doJobs(metaPlaybook, args, client, logger)
// Governance
case *def.UpdateAccount:
announce(job.Name, "UpdateAccount", logger)
var tx *pbpayload.GovTx
tx, job.Variables, err = FormulateUpdateAccountJob(job.UpdateAccount, playbook.Account, client, logger)
if err != nil {
return err
}
err = UpdateAccountJob(job.UpdateAccount, playbook.Account, tx, client, logger)
// Util jobs
case *def.Account:
announce(job.Name, "Account", logger)
job.Result, err = SetAccountJob(job.Account, args, playbook, logger)
case *def.Set:
announce(job.Name, "Set", logger)
job.Result, err = SetValJob(job.Set, args, logger)
// Transaction jobs
case *def.Send:
announce(job.Name, "Send", logger)
tx, err := FormulateSendJob(job.Send, playbook.Account, client, logger)
if err != nil {
return err
}
job.Result, err = SendJob(job.Send, tx, playbook.Account, client, logger)
if err != nil {
return err
}
case *def.Bond:
announce(job.Name, "Bond", logger)
tx, err := FormulateBondJob(job.Bond, playbook.Account, client, logger)
if err != nil {
return err
}
job.Result, err = BondJob(job.Bond, tx, playbook.Account, client, logger)
if err != nil {
return err
}
case *def.Unbond:
announce(job.Name, "Unbond", logger)
tx, err := FormulateUnbondJob(job.Unbond, playbook.Account, client, logger)
if err != nil {
return err
}
job.Result, err = UnbondJob(job.Unbond, tx, playbook.Account, client, logger)
if err != nil {
return err
}
case *def.RegisterName:
announce(job.Name, "RegisterName", logger)
txs, err := FormulateRegisterNameJob(job.RegisterName, args, playbook, client, logger)
if err != nil {
return err
}
job.Result, err = RegisterNameJob(job.RegisterName, args, playbook, txs, client, logger)
if err != nil {
return err
}
case *def.Permission:
announce(job.Name, "Permission", logger)
tx, err := FormulatePermissionJob(job.Permission, playbook.Account, client, logger)
if err != nil {
return err
}
job.Result, err = PermissionJob(job.Permission, playbook.Account, tx, client, logger)
if err != nil {
return err
}
case *def.Identify:
announce(job.Name, "Identify", logger)
tx, err := FormulateIdentifyJob(job.Identify, playbook.Account, client, logger)
if err != nil {
return err
}
job.Result, err = IdentifyJob(job.Identify, tx, playbook.Account, client, logger)
if err != nil {
return err
}
// Contracts jobs
case *def.Deploy:
announce(job.Name, "Deploy", logger)
txs, contracts, ferr := FormulateDeployJob(job.Deploy, args, playbook, client, job.Intermediate, logger)
if ferr != nil {
return ferr
}
job.Result, err = DeployJob(job.Deploy, args, playbook, client, txs, contracts, logger)
case *def.Call:
announce(job.Name, "Call", logger)
CallTx, ferr := FormulateCallJob(job.Call, args, playbook, client, logger)
if ferr != nil {
return ferr
}
job.Result, job.Variables, err = CallJob(job.Call, CallTx, args, playbook, client, logger)
case *def.Build:
announce(job.Name, "Build", logger)
var resp *compilers.Response
resp, err = getCompilerWork(job.Intermediate)
if err != nil {
return err
}
job.Result, err = BuildJob(job.Build, playbook, resp, logger)
// State jobs
case *def.RestoreState:
announce(job.Name, "RestoreState", logger)
job.Result, err = RestoreStateJob(job.RestoreState)
case *def.DumpState:
announce(job.Name, "DumpState", logger)
job.Result, err = DumpStateJob(job.DumpState)
// Test jobs
case *def.QueryAccount:
announce(job.Name, "QueryAccount", logger)
job.Result, err = QueryAccountJob(job.QueryAccount, client, logger)
case *def.QueryContract:
announce(job.Name, "QueryContract", logger)
job.Result, job.Variables, err = QueryContractJob(job.QueryContract, args, playbook, client, logger)
case *def.QueryName:
announce(job.Name, "QueryName", logger)
job.Result, err = QueryNameJob(job.QueryName, client, logger)
case *def.QueryVals:
announce(job.Name, "QueryVals", logger)
job.Result, err = QueryValsJob(job.QueryVals, client, logger)
case *def.Assert:
announce(job.Name, "Assert", logger)
job.Result, err = AssertJob(job.Assert, logger)
default:
logger.InfoMsg("Error")
return fmt.Errorf("the Job specified in deploy.yaml and parsed as '%v' is not recognised as a valid job",
job)
}
if len(job.Variables) != 0 {
for _, theJob := range job.Variables {
logger.InfoMsg("Job Vars", "name", theJob.Name, "value", theJob.Value)
}
}
if err != nil {
return err
}
}
return nil
}
Jobs with underlying RPC:
Job | Interface.Method | RPC |
---|---|---|
Proposal | queryClient.GetProposal | /rpcquery.Query/GetProposal |
UpdateAccount | transactClient.BroadcastTxSync | /rpctransact.Transact/BroadcastTxSync |
Send | transactClient.BroadcastTxSync | /rpctransact.Transact/BroadcastTxSync |
Bond | transactClient.BroadcastTxSync | /rpctransact.Transact/BroadcastTxSync |
Unbond | transactClient.BroadcastTxSync | /rpctransact.Transact/BroadcastTxSync |
RegisterName | transactClient.BroadcastTxSync | /rpctransact.Transact/BroadcastTxSync |
Permission | transactClient.BroadcastTxSync | /rpctransact.Transact/BroadcastTxSync |
Identify | transactClient.BroadcastTxSync | /rpctransact.Transact/BroadcastTxSync |
Deploy | transactClient.BroadcastTxSync | /rpctransact.Transact/BroadcastTxSync |
Call | transactClient.BroadcastTxSync | /rpctransact.Transact/BroadcastTxSync |
QueryAccount | queryClient.GetAccount | /rpcquery.Query/GetAccount |
QueryContract | transactClient.CallTxSim | /rpctransact.Transact/CallTxSim |
QueryName | queryClient.GetName | /rpcquery.Query/GetName |
QueryVals | /rpcquery.Query/GetValidatorSet | queryClient.GetValidatorSet |
The call and query contract job is for executing contract code by way of running one of the functions. The call job will create a transaction and will have to wait until the next block to retrieve the result; the query-contract job is for accessing read-only functions which do not require write access.