github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/cmd/burrow/commands/tx.go (about)

     1  package commands
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"io/ioutil"
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/hyperledger/burrow/config/source"
    11  	"github.com/hyperledger/burrow/deploy/def"
    12  	"github.com/hyperledger/burrow/deploy/jobs"
    13  	"github.com/hyperledger/burrow/logging"
    14  	"github.com/hyperledger/burrow/txs/payload"
    15  	cli "github.com/jawher/mow.cli"
    16  )
    17  
    18  // Tx constructs or sends payloads to a burrow daemon
    19  func Tx(output Output) func(cmd *cli.Cmd) {
    20  	return func(cmd *cli.Cmd) {
    21  		configOpts := addConfigOptions(cmd)
    22  		chainOpt := cmd.StringOpt("chain", "", "chain to be used in IP:PORT format")
    23  		timeoutOpt := cmd.IntOpt("t timeout", 5, "Timeout in seconds")
    24  		cmd.Spec += "[--chain=<ip>] [--timeout=<seconds>]"
    25  		// we don't want config sourcing logs
    26  		source.LogWriter = ioutil.Discard
    27  
    28  		// formulate first to enable better visibility for the tx input
    29  		cmd.Command("formulate", "formulate a tx", func(cmd *cli.Cmd) {
    30  			conf, err := configOpts.obtainBurrowConfig()
    31  			if err != nil {
    32  				output.Fatalf("could not set up config: %v", err)
    33  			}
    34  			if err := conf.Verify(); err != nil {
    35  				output.Fatalf("cannot continue with config: %v", err)
    36  			}
    37  
    38  			chainHost := jobs.FirstOf(*chainOpt, conf.RPC.GRPC.ListenAddress())
    39  			client := def.NewClient(chainHost, conf.Keys.RemoteAddress, true, time.Duration(*timeoutOpt)*time.Second)
    40  			logger := logging.NewNoopLogger()
    41  			address := conf.ValidatorAddress.String()
    42  
    43  			cmd.Command("send", "send value to another account", func(cmd *cli.Cmd) {
    44  				sourceOpt := cmd.StringOpt("s source", "", "Address to send from, if not set config is used")
    45  				targetOpt := cmd.StringOpt("t target", "", "Address to receive transfer, required")
    46  				amountOpt := cmd.StringOpt("a amount", "", "Amount of value to send, required")
    47  				cmd.Spec += "[--source=<address>] [--target=<address>] [--amount=<value>]"
    48  
    49  				cmd.Action = func() {
    50  					send := &def.Send{
    51  						Source:      jobs.FirstOf(*sourceOpt, address),
    52  						Destination: *targetOpt,
    53  						Amount:      *amountOpt,
    54  					}
    55  
    56  					if err := send.Validate(); err != nil {
    57  						output.Fatalf("could not validate SendTx: %v", err)
    58  					}
    59  
    60  					tx, err := jobs.FormulateSendJob(send, address, client, logger)
    61  					if err != nil {
    62  						output.Fatalf("could not formulate SendTx: %v", err)
    63  					}
    64  
    65  					output.Printf("%s", source.JSONString(payload.Any{
    66  						SendTx: tx,
    67  					}))
    68  				}
    69  			})
    70  
    71  			cmd.Command("bond", "bond a new validator", func(cmd *cli.Cmd) {
    72  				sourceOpt := cmd.StringOpt("s source", "", "Account with bonding perm, if not set config is used")
    73  				amountOpt := cmd.StringOpt("a amount", "", "Amount of value to bond, required")
    74  				cmd.Spec += "[--source=<address>] [--amount=<value>]"
    75  
    76  				cmd.Action = func() {
    77  					bond := &def.Bond{
    78  						Source: jobs.FirstOf(*sourceOpt, address),
    79  						Amount: *amountOpt,
    80  					}
    81  
    82  					if err := bond.Validate(); err != nil {
    83  						output.Fatalf("could not validate BondTx: %v", err)
    84  					}
    85  
    86  					tx, err := jobs.FormulateBondJob(bond, address, client, logger)
    87  					if err != nil {
    88  						output.Fatalf("could not formulate BondTx: %v", err)
    89  					}
    90  
    91  					output.Printf("%s", source.JSONString(payload.Any{
    92  						BondTx: tx,
    93  					}))
    94  				}
    95  			})
    96  
    97  			cmd.Command("unbond", "unbond an existing validator", func(cmd *cli.Cmd) {
    98  				sourceOpt := cmd.StringOpt("s source", "", "Validator to unbond, if not set config is used")
    99  				amountOpt := cmd.StringOpt("a amount", "", "Amount of value to unbond, required")
   100  				cmd.Spec += "[--source=<address>] [--amount=<value>]"
   101  
   102  				cmd.Action = func() {
   103  					unbond := &def.Unbond{
   104  						Source: jobs.FirstOf(*sourceOpt, address),
   105  						Amount: *amountOpt,
   106  					}
   107  
   108  					if err := unbond.Validate(); err != nil {
   109  						output.Fatalf("could not validate UnbondTx: %v", err)
   110  					}
   111  
   112  					tx, err := jobs.FormulateUnbondJob(unbond, address, client, logger)
   113  					if err != nil {
   114  						output.Fatalf("could not formulate UnbondTx: %v", err)
   115  					}
   116  
   117  					output.Printf("%s", source.JSONString(payload.Any{
   118  						UnbondTx: tx,
   119  					}))
   120  				}
   121  			})
   122  
   123  			cmd.Command("identify", "associate a validator with a node address", func(cmd *cli.Cmd) {
   124  				sourceOpt := cmd.StringOpt("source", "", "Address to send from, if not set config is used")
   125  				nodeKeyOpt := cmd.StringOpt("node-key", "", "File containing the nodeKey to use, default config")
   126  				networkOpt := cmd.StringOpt("network", "", "Publically reachable host IP")
   127  				monikerOpt := cmd.StringOpt("moniker", "", "Human readable node ID")
   128  				cmd.Spec += "[--source=<address>] [--node-key=<file>] [--network=<address>] [--moniker=<name>]"
   129  
   130  				cmd.Action = func() {
   131  
   132  					tmConf, err := conf.TendermintConfig()
   133  					if err != nil {
   134  						output.Fatalf("could not construct tendermint config: %v", err)
   135  					}
   136  
   137  					id := &def.Identify{
   138  						Source:     jobs.FirstOf(*sourceOpt, address),
   139  						NodeKey:    jobs.FirstOf(*nodeKeyOpt, tmConf.NodeKeyFile()),
   140  						Moniker:    *monikerOpt,
   141  						NetAddress: jobs.FirstOf(*networkOpt, conf.Tendermint.ListenHost),
   142  					}
   143  
   144  					if err := id.Validate(); err != nil {
   145  						output.Fatalf("could not validate IdentifyTx: %v", err)
   146  					}
   147  
   148  					tx, err := jobs.FormulateIdentifyJob(id, address, client, logger)
   149  					if err != nil {
   150  						output.Fatalf("could not formulate IdentifyTx: %v", err)
   151  					}
   152  
   153  					output.Printf("%s", source.JSONString(payload.Any{
   154  						IdentifyTx: tx,
   155  					}))
   156  				}
   157  			})
   158  		})
   159  
   160  		cmd.Command("commit", "read and send a tx to mempool", func(cmd *cli.Cmd) {
   161  			conf, err := configOpts.obtainBurrowConfig()
   162  			if err != nil {
   163  				output.Fatalf("could not set up config: %v", err)
   164  			}
   165  			fileOpt := cmd.StringOpt("f file", "", "Read the tx spec from a file")
   166  			cmd.Spec += "[--file=<location>]"
   167  
   168  			cmd.Action = func() {
   169  				if err := conf.Verify(); err != nil {
   170  					output.Fatalf("can't continue with config: %v", err)
   171  				}
   172  
   173  				chainHost := jobs.FirstOf(*chainOpt, conf.RPC.GRPC.ListenAddress())
   174  				client := def.NewClient(chainHost, conf.Keys.RemoteAddress, true, time.Duration(*timeoutOpt)*time.Second)
   175  
   176  				var rawTx payload.Any
   177  				var hash string
   178  				var err error
   179  
   180  				data, err := readInput(*fileOpt)
   181  				if err != nil {
   182  					output.Fatalf("no input: %v", err)
   183  				}
   184  
   185  				if err = json.Unmarshal(data, &rawTx); err != nil {
   186  					output.Fatalf("could not unmarshal Tx: %v", err)
   187  				}
   188  
   189  				switch tx := rawTx.GetValue().(type) {
   190  				case *payload.SendTx:
   191  					hash, err = makeTx(client, tx)
   192  				case *payload.BondTx:
   193  					hash, err = makeTx(client, tx)
   194  				case *payload.UnbondTx:
   195  					hash, err = makeTx(client, tx)
   196  				case *payload.IdentifyTx:
   197  					hash, err = makeTx(client, tx)
   198  				default:
   199  					output.Fatalf("payload type not recognized")
   200  				}
   201  
   202  				if err != nil {
   203  					output.Fatalf("failed to commit tx to mempool: %v", err)
   204  				}
   205  
   206  				output.Printf("%s", hash)
   207  			}
   208  		})
   209  	}
   210  }
   211  
   212  func makeTx(client *def.Client, tx payload.Payload) (string, error) {
   213  	logger := logging.NewNoopLogger()
   214  	txe, err := client.SignAndBroadcast(tx, logger)
   215  	if err != nil {
   216  		return "", err
   217  	}
   218  
   219  	jobs.LogTxExecution(txe, logger)
   220  	if err != nil {
   221  		return "", err
   222  	}
   223  	return txe.Receipt.TxHash.String(), nil
   224  }
   225  
   226  func readInput(file string) ([]byte, error) {
   227  	if file != "" {
   228  		data, err := ioutil.ReadFile(file)
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  		return data, nil
   233  	}
   234  
   235  	stat, _ := os.Stdin.Stat()
   236  	if (stat.Mode() & os.ModeCharDevice) != 0 {
   237  		return nil, errors.New("expected input from STDIN, use --file otherwise")
   238  	}
   239  
   240  	data, err := ioutil.ReadAll(os.Stdin)
   241  	if err != nil {
   242  		return nil, errors.New("could not read data from STDIN")
   243  	}
   244  
   245  	return data, nil
   246  }