Library for RPC requests to Planetmint (#173)

* [lib] Add initial version

// See #152

* [lib] Add initial README.md

Signed-off-by: Julian Strobl <jmastr@mailbox.org>
This commit is contained in:
Julian Strobl 2023-11-14 15:04:46 +01:00 committed by GitHub
parent 1e1138d026
commit 1ca7faec60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 2263 additions and 0 deletions

129
lib/README.md Normal file
View File

@ -0,0 +1,129 @@
# Library for RPC requests to Planetmint
## How to use it
In the example below we use the account `addr0` for which we have the private key in our keyring.
We configure the address prefix andd change the default RPC endpoint to a remote one.
The only keyring backend currently supported is the test backend under `keyring-test`.
After that we construct three messages to send `10plmnt` each to three addresses `addr1`, `addr2` and `addr3`.
We then build and sign the transaction and eventually send this transaction via RPC.
For debugging purposes we print the transaction that we send as JSON.
```
package main
import (
"fmt"
"log"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/planetmint/planetmint-go/lib"
)
func main() {
libConfig := lib.GetConfig()
libConfig.SetBech32PrefixForAccount("plmnt")
libConfig.SetRPCEndpoint("https://testnet-api.rddl.io")
addr0 := sdk.MustAccAddressFromBech32("plmnt168z8fyyzap0nw75d4atv9ucr2ye60d57dzlzaf")
addr1 := sdk.MustAccAddressFromBech32("plmnt1vklujvmr9hsk9zwpquk4waecr2u5vcyjd8vgm8")
addr2 := sdk.MustAccAddressFromBech32("plmnt1pwquxvqmmdry4gdel4g4rz0js7jy65453h92g7")
addr3 := sdk.MustAccAddressFromBech32("plmnt1dyuhg8ldu3d6nvhrvzzemtc3893dys9v9lvdty")
coin := sdk.NewCoins(sdk.NewInt64Coin("plmnt", 10))
msg1 := banktypes.NewMsgSend(addr0, addr1, coin)
msg2 := banktypes.NewMsgSend(addr0, addr2, coin)
msg3 := banktypes.NewMsgSend(addr0, addr3, coin)
txBytes, txJSON, err := lib.BuildAndSignTx(addr0, msg1, msg2, msg3)
if err != nil {
log.Fatal(err)
}
fmt.Println(txJSON)
_, err = lib.BroadcastTx(txBytes)
if err != nil {
log.Fatal(err)
}
}
```
Sample output:
```
$ go run main.go|jq
{
"body": {
"messages": [
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "plmnt168z8fyyzap0nw75d4atv9ucr2ye60d57dzlzaf",
"to_address": "plmnt1vklujvmr9hsk9zwpquk4waecr2u5vcyjd8vgm8",
"amount": [
{
"denom": "plmnt",
"amount": "10"
}
]
},
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "plmnt168z8fyyzap0nw75d4atv9ucr2ye60d57dzlzaf",
"to_address": "plmnt1pwquxvqmmdry4gdel4g4rz0js7jy65453h92g7",
"amount": [
{
"denom": "plmnt",
"amount": "10"
}
]
},
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "plmnt168z8fyyzap0nw75d4atv9ucr2ye60d57dzlzaf",
"to_address": "plmnt1dyuhg8ldu3d6nvhrvzzemtc3893dys9v9lvdty",
"amount": [
{
"denom": "plmnt",
"amount": "10"
}
]
}
],
"memo": "",
"timeout_height": "0",
"extension_options": [],
"non_critical_extension_options": []
},
"auth_info": {
"signer_infos": [
{
"public_key": {
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "AzthjTLaH+NCBBRH4NImcxSa9Ma59TJ0ntQE9S5TJ/wb"
},
"mode_info": {
"single": {
"mode": "SIGN_MODE_DIRECT"
}
},
"sequence": "6"
}
],
"fee": {
"amount": [
{
"denom": "plmnt",
"amount": "1"
}
],
"gas_limit": "200000",
"payer": "",
"granter": ""
},
"tip": null
},
"signatures": [
"iolwpJtcFPKshQWMfgvYO+EMSavdq0auicWZCNI46AIBWH6aPEca7esfqdv2m6VE4hCHzxCNx58wnfVNnutEEQ=="
]
}
```

63
lib/config.go Normal file
View File

@ -0,0 +1,63 @@
package lib
import (
"sync"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Config defines library top level configuration.
type Config struct {
ChainID string `mapstructure:"chain-id" json:"chain-id"`
RootDir string `mapstructure:"root-dir" json:"root-dir"`
RPCEndpoint string `mapstructure:"rpc-endpoint" json:"rpc-endpoint"`
}
// lib wide global singleton
var (
libConfig *Config
sdkConfig *sdk.Config
initConfig sync.Once
)
// DefaultConfig returns library default configuration.
func DefaultConfig() *Config {
return &Config{
ChainID: "planetmint-testnet-1",
RootDir: "~/.planetmint-go/",
RPCEndpoint: "http://127.0.0.1:1317",
}
}
// GetConfig returns the config instance for the SDK.
func GetConfig() *Config {
initConfig.Do(func() {
libConfig = DefaultConfig()
sdkConfig = sdk.GetConfig()
})
return libConfig
}
// SetBech32PrefixForAccount sets the bech32 account prefix.
func (config *Config) SetBech32PrefixForAccount(bech32Prefix string) *Config {
sdkConfig.SetBech32PrefixForAccount(bech32Prefix, "pub")
return config
}
// SetChainID sets the chain ID parameter.
func (config *Config) SetChainID(chainID string) *Config {
config.ChainID = chainID
return config
}
// SetRoot sets the root directory where to find the keyring.
func (config *Config) SetRoot(root string) *Config {
config.RootDir = root
return config
}
// SetRPCEndpoint sets the RPC endpoint to send requests to.
func (config *Config) SetRPCEndpoint(rpcEndpoint string) *Config {
config.RPCEndpoint = rpcEndpoint
return config
}

178
lib/go.mod Normal file
View File

@ -0,0 +1,178 @@
module github.com/planetmint/planetmint-go/lib
go 1.20
require (
github.com/99designs/keyring v1.2.1
github.com/cosmos/cosmos-sdk v0.47.5
github.com/planetmint/planetmint-go v0.5.5
)
require (
cloud.google.com/go v0.110.4 // indirect
cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.0 // indirect
cloud.google.com/go/storage v1.30.1 // indirect
cosmossdk.io/api v0.3.1 // indirect
cosmossdk.io/core v0.5.1 // indirect
cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
cosmossdk.io/errors v1.0.0 // indirect
cosmossdk.io/log v1.2.1 // indirect
cosmossdk.io/math v1.1.2 // indirect
cosmossdk.io/tools/rosetta v0.2.1 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/aws/aws-sdk-go v1.44.203 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
github.com/btcsuite/btcd v0.23.2 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/btcsuite/btcd/btcutil v1.1.2 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
github.com/cockroachdb/errors v1.10.0 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/coinbase/rosetta-sdk-go/types v1.0.0 // indirect
github.com/cometbft/cometbft v0.37.2 // indirect
github.com/cometbft/cometbft-db v0.7.0 // indirect
github.com/confio/ics23/go v0.9.0 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/gogoproto v1.4.10 // indirect
github.com/cosmos/iavl v0.20.0 // indirect
github.com/cosmos/ibc-go/v7 v7.1.0 // indirect
github.com/cosmos/ics23/go v0.10.0 // indirect
github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect
github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect
github.com/creachadair/taskgroup v0.4.2 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/getsentry/sentry-go v0.23.0 // indirect
github.com/go-kit/kit v0.12.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/orderedcode v0.0.1 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/gtank/merlin v0.1.1 // indirect
github.com/gtank/ristretto255 v0.1.2 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-getter v1.7.1 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hdevalence/ed25519consensus v0.1.0 // indirect
github.com/huandu/skiplist v1.2.0 // indirect
github.com/improbable-eng/grpc-web v0.15.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/klauspost/compress v1.16.3 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/rakyll/statik v0.1.7 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/rs/cors v1.8.3 // indirect
github.com/rs/zerolog v1.30.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.15.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.6.0 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/zondax/hid v0.9.1 // indirect
github.com/zondax/ledger-go v0.14.1 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.126.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.56.3 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
nhooyr.io/websocket v1.8.6 // indirect
pgregory.net/rapid v0.5.5 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

1679
lib/go.sum Normal file

File diff suppressed because it is too large Load Diff

214
lib/tx.go Normal file
View File

@ -0,0 +1,214 @@
package lib
import (
"bytes"
"encoding/hex"
"encoding/json"
"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"
"github.com/planetmint/planetmint-go/app"
)
// 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 := app.MakeEncodingConfig()
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
}