github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/x/auth/client/utils/tx.go (about) 1 package utils 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io/ioutil" 8 "math/big" 9 "os" 10 11 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client" 12 types2 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec/types" 13 txmsg "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/ibc-adapter" 14 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/tx/signing" 15 ibctx "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/ibc-tx" 16 signingtypes "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/ibcsigning" 17 18 "github.com/pkg/errors" 19 "github.com/spf13/viper" 20 21 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/context" 22 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/flags" 23 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/input" 24 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/keys" 25 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec" 26 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 27 authtypes "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/types" 28 ) 29 30 // GasEstimateResponse defines a response definition for tx gas estimation. 31 type GasEstimateResponse struct { 32 GasEstimate uint64 `json:"gas_estimate" yaml:"gas_estimate"` 33 } 34 35 func (gr GasEstimateResponse) String() string { 36 return fmt.Sprintf("gas estimate: %d", gr.GasEstimate) 37 } 38 39 // GenerateOrBroadcastMsgs creates a StdTx given a series of messages. If 40 // the provided context has generate-only enabled, the tx will only be printed 41 // to STDOUT in a fully offline manner. Otherwise, the tx will be signed and 42 // broadcasted. 43 func GenerateOrBroadcastMsgs(cliCtx context.CLIContext, txBldr authtypes.TxBuilder, msgs []sdk.Msg) error { 44 if cliCtx.GenerateOnly { 45 return PrintUnsignedStdTx(txBldr, cliCtx, msgs) 46 } 47 48 return CompleteAndBroadcastTxCLI(txBldr, cliCtx, msgs) 49 } 50 51 // CompleteAndBroadcastTxCLI implements a utility function that facilitates 52 // sending a series of messages in a signed transaction given a TxBuilder and a 53 // QueryContext. It ensures that the account exists, has a proper number and 54 // sequence set. In addition, it builds and signs a transaction with the 55 // supplied messages. Finally, it broadcasts the signed transaction to a node. 56 func CompleteAndBroadcastTxCLI(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { 57 txConfig := NewPbTxConfig(cliCtx.InterfaceRegistry) 58 txBldr, err := PrepareTxBuilder(txBldr, cliCtx) 59 if err != nil { 60 return err 61 } 62 63 fromName := cliCtx.GetFromName() 64 65 if txBldr.SimulateAndExecute() || cliCtx.Simulate { 66 txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs) 67 if err != nil { 68 return err 69 } 70 71 gasEst := GasEstimateResponse{GasEstimate: txBldr.Gas()} 72 _, _ = fmt.Fprintf(os.Stderr, "%s\n", gasEst.String()) 73 } 74 75 if cliCtx.Simulate { 76 return nil 77 } 78 txBytes := []byte{} 79 pbtxMsgs, isPbTxMsg := convertIfPbTx(msgs) 80 if !cliCtx.SkipConfirm { 81 var signData interface{} 82 var json []byte 83 if isPbTxMsg { 84 85 tx, err := buildUnsignedPbTx(txBldr, txConfig, pbtxMsgs...) 86 if err != nil { 87 return err 88 } 89 json, err = txConfig.TxJSONEncoder()(tx.GetTx()) 90 if err != nil { 91 panic(err) 92 } 93 } else { 94 signData, err = txBldr.BuildSignMsg(msgs) 95 if err != nil { 96 return err 97 } 98 99 if viper.GetBool(flags.FlagIndentResponse) { 100 json, err = cliCtx.Codec.MarshalJSONIndent(signData, "", " ") 101 if err != nil { 102 panic(err) 103 } 104 } else { 105 json = cliCtx.Codec.MustMarshalJSON(signData) 106 } 107 } 108 109 _, _ = fmt.Fprintf(os.Stderr, "%s\n\n", json) 110 111 buf := bufio.NewReader(os.Stdin) 112 ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf) 113 if err != nil || !ok { 114 _, _ = fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction") 115 return err 116 } 117 118 } 119 120 if isPbTxMsg { 121 txBytes, err = PbTxBuildAndSign(cliCtx, txConfig, txBldr, keys.DefaultKeyPass, pbtxMsgs) 122 if err != nil { 123 panic(err) 124 } 125 } else { 126 // build and sign the transaction 127 txBytes, err = txBldr.BuildAndSign(fromName, keys.DefaultKeyPass, msgs) 128 if err != nil { 129 return err 130 } 131 } 132 // broadcast to a Tendermint node 133 res, err := cliCtx.BroadcastTx(txBytes) 134 if err != nil { 135 return err 136 } 137 138 return cliCtx.PrintOutput(res) 139 } 140 141 func buildUnsignedPbTx(txf authtypes.TxBuilder, txConfig client.TxConfig, msgs ...txmsg.Msg) (client.TxBuilder, error) { 142 if txf.ChainID() == "" { 143 return nil, fmt.Errorf("chain ID required but not specified") 144 } 145 146 fees := txf.Fees() 147 148 if !txf.GasPrices().IsZero() { 149 if !fees.IsZero() { 150 return nil, errors.New("cannot provide both fees and gas prices") 151 } 152 153 glDec := sdk.NewDec(int64(txf.Gas())) 154 155 // Derive the fees based on the provided gas prices, where 156 // fee = ceil(gasPrice * gasLimit). 157 fees = make(sdk.Coins, len(txf.GasPrices())) 158 159 for i, gp := range txf.GasPrices() { 160 fee := gp.Amount.Mul(glDec) 161 fees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) 162 } 163 } 164 165 tx := txConfig.NewTxBuilder() 166 167 if err := tx.SetMsgs(msgs...); err != nil { 168 return nil, err 169 } 170 tx.SetMemo(txf.Memo()) 171 coins := []sdk.CoinAdapter{} 172 for _, fee := range txf.Fees() { 173 prec := newCoinFromDec() 174 175 am := sdk.NewIntFromBigInt(fee.Amount.BigInt().Div(fee.Amount.BigInt(), prec)) 176 177 coins = append(coins, sdk.NewCoinAdapter(fee.Denom, am)) 178 } 179 tx.SetFeeAmount(coins) 180 181 tx.SetGasLimit(txf.Gas()) 182 //tx.SetTimeoutHeight(txf.TimeoutHeight()) 183 184 return tx, nil 185 } 186 187 func newCoinFromDec() *big.Int { 188 n := big.Int{} 189 prec, ok := n.SetString(sdk.DefaultDecStr, 10) 190 if !ok { 191 panic(errors.New("newCoinFromDec setstring error")) 192 } 193 return prec 194 } 195 196 func PbTxBuildAndSign(clientCtx context.CLIContext, txConfig client.TxConfig, txbld authtypes.TxBuilder, passphrase string, msgs []txmsg.Msg) ([]byte, error) { 197 //txb := txConfig.NewTxBuilder() 198 txb, err := buildUnsignedPbTx(txbld, txConfig, msgs...) 199 if err != nil { 200 return nil, err 201 } 202 if !clientCtx.SkipConfirm { 203 out, err := txConfig.TxJSONEncoder()(txb.GetTx()) 204 if err != nil { 205 return nil, err 206 } 207 208 _, _ = fmt.Fprintf(os.Stderr, "%s\n\n", out) 209 210 buf := bufio.NewReader(os.Stdin) 211 ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf) 212 213 if err != nil || !ok { 214 _, _ = fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction") 215 return nil, err 216 } 217 } 218 219 err = signPbTx(txConfig, txbld, clientCtx.GetFromName(), passphrase, &txb, true) 220 if err != nil { 221 return nil, err 222 } 223 224 return txConfig.TxEncoder()(txb.GetTx()) 225 } 226 227 func signPbTx(txConfig client.TxConfig, txf authtypes.TxBuilder, name string, passwd string, pbTxBld *client.TxBuilder, overwriteSig bool) error { 228 if txf.Keybase() == nil { 229 return errors.New("keybase must be set prior to signing a transaction") 230 } 231 signMode := txConfig.SignModeHandler().DefaultMode() 232 privKey, err := txf.Keybase().ExportPrivateKeyObject(name, passwd) 233 if err != nil { 234 return err 235 } 236 237 pubKeyPB := ibctx.LagacyKey2PbKey(privKey.PubKey()) 238 239 signerData := signingtypes.SignerData{ 240 ChainID: txf.ChainID(), 241 AccountNumber: txf.AccountNumber(), 242 Sequence: txf.Sequence(), 243 } 244 245 // For SIGN_MODE_DIRECT, calling SetSignatures calls setSignerInfos on 246 // TxBuilder under the hood, and SignerInfos is needed to generated the 247 // sign bytes. This is the reason for setting SetSignatures here, with a 248 // nil signature. 249 // 250 // Note: this line is not needed for SIGN_MODE_LEGACY_AMINO, but putting it 251 // also doesn't affect its generated sign bytes, so for code's simplicity 252 // sake, we put it here. 253 sigData := signing.SingleSignatureData{ 254 SignMode: signMode, 255 Signature: nil, 256 } 257 258 sig := signing.SignatureV2{ 259 PubKey: pubKeyPB, 260 Data: &sigData, 261 Sequence: txf.Sequence(), 262 } 263 var prevSignatures []signing.SignatureV2 264 if !overwriteSig { 265 prevSignatures, err = (*pbTxBld).GetTx().GetSignaturesV2() 266 if err != nil { 267 return err 268 } 269 } 270 if err := (*pbTxBld).SetSignatures(sig); err != nil { 271 return err 272 } 273 274 // Generate the bytes to be signed. 275 bytesToSign, err := txConfig.SignModeHandler().GetSignBytes(signMode, signerData, (*pbTxBld).GetTx()) 276 if err != nil { 277 return err 278 } 279 280 sigBytes, err := privKey.Sign(bytesToSign) 281 if err != nil { 282 panic(err) 283 } 284 sigData = signing.SingleSignatureData{ 285 SignMode: signMode, 286 Signature: sigBytes, 287 } 288 sig = signing.SignatureV2{ 289 PubKey: pubKeyPB, 290 Data: &sigData, 291 Sequence: txf.Sequence(), 292 } 293 294 if overwriteSig { 295 return (*pbTxBld).SetSignatures(sig) 296 } 297 prevSignatures = append(prevSignatures, sig) 298 299 return (*pbTxBld).SetSignatures(prevSignatures...) 300 } 301 302 func convertIfPbTx(msgs []sdk.Msg) ([]txmsg.Msg, bool) { 303 retmsg := []txmsg.Msg{} 304 for _, msg := range msgs { 305 if m, ok := msg.(txmsg.Msg); ok { 306 retmsg = append(retmsg, m) 307 } 308 } 309 310 if len(retmsg) > 0 { 311 return retmsg, true 312 } 313 return nil, false 314 } 315 316 // EnrichWithGas calculates the gas estimate that would be consumed by the 317 // transaction and set the transaction's respective value accordingly. 318 func EnrichWithGas(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (authtypes.TxBuilder, error) { 319 _, adjusted, err := simulateMsgs(txBldr, cliCtx, msgs) 320 if err != nil { 321 return txBldr, err 322 } 323 324 return txBldr.WithGas(adjusted), nil 325 } 326 327 // CalculateGas simulates the execution of a transaction and returns 328 // the simulation response obtained by the query and the adjusted gas amount. 329 func CalculateGas( 330 queryFunc func(string, []byte) ([]byte, int64, error), cdc *codec.Codec, 331 txBytes []byte, adjustment float64, 332 ) (sdk.SimulationResponse, uint64, error) { 333 334 // run a simulation (via /app/simulate query) to 335 // estimate gas and update TxBuilder accordingly 336 rawRes, _, err := queryFunc("/app/simulate", txBytes) 337 if err != nil { 338 return sdk.SimulationResponse{}, 0, err 339 } 340 341 simRes, err := parseQueryResponse(cdc, rawRes) 342 if err != nil { 343 return sdk.SimulationResponse{}, 0, err 344 } 345 346 adjusted := adjustGasEstimate(simRes.GasUsed, adjustment) 347 return simRes, adjusted, nil 348 } 349 func NewPbTxConfig(reg types2.InterfaceRegistry) client.TxConfig { 350 marshaler := codec.NewProtoCodec(reg) 351 return ibctx.NewTxConfig(marshaler, ibctx.DefaultSignModes) 352 } 353 354 // PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout. 355 func PrintUnsignedStdTx(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { 356 stdTx, err := buildUnsignedStdTxOffline(txBldr, cliCtx, msgs) 357 if err != nil { 358 return err 359 } 360 361 var json []byte 362 pbTxMsgs, isPbTxMsg := convertIfPbTx(msgs) 363 364 if isPbTxMsg { 365 txConfig := NewPbTxConfig(cliCtx.InterfaceRegistry) 366 tx, err := buildUnsignedPbTx(txBldr, txConfig, pbTxMsgs...) 367 if err != nil { 368 return err 369 } 370 json, err = txConfig.TxJSONEncoder()(tx.GetTx()) 371 if err != nil { 372 return err 373 } 374 } else { 375 if viper.GetBool(flags.FlagIndentResponse) { 376 json, err = cliCtx.Codec.MarshalJSONIndent(stdTx, "", " ") 377 } else { 378 json, err = cliCtx.Codec.MarshalJSON(stdTx) 379 } 380 if err != nil { 381 return err 382 } 383 } 384 385 _, _ = fmt.Fprintf(cliCtx.Output, "%s\n", json) 386 return nil 387 } 388 389 // SignStdTx appends a signature to a StdTx and returns a copy of it. If appendSig 390 // is false, it replaces the signatures already attached with the new signature. 391 // Don't perform online validation or lookups if offline is true. 392 func SignStdTx( 393 txBldr authtypes.TxBuilder, cliCtx context.CLIContext, name string, 394 stdTx *authtypes.StdTx, appendSig bool, offline bool, 395 ) (*authtypes.StdTx, error) { 396 397 info, err := txBldr.Keybase().Get(name) 398 if err != nil { 399 return nil, err 400 } 401 402 addr := info.GetPubKey().Address() 403 404 // check whether the address is a signer 405 if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) { 406 return nil, fmt.Errorf("%s: %s", errInvalidSigner, name) 407 } 408 409 if !offline { 410 txBldr, err = populateAccountFromState(txBldr, cliCtx, sdk.AccAddress(addr)) 411 if err != nil { 412 return nil, err 413 } 414 } 415 416 return txBldr.SignStdTx(name, keys.DefaultKeyPass, stdTx, appendSig) 417 } 418 419 // SignStdTxWithSignerAddress attaches a signature to a StdTx and returns a copy of a it. 420 // Don't perform online validation or lookups if offline is true, else 421 // populate account and sequence numbers from a foreign account. 422 func SignStdTxWithSignerAddress(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, 423 addr sdk.AccAddress, name string, stdTx *authtypes.StdTx, 424 offline bool) (signedStdTx *authtypes.StdTx, err error) { 425 426 // check whether the address is a signer 427 if !isTxSigner(addr, stdTx.GetSigners()) { 428 return signedStdTx, fmt.Errorf("%s: %s", errInvalidSigner, name) 429 } 430 431 if !offline { 432 txBldr, err = populateAccountFromState(txBldr, cliCtx, addr) 433 if err != nil { 434 return signedStdTx, err 435 } 436 } 437 438 return txBldr.SignStdTx(name, keys.DefaultKeyPass, stdTx, false) 439 } 440 441 // Read and decode a StdTx from the given filename. Can pass "-" to read from stdin. 442 func ReadStdTxFromFile(cdc *codec.Codec, filename string) (*authtypes.StdTx, error) { 443 var bytes []byte 444 var tx authtypes.StdTx 445 var err error 446 447 if filename == "-" { 448 bytes, err = ioutil.ReadAll(os.Stdin) 449 } else { 450 bytes, err = ioutil.ReadFile(filename) 451 } 452 453 if err != nil { 454 return nil, err 455 } 456 457 if err = cdc.UnmarshalJSON(bytes, &tx); err != nil { 458 return nil, err 459 } 460 461 return &tx, nil 462 } 463 464 func populateAccountFromState( 465 txBldr authtypes.TxBuilder, cliCtx context.CLIContext, addr sdk.AccAddress, 466 ) (authtypes.TxBuilder, error) { 467 468 num, seq, err := authtypes.NewAccountRetriever(cliCtx).GetAccountNumberSequence(addr) 469 if err != nil { 470 return txBldr, err 471 } 472 473 return txBldr.WithAccountNumber(num).WithSequence(seq), nil 474 } 475 476 type txEncoderConfig struct { 477 isEthereumTx bool 478 } 479 480 type Option func(config *txEncoderConfig) 481 482 func WithEthereumTx() Option { 483 return func(cfg *txEncoderConfig) { 484 cfg.isEthereumTx = true 485 } 486 } 487 488 // GetTxEncoder return tx encoder from global sdk configuration if ones is defined. 489 // Otherwise returns encoder with default logic. 490 func GetTxEncoder(cdc *codec.Codec, options ...Option) (encoder sdk.TxEncoder) { 491 encoder = sdk.GetConfig().GetTxEncoder() 492 if encoder == nil { 493 var cfg txEncoderConfig 494 for _, op := range options { 495 op(&cfg) 496 } 497 if cfg.isEthereumTx { 498 encoder = authtypes.EthereumTxEncoder(cdc) 499 } else { 500 encoder = authtypes.DefaultTxEncoder(cdc) 501 } 502 } 503 504 return 505 } 506 507 // simulateMsgs simulates the transaction and returns the simulation response and 508 // the adjusted gas value. 509 func simulateMsgs(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (sdk.SimulationResponse, uint64, error) { 510 txBytes, err := txBldr.BuildTxForSim(msgs) 511 if err != nil { 512 return sdk.SimulationResponse{}, 0, err 513 } 514 515 return CalculateGas(cliCtx.QueryWithData, cliCtx.Codec, txBytes, txBldr.GasAdjustment()) 516 } 517 518 func adjustGasEstimate(estimate uint64, adjustment float64) uint64 { 519 return uint64(adjustment * float64(estimate)) 520 } 521 522 func parseQueryResponse(cdc *codec.Codec, rawRes []byte) (sdk.SimulationResponse, error) { 523 var simRes sdk.SimulationResponse 524 if err := cdc.UnmarshalBinaryBare(rawRes, &simRes); err != nil { 525 return sdk.SimulationResponse{}, err 526 } 527 528 return simRes, nil 529 } 530 531 // PrepareTxBuilder populates a TxBuilder in preparation for the build of a Tx. 532 func PrepareTxBuilder(txBldr authtypes.TxBuilder, cliCtx context.CLIContext) (authtypes.TxBuilder, error) { 533 from := cliCtx.GetFromAddress() 534 535 accGetter := authtypes.NewAccountRetriever(cliCtx) 536 if err := accGetter.EnsureExists(from); err != nil { 537 return txBldr, err 538 } 539 540 txbldrAccNum, txbldrAccSeq := txBldr.AccountNumber(), txBldr.Sequence() 541 // TODO: (ref #1903) Allow for user supplied account number without 542 // automatically doing a manual lookup. 543 if txbldrAccNum == 0 || txbldrAccSeq == 0 { 544 num, seq, err := authtypes.NewAccountRetriever(cliCtx).GetAccountNumberSequence(from) 545 if err != nil { 546 return txBldr, err 547 } 548 549 if txbldrAccNum == 0 { 550 txBldr = txBldr.WithAccountNumber(num) 551 } 552 if txbldrAccSeq == 0 { 553 txBldr = txBldr.WithSequence(seq) 554 } 555 } 556 557 return txBldr, nil 558 } 559 560 func buildUnsignedStdTxOffline(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx *authtypes.StdTx, err error) { 561 if txBldr.SimulateAndExecute() { 562 if cliCtx.GenerateOnly { 563 return stdTx, errors.New("cannot estimate gas with generate-only") 564 } 565 566 txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs) 567 if err != nil { 568 return stdTx, err 569 } 570 571 _, _ = fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas()) 572 } 573 574 stdSignMsg, err := txBldr.BuildSignMsg(msgs) 575 if err != nil { 576 return stdTx, err 577 } 578 579 return authtypes.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil 580 } 581 582 func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool { 583 for _, s := range signers { 584 if bytes.Equal(user.Bytes(), s.Bytes()) { 585 return true 586 } 587 } 588 589 return false 590 } 591 592 func CliConvertCoinToCoinAdapters(coins sdk.Coins) sdk.CoinAdapters { 593 ret := make(sdk.CoinAdapters, 0) 594 for _, v := range coins { 595 ret = append(ret, CliConvertCoinToCoinAdapter(v)) 596 } 597 return ret 598 } 599 600 func CliConvertCoinToCoinAdapter(coin sdk.Coin) sdk.CoinAdapter { 601 prec := newCoinFromDec() 602 603 am := sdk.NewIntFromBigInt(coin.Amount.BigInt().Div(coin.Amount.BigInt(), prec)) 604 605 return sdk.NewCoinAdapter(coin.Denom, am) 606 }