Julian Strobl 5bc0097ae3
Use RPC instead of exec.Command(...) (#176)
* Move `encoding.go` to lib to avoid import cycle

* [lib] Add `SetEncodingConfig` to avoid import cycle

```
package github.com/planetmint/planetmint-go/app
        imports github.com/planetmint/planetmint-go/app/ante
        imports github.com/planetmint/planetmint-go/x/dao
        imports github.com/planetmint/planetmint-go/util
        imports github.com/planetmint/planetmint-go/app: import cycle not allowed
```

* Use RPC instead of `exec.Command(...)`

// Closes #152

Signed-off-by: Julian Strobl <jmastr@mailbox.org>
2023-11-15 14:16:21 +01:00

219 lines
5.2 KiB
Go

package lib
import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"path/filepath"
"strconv"
"github.com/99designs/keyring"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/crypto/hd"
cryptokeyring "github.com/cosmos/cosmos-sdk/crypto/keyring"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdktx "github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
)
// KeyPair defines a public/private key pair to e.g. sign a transaction.
type KeyPair struct {
Pub cryptotypes.PubKey
Priv cryptotypes.PrivKey
}
// Result defines a generic way to receive responses from the RPC endpoint.
type Result struct {
Info map[string]interface{} `mapstructure:"info" json:"info"`
}
func init() {
GetConfig()
}
func getKeyPairFromKeyring(address sdk.AccAddress) (keyPair KeyPair, err error) {
ring, err := keyring.Open(keyring.Config{
AllowedBackends: []keyring.BackendType{keyring.FileBackend},
FileDir: filepath.Join(libConfig.RootDir, "keyring-test"),
FilePasswordFunc: func(_ string) (string, error) {
return "test", nil
},
})
if err != nil {
return
}
name := fmt.Sprintf("%x", []byte(address)) + ".address"
i, err := ring.Get(name)
if err != nil {
return
}
name = string(i.Data)
i, err = ring.Get(name)
if err != nil {
return
}
s := hex.EncodeToString(i.Data)
privKey := s[len(s)-64:]
decodedPriv, err := hex.DecodeString(privKey)
if err != nil {
return
}
algo, err := cryptokeyring.NewSigningAlgoFromString("secp256k1", cryptokeyring.SigningAlgoList{hd.Secp256k1})
if err != nil {
return
}
priv := algo.Generate()(decodedPriv)
pub := priv.PubKey()
keyPair = KeyPair{
Pub: pub,
Priv: priv,
}
return
}
func getAccountNumberAndSequence(address sdk.AccAddress) (accountNumber, sequence uint64, err error) {
resp, err := http.Get(fmt.Sprintf("%s/cosmos/auth/v1beta1/account_info/%s", libConfig.RPCEndpoint, address.String()))
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return
}
var result Result
err = json.Unmarshal(bodyBytes, &result)
if err != nil {
return
}
accountNumber, err = strconv.ParseUint(result.Info["account_number"].(string), 10, 64)
if err != nil {
return
}
sequence, err = strconv.ParseUint(result.Info["sequence"].(string), 10, 64)
if err != nil {
return
}
return
}
// BuildAndSignTx constructs the transaction from address' private key and messages.
func BuildAndSignTx(address sdk.AccAddress, msgs ...sdk.Msg) (txBytes []byte, txJSON string, err error) {
encodingConfig := GetConfig().EncodingConfig
if encodingConfig.TxConfig == nil {
err = errors.New("encoding config must not be nil")
return
}
txBuilder := encodingConfig.TxConfig.NewTxBuilder()
err = txBuilder.SetMsgs(msgs...)
if err != nil {
return
}
txBuilder.SetFeeAmount(sdk.Coins{sdk.NewInt64Coin("plmnt", 1)})
txBuilder.SetGasLimit(200000)
txBuilder.SetTimeoutHeight(0)
keyPair, err := getKeyPairFromKeyring(address)
if err != nil {
return
}
accountNumber, sequence, err := getAccountNumberAndSequence(address)
if err != nil {
return
}
// First round: we gather all the signer infos. We use the "set empty signature" hack to do that.
var sigsV2 []signing.SignatureV2
sigV2 := signing.SignatureV2{
PubKey: keyPair.Pub,
Data: &signing.SingleSignatureData{
SignMode: encodingConfig.TxConfig.SignModeHandler().DefaultMode(),
Signature: nil,
},
Sequence: sequence,
}
sigsV2 = append(sigsV2, sigV2)
err = txBuilder.SetSignatures(sigsV2...)
if err != nil {
return
}
// Second round: all signer infos are set, so each signer can sign.
sigsV2 = []signing.SignatureV2{}
signerData := xauthsigning.SignerData{
ChainID: libConfig.ChainID,
AccountNumber: accountNumber,
Sequence: sequence,
}
sigV2, err = tx.SignWithPrivKey(encodingConfig.TxConfig.SignModeHandler().DefaultMode(), signerData, txBuilder, keyPair.Priv, encodingConfig.TxConfig, sequence)
if err != nil {
return
}
sigsV2 = append(sigsV2, sigV2)
err = txBuilder.SetSignatures(sigsV2...)
if err != nil {
return
}
// Generated Protobuf-encoded bytes.
txBytes, err = encodingConfig.TxConfig.TxEncoder()(txBuilder.GetTx())
if err != nil {
return
}
// Generate a JSON string.
txJSONBytes, err := encodingConfig.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
if err != nil {
return
}
txJSON = string(txJSONBytes)
return
}
// BroadcastTx broadcasts a transaction via RPC.
func BroadcastTx(txBytes []byte) (broadcastTxResponseJSON string, err error) {
broadcastTxRequest := sdktx.BroadcastTxRequest{
TxBytes: txBytes,
Mode: sdktx.BroadcastMode_BROADCAST_MODE_SYNC,
}
broadcastTxRequestJSON, err := json.Marshal(broadcastTxRequest)
if err != nil {
return
}
resp, err := http.Post(fmt.Sprintf("%s/cosmos/tx/v1beta1/txs", libConfig.RPCEndpoint), "application/json", bytes.NewBuffer(broadcastTxRequestJSON))
if err != nil {
return
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return
}
broadcastTxResponseJSON = string(bodyBytes)
return
}