Replace call to external python program (#215)

* Split asset registry endpoint

Into scheme, domain and path. We need the domain in the liquid contract.

* Switch to pure Go implementation

This is a reimplementation of the `issue2liquid.py`.

See https://github.com/rddl-network/issuer_service

// Closes #196

* Move elements RPC URL into config

Signed-off-by: Julian Strobl <jmastr@mailbox.org>
This commit is contained in:
Julian Strobl 2023-12-11 15:53:58 +01:00 committed by GitHub
parent 363d82d344
commit ede70b073b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 164 additions and 71 deletions

View File

@ -2,6 +2,7 @@ package config
import (
"encoding/json"
"fmt"
"sync"
)
@ -12,7 +13,9 @@ const DefaultConfigTemplate = `
[planetmint]
asset-registry-endpoint = "{{ .PlmntConfig.AssetRegistryEndpoint }}"
asset-registry-scheme = "{{ .PlmntConfig.AssetRegistryScheme}}"
asset-registry-domain = "{{ .PlmntConfig.AssetRegistryDomain }}"
asset-registry-path = "{{ .PlmntConfig.AssetRegistryPath }}"
token-denom = "{{ .PlmntConfig.TokenDenom }}"
stake-denom = "{{ .PlmntConfig.StakeDenom }}"
fee-denom = "{{ .PlmntConfig.FeeDenom }}"
@ -23,7 +26,8 @@ rpc-host = "{{ .PlmntConfig.RPCHost }}"
rpc-port = {{ .PlmntConfig.RPCPort }}
rpc-user = "{{ .PlmntConfig.RPCUser }}"
rpc-password = "{{ .PlmntConfig.RPCPassword }}"
issuance-service-dir = "{{ .PlmntConfig.IssuanceServiceDir }}"
rpc-scheme = "{{ .PlmntConfig.RPCScheme }}"
rpc-wallet = "{{ .PlmntConfig.RPCWallet }}"
reissuance-asset = "{{ .PlmntConfig.ReissuanceAsset }}"
validator-address = "{{ .PlmntConfig.ValidatorAddress }}"
distribution-address-inv = "{{ .PlmntConfig.DistributionAddrInv }}"
@ -35,26 +39,29 @@ re-issuance-epochs = {{ .PlmntConfig.ReIssuanceEpochs }}
// Config defines Planetmint's top level configuration
type Config struct {
AssetRegistryEndpoint string `json:"asset-registry-endpoint" mapstructure:"asset-registry-endpoint"`
TokenDenom string `json:"token-denom" mapstructure:"token-denom"`
StakeDenom string `json:"stake-denom" mapstructure:"stake-denom"`
FeeDenom string `json:"fee-denom" mapstructure:"fee-denom"`
StagedDenom string `json:"staged-denom" mapstructure:"staged-denom"`
ClaimDenom string `json:"claim-denom" mapstructure:"claim-denom"`
ConfigRootDir string `json:"config-root-dir" mapstructure:"config-root-dir"`
PopEpochs int `json:"pop-epochs" mapstructure:"pop-epochs"`
RPCHost string `json:"rpc-host" mapstructure:"rpc-host"`
RPCPort int `json:"rpc-port" mapstructure:"rpc-port"`
RPCUser string `json:"rpc-user" mapstructure:"rpc-user"`
RPCPassword string `json:"rpc-password" mapstructure:"rpc-password"`
IssuanceServiceDir string `json:"issuance-service-dir" mapstructure:"issuance-service-dir"`
ReissuanceAsset string `json:"reissuance-asset" mapstructure:"reissuance-asset"`
ValidatorAddress string `json:"validator-address" mapstructure:"validator-address"`
DistributionAddrInv string `json:"distribution-addr-inv" mapstructure:"distribution-addr-inv"`
DistributionAddrDAO string `json:"distribution-addr-dao" mapstructure:"distribution-addr-dao"`
DistributionAddrPop string `json:"distribution-addr-pop" mapstructure:"distribution-addr-pop"`
DistributionEpochs int `json:"distribution-epochs" mapstructure:"distribution-epochs"`
ReIssuanceEpochs int `json:"re-issuance-epochs" mapstructure:"re-issuance-epochs"`
AssetRegistryScheme string `json:"asset-registry-scheme" mapstructure:"asset-registry-scheme"`
AssetRegistryDomain string `json:"asset-registry-domain" mapstructure:"asset-registry-domain"`
AssetRegistryPath string `json:"asset-registry-path" mapstructure:"asset-registry-path"`
TokenDenom string `json:"token-denom" mapstructure:"token-denom"`
StakeDenom string `json:"stake-denom" mapstructure:"stake-denom"`
FeeDenom string `json:"fee-denom" mapstructure:"fee-denom"`
StagedDenom string `json:"staged-denom" mapstructure:"staged-denom"`
ClaimDenom string `json:"claim-denom" mapstructure:"claim-denom"`
ConfigRootDir string `json:"config-root-dir" mapstructure:"config-root-dir"`
PopEpochs int `json:"pop-epochs" mapstructure:"pop-epochs"`
RPCHost string `json:"rpc-host" mapstructure:"rpc-host"`
RPCPort int `json:"rpc-port" mapstructure:"rpc-port"`
RPCUser string `json:"rpc-user" mapstructure:"rpc-user"`
RPCPassword string `json:"rpc-password" mapstructure:"rpc-password"`
RPCScheme string `json:"rpc-scheme" mapstructure:"rpc-scheme"`
RPCWallet string `json:"rpc-wallet" mapstructure:"rpc-wallet"`
ReissuanceAsset string `json:"reissuance-asset" mapstructure:"reissuance-asset"`
ValidatorAddress string `json:"validator-address" mapstructure:"validator-address"`
DistributionAddrInv string `json:"distribution-addr-inv" mapstructure:"distribution-addr-inv"`
DistributionAddrDAO string `json:"distribution-addr-dao" mapstructure:"distribution-addr-dao"`
DistributionAddrPop string `json:"distribution-addr-pop" mapstructure:"distribution-addr-pop"`
DistributionEpochs int `json:"distribution-epochs" mapstructure:"distribution-epochs"`
ReIssuanceEpochs int `json:"re-issuance-epochs" mapstructure:"re-issuance-epochs"`
}
// cosmos-sdk wide global singleton
@ -66,26 +73,29 @@ var (
// DefaultConfig returns planetmint's default configuration.
func DefaultConfig() *Config {
return &Config{
AssetRegistryEndpoint: "https://assets.rddl.io/register_asset",
TokenDenom: "plmnt",
StakeDenom: "plmntstake",
FeeDenom: "plmnt",
StagedDenom: "stagedcrddl",
ClaimDenom: "crddl",
ConfigRootDir: "",
PopEpochs: 24, // 24 CometBFT epochs of 5s equate 120s
RPCHost: "localhost",
RPCPort: 18884,
RPCUser: "user",
RPCPassword: "password",
IssuanceServiceDir: "/opt/issuer_service",
ReissuanceAsset: "7add40beb27df701e02ee85089c5bc0021bc813823fedb5f1dcb5debda7f3da9",
ValidatorAddress: "plmnt1w5dww335zhh98pzv783hqre355ck3u4w4hjxcx",
DistributionAddrInv: "vjTyRN2G42Yq3T5TJBecHj1dF1xdhKF89hKV4HJN3uXxUbaVGVR76hAfVRQqQCovWaEpar7G5qBBprFG",
DistributionAddrDAO: "vjU8eMzU3JbUWZEpVANt2ePJuPWSPixgjiSj2jDMvkVVQQi2DDnZuBRVX4Ygt5YGBf5zvTWCr1ntdqYH",
DistributionAddrPop: "vjTvXCFSReRsZ7grdsAreRR12KuKpDw8idueQJK9Yh1BYS7ggAqgvCxCgwh13KGK6M52y37HUmvr4GdD",
DistributionEpochs: 17640, // CometBFT epochs of 5s equate 1 day (12*60*24) + 15 min (15*24) to wait for confirmations on the re-issuance
ReIssuanceEpochs: 17280, // CometBFT epochs of 5s equate 1 day (12*60*24)
AssetRegistryScheme: "https",
AssetRegistryDomain: "assets.rddl.io",
AssetRegistryPath: "register_asset",
TokenDenom: "plmnt",
StakeDenom: "plmntstake",
FeeDenom: "plmnt",
StagedDenom: "stagedcrddl",
ClaimDenom: "crddl",
ConfigRootDir: "",
PopEpochs: 24, // 24 CometBFT epochs of 5s equate 120s
RPCHost: "localhost",
RPCPort: 18884,
RPCUser: "user",
RPCPassword: "password",
RPCScheme: "http",
RPCWallet: "rpcwallet",
ReissuanceAsset: "7add40beb27df701e02ee85089c5bc0021bc813823fedb5f1dcb5debda7f3da9",
ValidatorAddress: "plmnt1w5dww335zhh98pzv783hqre355ck3u4w4hjxcx",
DistributionAddrInv: "vjTyRN2G42Yq3T5TJBecHj1dF1xdhKF89hKV4HJN3uXxUbaVGVR76hAfVRQqQCovWaEpar7G5qBBprFG",
DistributionAddrDAO: "vjU8eMzU3JbUWZEpVANt2ePJuPWSPixgjiSj2jDMvkVVQQi2DDnZuBRVX4Ygt5YGBf5zvTWCr1ntdqYH",
DistributionAddrPop: "vjTvXCFSReRsZ7grdsAreRR12KuKpDw8idueQJK9Yh1BYS7ggAqgvCxCgwh13KGK6M52y37HUmvr4GdD",
DistributionEpochs: 17640, // CometBFT epochs of 5s equate 1 day (12*60*24) + 15 min (15*24) to wait for confirmations on the re-issuance
ReIssuanceEpochs: 17280, // CometBFT epochs of 5s equate 1 day (12*60*24)
}
}
@ -97,6 +107,12 @@ func GetConfig() *Config {
return plmntConfig
}
// GetRPCURL returns the elements RPC URL
func (config *Config) GetRPCURL() (url string) {
url = fmt.Sprintf("%s://%s:%s@%s:%d/wallet/%s", config.RPCScheme, config.RPCUser, config.RPCPassword, config.RPCHost, config.RPCPort, config.RPCWallet)
return
}
func (config *Config) SetRoot(root string) *Config {
config.ConfigRootDir = root
return config

1
go.mod
View File

@ -21,6 +21,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
github.com/planetmint/planetmint-go/lib v0.2.1
github.com/rddl-network/elements-rpc v0.2.0
github.com/spf13/cast v1.5.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5

2
go.sum
View File

@ -889,6 +889,8 @@ github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Ung
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rddl-network/elements-rpc v0.2.0 h1:xZCbNZaeVYO2gVnwYUF25/aPKcCYcOwjUZM0hb1C/xI=
github.com/rddl-network/elements-rpc v0.2.0/go.mod h1:WOSYDMhq+V74lReSInnSejbdEyGI8hiQZSn4cSoFuxo=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=

View File

@ -3,16 +3,19 @@ package keeper
import (
"bytes"
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os/exec"
"strconv"
"strings"
config "github.com/planetmint/planetmint-go/config"
"github.com/planetmint/planetmint-go/util"
"github.com/planetmint/planetmint-go/x/machine/types"
elements "github.com/rddl-network/elements-rpc"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
@ -85,35 +88,93 @@ func validateExtendedPublicKey(issuer string, cfg chaincfg.Params) bool {
func (k msgServer) issueNFTAsset(goCtx context.Context, name string, machineAddress string) (assetID string, contract string, err error) {
ctx := sdk.UnwrapSDKContext(goCtx)
conf := config.GetConfig()
cmdName := "poetry"
cmdArgs := []string{"run", "python", "issuer_service/issue2liquid.py", name, machineAddress}
// Create a new command
cmd := exec.Command(cmdName, cmdArgs...)
// If you want to set the working directory
cmd.Dir = conf.IssuanceServiceDir
// Capture the output
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
// Execute the command
err = cmd.Run()
url := conf.GetRPCURL()
address, err := elements.GetNewAddress(url, []string{``})
if err != nil {
util.GetAppLogger().Error(ctx, "Issue2Liquid.py failed with %s\n", err)
err = errorsmod.Wrap(types.ErrMachineNFTIssuance, stderr.String())
} else {
util.GetAppLogger().Info(ctx, "Liquid Token Issuance: "+stdout.String())
lines := strings.Split(stdout.String(), "\n")
if len(lines) == 3 {
assetID = lines[0]
contract = lines[1]
} else {
err = errorsmod.Wrap(types.ErrMachineNFTIssuanceNoOutput, stderr.String())
}
return
}
addressInfo, err := elements.GetAddressInfo(url, []string{address})
if err != nil {
return
}
hex, err := elements.CreateRawTransaction(url, []string{`[]`, `[{"data":"00"}]`})
if err != nil {
return
}
fundRawTransactionResult, err := elements.FundRawTransaction(url, []string{hex, `{"feeRate":0.00001000}`})
if err != nil {
return
}
c := types.Contract{
Entity: types.Entity{
Domain: conf.AssetRegistryDomain,
},
IssuerPubkey: addressInfo.Pubkey,
MachineAddr: machineAddress,
Name: name,
Precision: 0,
Version: 0,
}
contractBytes, err := json.Marshal(c)
if err != nil {
return
}
// e.g. {"entity":{"domain":"testnet-assets.rddl.io"}, "issuer_pubkey":"02...}
contract = string(contractBytes)
h := sha256.New()
_, err = h.Write(contractBytes)
if err != nil {
return
}
// e.g. 7ca8bb403ee5dccddef7b89b163048cf39439553f0402351217a4a03d2224df8
hash := h.Sum(nil)
// Reverse hash, e.g. f84d22d2034a7a21512340f053954339cf4830169bb8f7decddce53e40bba87c
for i, j := 0, len(hash)-1; i < j; i, j = i+1, j-1 {
hash[i], hash[j] = hash[j], hash[i]
}
rawIssueAssetResults, err := elements.RawIssueAsset(url, []string{fundRawTransactionResult.Hex,
`[{"asset_amount":0.00000001, "asset_address":"` + address + `", "blind":false, "contract_hash":"` + fmt.Sprintf("%+x", hash) + `"}]`,
})
if err != nil {
return
}
rawIssueAssetResult := rawIssueAssetResults[len(rawIssueAssetResults)-1]
hex, err = elements.BlindRawTransaction(url, []string{rawIssueAssetResult.Hex, `true`, `[]`, `false`})
if err != nil {
return
}
assetID = rawIssueAssetResult.Asset
signRawTransactionWithWalletResult, err := elements.SignRawTransactionWithWallet(url, []string{hex})
if err != nil {
return
}
testMempoolAcceptResults, err := elements.TestMempoolAccept(url, []string{`["` + signRawTransactionWithWalletResult.Hex + `"]`})
if err != nil {
return
}
testMempoolAcceptResult := testMempoolAcceptResults[len(testMempoolAcceptResults)-1]
if !testMempoolAcceptResult.Allowed {
log.Fatalln("not accepted by mempool")
}
hex, err = elements.SendRawTransaction(url, []string{signRawTransactionWithWalletResult.Hex})
if err != nil {
return
}
util.GetAppLogger().Info(ctx, "Liquid Token Issuance assetID: "+assetID+" contract: "+contract+" tx: "+hex)
return assetID, contract, err
}
@ -160,7 +221,8 @@ func (k msgServer) registerAsset(goCtx context.Context, assetID string, contract
return errorsmod.Wrap(types.ErrAssetRegistryReqFailure, "Marshall "+err.Error())
}
req, err := http.NewRequestWithContext(goCtx, http.MethodPost, conf.AssetRegistryEndpoint, bytes.NewBuffer(jsonData))
assetRegistryEndpoint := fmt.Sprintf("%s://%s/%s", conf.AssetRegistryScheme, conf.AssetRegistryDomain, conf.AssetRegistryPath)
req, err := http.NewRequestWithContext(goCtx, http.MethodPost, assetRegistryEndpoint, bytes.NewBuffer(jsonData))
if err != nil {
return errorsmod.Wrap(types.ErrAssetRegistryReqFailure, "Request creation: "+err.Error())
}

View File

@ -18,7 +18,6 @@ var (
ErrInvalidTrustAnchorKey = errorsmod.Register(ModuleName, 9, "invalid trust anchor pubkey")
ErrTrustAnchorAlreadyRegistered = errorsmod.Register(ModuleName, 10, "trust anchor is already registered")
ErrMachineNFTIssuance = errorsmod.Register(ModuleName, 11, "the machine NFT could not be issued")
ErrMachineNFTIssuanceNoOutput = errorsmod.Register(ModuleName, 12, "the machine NFT issuing process derivated")
ErrAssetRegistryReqFailure = errorsmod.Register(ModuleName, 13, "request to asset registry could not be created")
ErrAssetRegistryReqSending = errorsmod.Register(ModuleName, 14, "request to asset registry could not be sent")
ErrAssetRegistryRepsonse = errorsmod.Register(ModuleName, 15, "request response issue")

View File

@ -1 +1,14 @@
package types
type Entity struct {
Domain string `json:"domain"`
}
type Contract struct {
Entity Entity `json:"entity"`
IssuerPubkey string `json:"issuer_pubkey"` //nolint:tagliatelle // the format liquid network needs it
MachineAddr string `json:"machine_addr"` //nolint:tagliatelle // the format liquid network needs it
Name string `json:"name"`
Precision uint64 `json:"precision"`
Version uint64 `json:"version"`
}