REST api added for asset transfer in Golang

Signed-off-by: Basil K Y <techiebasil@gmail.com>
This commit is contained in:
Basil K Y 2022-09-23 22:01:28 +05:30
parent ea533d61b9
commit 9485e4bd10
8 changed files with 289 additions and 0 deletions

View file

@ -0,0 +1,3 @@
go.sum
Requests.http
rest-api-go

View 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'
```

View 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
)

View 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))
}

View 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)
}
}

View 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)
}

View 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())
}

View 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)
}