diff --git a/clients/shamir_coordinator_client.go b/clients/shamir_coordinator_client.go index 5660974..6183e96 100644 --- a/clients/shamir_coordinator_client.go +++ b/clients/shamir_coordinator_client.go @@ -50,11 +50,21 @@ func ReIssueAsset(ctx context.Context, asset string, amount string) (txID string return res.TxID, nil } +func IssueNFTAsset(ctx context.Context, name string, machineAddress string, domain string) (assetID string, contract string, hexTx string, err error) { + client := lazyLoadShamirCoordinatorClient() + res, err := client.IssueMachineNFT(ctx, name, machineAddress, domain) + if err != nil { + return + } + return res.Asset, res.Contract, res.HexTX, nil +} + type IShamirCoordinatorClient interface { GetMnemonics(ctx context.Context) (res MnemonicsResponse, err error) PostMnemonics(ctx context.Context, secret string) (err error) SendTokens(ctx context.Context, recipient string, amount string, asset string) (res SendTokensResponse, err error) ReIssueAsset(ctx context.Context, asset string, amount string) (res ReIssueResponse, err error) + IssueMachineNFT(ctx context.Context, name string, machineAddress string, domain string) (res IssueMachineNFTResponse, err error) } type SendTokensRequest struct { @@ -81,6 +91,18 @@ type MnemonicsResponse struct { Seed string `binding:"required" json:"seed"` } +type IssueMachineNFTRequest struct { + Name string `binding:"required" json:"name"` + MachineAddress string `binding:"required" json:"machine-address"` + Domain string `binding:"required" json:"domain"` +} + +type IssueMachineNFTResponse struct { + Asset string `binding:"required" json:"asset"` + Contract string `binding:"required" json:"contract"` + HexTX string `binding:"required" json:"hex-tx"` +} + type ShamirCoordinatorClient struct { baseURL string client *http.Client @@ -125,6 +147,16 @@ func (scc *ShamirCoordinatorClient) ReIssueAsset(ctx context.Context, asset stri return } +func (scc *ShamirCoordinatorClient) IssueMachineNFT(ctx context.Context, name string, machineAddress string, domain string) (res IssueMachineNFTResponse, err error) { + requestBody := IssueMachineNFTRequest{ + Name: name, + MachineAddress: machineAddress, + Domain: domain, + } + err = scc.doRequest(ctx, http.MethodPost, scc.baseURL+"/issue-machine-nft", &requestBody, &res) + return +} + func (scc *ShamirCoordinatorClient) doRequest(ctx context.Context, method, url string, body interface{}, response interface{}) (err error) { var bodyReader io.Reader if body != nil { diff --git a/testutil/mocks/shamir_coordinator_client_mock.go b/testutil/mocks/shamir_coordinator_client_mock.go index 57dc84f..d98db8b 100644 --- a/testutil/mocks/shamir_coordinator_client_mock.go +++ b/testutil/mocks/shamir_coordinator_client_mock.go @@ -50,6 +50,21 @@ func (mr *MockIShamirCoordinatorClientMockRecorder) GetMnemonics(ctx interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMnemonics", reflect.TypeOf((*MockIShamirCoordinatorClient)(nil).GetMnemonics), ctx) } +// IssueMachineNFT mocks base method. +func (m *MockIShamirCoordinatorClient) IssueMachineNFT(ctx context.Context, name, machineAddress, domain string) (clients.IssueMachineNFTResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IssueMachineNFT", ctx, name, machineAddress, domain) + ret0, _ := ret[0].(clients.IssueMachineNFTResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IssueMachineNFT indicates an expected call of IssueMachineNFT. +func (mr *MockIShamirCoordinatorClientMockRecorder) IssueMachineNFT(ctx, name, machineAddress, domain interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueMachineNFT", reflect.TypeOf((*MockIShamirCoordinatorClient)(nil).IssueMachineNFT), ctx, name, machineAddress, domain) +} + // PostMnemonics mocks base method. func (m *MockIShamirCoordinatorClient) PostMnemonics(ctx context.Context, secret string) error { m.ctrl.T.Helper() diff --git a/testutil/network/loader.go b/testutil/network/loader.go index a4db58b..df69f7a 100644 --- a/testutil/network/loader.go +++ b/testutil/network/loader.go @@ -62,6 +62,11 @@ func Load(t *testing.T, configs ...Config) *Network { shamirMock.EXPECT().ReIssueAsset(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(clients.ReIssueResponse{ TxID: "7add40beb27df701e02ee85089c5bc0021bc813823fedb5f1dcb5debda7f3da9", }, nil) + shamirMock.EXPECT().IssueMachineNFT(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(clients.IssueMachineNFTResponse{ + HexTX: "0000000000000000000000000000000000000000000000000000000000000000", + Contract: "contract", + Asset: "7add40beb27df701e02ee85089c5bc0021bc813823fedb5f1dcb5debda7f3da9", + }, nil) clients.ShamirCoordinatorServiceClient = shamirMock // enable application logger in tests diff --git a/util/elementsd_connector.go b/util/elementsd_connector.go deleted file mode 100644 index 6fd5c61..0000000 --- a/util/elementsd_connector.go +++ /dev/null @@ -1,112 +0,0 @@ -package util - -import ( - "crypto/sha256" - "encoding/json" - "fmt" - "sync" - - "github.com/planetmint/planetmint-go/config" - "github.com/planetmint/planetmint-go/x/machine/types" - elements "github.com/rddl-network/elements-rpc" -) - -var ( - // this mutex has to protect all signing and crafting of transactions and their inputs - // so that UTXOs are not spend twice by accident - elementsSyncAccess sync.Mutex -) - -func IssueNFTAsset(name string, machineAddress string, domain string) (assetID string, contract string, hexTx string, err error) { - conf := config.GetConfig() - url := conf.GetRPCURL() - - address, err := elements.GetNewAddress(url, []string{``}) - if err != nil { - return - } - - addressInfo, err := elements.GetAddressInfo(url, []string{`"` + address + `"`}) - if err != nil { - return - } - - elementsSyncAccess.Lock() - defer elementsSyncAccess.Unlock() - 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: domain, - }, - 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 { - err = fmt.Errorf("not accepted by mempool: %+v %+v", testMempoolAcceptResult, signRawTransactionWithWalletResult) - return - } - - hex, err = elements.SendRawTransaction(url, []string{`"` + signRawTransactionWithWalletResult.Hex + `"`}) - if err != nil { - return - } - - return assetID, contract, hex, err -} diff --git a/util/elementsd_connector_test.go b/util/elementsd_connector_test.go deleted file mode 100644 index 7919071..0000000 --- a/util/elementsd_connector_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package util_test - -import ( - "math/rand" - "strconv" - "sync" - "testing" - - "github.com/planetmint/planetmint-go/testutil/moduleobject" - "github.com/planetmint/planetmint-go/testutil/sample" - "github.com/planetmint/planetmint-go/util" - "github.com/planetmint/planetmint-go/x/machine/types" - elements "github.com/rddl-network/elements-rpc" - elementsmocks "github.com/rddl-network/elements-rpc/utils/mocks" - "github.com/stretchr/testify/assert" -) - -func TestIssueNFTAsset(t *testing.T) { - elements.Client = &elementsmocks.MockClient{} - - params := types.DefaultParams() - var wg sync.WaitGroup - - for i := 0; i < 1; i++ { - wg.Add(1) - go func() { - randomInt := rand.Int() - sk, pk := sample.KeyPair(randomInt) - machine := moduleobject.MachineRandom(pk, pk, sk, "address "+strconv.Itoa(randomInt), randomInt) - - _, _, _, err := util.IssueNFTAsset(machine.Name, machine.Address, params.AssetRegistryDomain) - assert.NoError(t, err) - - wg.Done() - }() - } - wg.Wait() -} diff --git a/util/machine_nft.go b/util/machine_nft.go index 28eba1c..9ceb6ee 100644 --- a/util/machine_nft.go +++ b/util/machine_nft.go @@ -12,6 +12,7 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/planetmint/planetmint-go/clients" "github.com/planetmint/planetmint-go/x/machine/types" ) @@ -32,7 +33,7 @@ func IssueMachineNFT(goCtx context.Context, machine *types.Machine, scheme strin // asset registration is in order to have the contact published var notarizedAsset types.LiquidAsset notarizedAsset.Registered = true - assetID, contract, hex, err := IssueNFTAsset(machine.Name, machine.Address, domain) + assetID, contract, hex, err := clients.IssueNFTAsset(goCtx, machine.Name, machine.Address, domain) if err != nil { GetAppLogger().Error(ctx, err.Error()) return err