mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-23 01:55:10 +00:00
REST api added for asset transfer in Golang
Signed-off-by: Basil K Y <techiebasil@gmail.com>
This commit is contained in:
parent
ea533d61b9
commit
9485e4bd10
8 changed files with 289 additions and 0 deletions
3
asset-transfer-basic/rest-api-go/.gitignore
vendored
Normal file
3
asset-transfer-basic/rest-api-go/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
go.sum
|
||||
Requests.http
|
||||
rest-api-go
|
||||
39
asset-transfer-basic/rest-api-go/README.md
Normal file
39
asset-transfer-basic/rest-api-go/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Asset Transfer REST API Sample
|
||||
|
||||
This is a simple REST server written in golang with endpoints for chaincode invoke and query.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
- Setup fabric test network and deploy the asset transfer chaincode by [following this instructions](https://hyperledger-fabric.readthedocs.io/en/release-2.4/test_network.html).
|
||||
|
||||
- cd into rest-api-go directory
|
||||
- Download required dependencies using `go mod download`
|
||||
- Run `go run main.go` to run the REST server
|
||||
|
||||
## Sending Requests
|
||||
|
||||
Invoke endpoint accepts POST requests with chaincode function and arguments. Query endpoint accepts get requests with chaincode function and arguments.
|
||||
|
||||
Sample chaincode invoke for the "createAsset" function. Response will contain transaction ID for a successful invoke.
|
||||
|
||||
``` sh
|
||||
curl --request POST \
|
||||
--url http://localhost:3000/invoke \
|
||||
--header 'content-type: application/x-www-form-urlencoded' \
|
||||
--data = \
|
||||
--data channelid=mychannel \
|
||||
--data chaincodeid=basic \
|
||||
--data function=createAsset \
|
||||
--data args=Asset123 \
|
||||
--data args=yellow \
|
||||
--data args=54 \
|
||||
--data args=Tom \
|
||||
--data args=13005
|
||||
```
|
||||
Sample chaincode query for getting asset details.
|
||||
|
||||
``` sh
|
||||
curl --request GET \
|
||||
--url 'http://localhost:3000/query?channelid=mychannel&chaincodeid=basic&function=ReadAsset&args=Asset123'
|
||||
```
|
||||
19
asset-transfer-basic/rest-api-go/go.mod
Normal file
19
asset-transfer-basic/rest-api-go/go.mod
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
module rest-api-go
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/hyperledger/fabric-gateway v1.1.0
|
||||
google.golang.org/grpc v1.49.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/hyperledger/fabric-protos-go-apiv2 v0.0.0-20220615102044-467be1c7b2e7 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220527130721-00d5c0f3be58 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
)
|
||||
26
asset-transfer-basic/rest-api-go/main.go
Normal file
26
asset-transfer-basic/rest-api-go/main.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"rest-api-go/web"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//Initialize setup for Org1
|
||||
cryptoPath := "../../test-network/organizations/peerOrganizations/org1.example.com"
|
||||
orgConfig := web.OrgSetup{
|
||||
OrgName: "Org1",
|
||||
MSPID: "Org1MSP",
|
||||
CertPath: cryptoPath + "/users/User1@org1.example.com/msp/signcerts/cert.pem",
|
||||
KeyPath: cryptoPath + "/users/User1@org1.example.com/msp/keystore/",
|
||||
TLSCertPath: cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt",
|
||||
PeerEndpoint: "localhost:7051",
|
||||
GatewayPeer: "peer0.org1.example.com",
|
||||
}
|
||||
|
||||
orgSetup, err := web.Initialize(orgConfig)
|
||||
if err != nil {
|
||||
fmt.Println("Error initializing setup for Org1: ", err)
|
||||
}
|
||||
web.Serve(web.OrgSetup(*orgSetup))
|
||||
}
|
||||
31
asset-transfer-basic/rest-api-go/web/app.go
Normal file
31
asset-transfer-basic/rest-api-go/web/app.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hyperledger/fabric-gateway/pkg/client"
|
||||
)
|
||||
|
||||
// OrgSetup contains organization's config to interact with the network.
|
||||
type OrgSetup struct {
|
||||
OrgName string
|
||||
MSPID string
|
||||
CryptoPath string
|
||||
CertPath string
|
||||
KeyPath string
|
||||
TLSCertPath string
|
||||
PeerEndpoint string
|
||||
GatewayPeer string
|
||||
Gateway client.Gateway
|
||||
}
|
||||
|
||||
// Serve starts http web server.
|
||||
func Serve(setups OrgSetup) {
|
||||
http.HandleFunc("/query", setups.Query)
|
||||
http.HandleFunc("/invoke", setups.Invoke)
|
||||
fmt.Println("Listening (http://localhost:3000/)...")
|
||||
if err := http.ListenAndServe(":3000", nil); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
106
asset-transfer-basic/rest-api-go/web/initialize.go
Normal file
106
asset-transfer-basic/rest-api-go/web/initialize.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/hyperledger/fabric-gateway/pkg/client"
|
||||
"github.com/hyperledger/fabric-gateway/pkg/identity"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
// Initialize the setup for the organization.
|
||||
func Initialize(setup OrgSetup) (*OrgSetup, error) {
|
||||
log.Printf("Initializing connection for %s...\n", setup.OrgName)
|
||||
clientConnection := setup.newGrpcConnection()
|
||||
id := setup.newIdentity()
|
||||
sign := setup.newSign()
|
||||
|
||||
gateway, err := client.Connect(
|
||||
id,
|
||||
client.WithSign(sign),
|
||||
client.WithClientConnection(clientConnection),
|
||||
client.WithEvaluateTimeout(5*time.Second),
|
||||
client.WithEndorseTimeout(15*time.Second),
|
||||
client.WithSubmitTimeout(5*time.Second),
|
||||
client.WithCommitStatusTimeout(1*time.Minute),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
setup.Gateway = *gateway
|
||||
log.Println("Initialization complete")
|
||||
return &setup, nil
|
||||
}
|
||||
|
||||
// newGrpcConnection creates a gRPC connection to the Gateway server.
|
||||
func (setup OrgSetup) newGrpcConnection() *grpc.ClientConn {
|
||||
certificate, err := loadCertificate(setup.TLSCertPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
certPool := x509.NewCertPool()
|
||||
certPool.AddCert(certificate)
|
||||
transportCredentials := credentials.NewClientTLSFromCert(certPool, setup.GatewayPeer)
|
||||
|
||||
connection, err := grpc.Dial(setup.PeerEndpoint, grpc.WithTransportCredentials(transportCredentials))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to create gRPC connection: %w", err))
|
||||
}
|
||||
|
||||
return connection
|
||||
}
|
||||
|
||||
// newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
|
||||
func (setup OrgSetup) newIdentity() *identity.X509Identity {
|
||||
certificate, err := loadCertificate(setup.CertPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
id, err := identity.NewX509Identity(setup.MSPID, certificate)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// newSign creates a function that generates a digital signature from a message digest using a private key.
|
||||
func (setup OrgSetup) newSign() identity.Sign {
|
||||
files, err := ioutil.ReadDir(setup.KeyPath)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to read private key directory: %w", err))
|
||||
}
|
||||
privateKeyPEM, err := ioutil.ReadFile(path.Join(setup.KeyPath, files[0].Name()))
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to read private key file: %w", err))
|
||||
}
|
||||
|
||||
privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sign, err := identity.NewPrivateKeySign(privateKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sign
|
||||
}
|
||||
|
||||
func loadCertificate(filename string) (*x509.Certificate, error) {
|
||||
certificatePEM, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read certificate file: %w", err)
|
||||
}
|
||||
return identity.CertificateFromPEM(certificatePEM)
|
||||
}
|
||||
40
asset-transfer-basic/rest-api-go/web/invoke.go
Normal file
40
asset-transfer-basic/rest-api-go/web/invoke.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hyperledger/fabric-gateway/pkg/client"
|
||||
)
|
||||
|
||||
// Invoke handles chaincode invoke requests.
|
||||
func (setup *OrgSetup) Invoke(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Received Invoke request")
|
||||
if err := r.ParseForm(); err != nil {
|
||||
fmt.Fprintf(w, "ParseForm() err: %s", err)
|
||||
return
|
||||
}
|
||||
chainCodeName := r.FormValue("chaincodeid")
|
||||
channelID := r.FormValue("channelid")
|
||||
function := r.FormValue("function")
|
||||
args := r.Form["args"]
|
||||
fmt.Printf("channel: %s, chaincode: %s, function: %s, args: %s\n", channelID, chainCodeName, function, args)
|
||||
network := setup.Gateway.GetNetwork(channelID)
|
||||
contract := network.GetContract(chainCodeName)
|
||||
txn_proposal, err := contract.NewProposal(function, client.WithArguments(args...))
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Error creating txn proposal: %s", err)
|
||||
return
|
||||
}
|
||||
txn_endorsed, err := txn_proposal.Endorse()
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Error endorsing txn: %s", err)
|
||||
return
|
||||
}
|
||||
txn_committed, err := txn_endorsed.Submit()
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Error submitting transaction: %s", err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "Transaction ID : %s Response: %s", txn_committed.TransactionID(), txn_endorsed.Result())
|
||||
}
|
||||
25
asset-transfer-basic/rest-api-go/web/query.go
Normal file
25
asset-transfer-basic/rest-api-go/web/query.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Query handles chaincode query requests.
|
||||
func (setup OrgSetup) Query(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Received Query request")
|
||||
queryParams := r.URL.Query()
|
||||
chainCodeName := queryParams.Get("chaincodeid")
|
||||
channelID := queryParams.Get("channelid")
|
||||
function := queryParams.Get("function")
|
||||
args := r.URL.Query()["args"]
|
||||
fmt.Printf("channel: %s, chaincode: %s, function: %s, args: %s\n", channelID, chainCodeName, function, args)
|
||||
network := setup.Gateway.GetNetwork(channelID)
|
||||
contract := network.GetContract(chainCodeName)
|
||||
evaluateResponse, err := contract.EvaluateTransaction(function, args...)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Error: %s", err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "Response: %s", evaluateResponse)
|
||||
}
|
||||
Loading…
Reference in a new issue