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 }