README ¶
I O(we) (yo)U
In this section, we will cover a classic use-case, the I O(we) (yo)U
.
Let us consider two parties: a lender
and a borrower
.
The borrower owes the lender a certain amount of money.
Both parties want to track the evolution of this amount.
Moreover, the parties want an approver
to validate their operations.
We can think about the approver as a mediator between the parties.
Looking ahead, the approver plays the role of the endorser of the namespace
in a Fabric channel that contains the IOU state.
We have already learned, when exploring the 'Fabric's hidden gems',
that an endorser is just a network node that executes some code
and possesses a signing key compatible with an endorsement policy.
The node produces a signature to signal its approval. Therefore, if we equip an FSC node with
a signing secret key accepted by our endorsement policy, that FSC node can endorse Fabric transactions.
For this example, we will employ the State-Based Programming Model
(SPM, for short) that the FSC introduces.
This model provides an API that helps the developer to think in terms of states rather than
RWSet. A Transaction in SPM wraps a Fabric Transaction and let the developer:
- Add
references
to input states. The equivalent of a read-dependency in the RWSet. Delete
states. A write-entry in the RWSet thatdeletes a key
.Update
states. A write-entry in the RWSet thatupdates a key's value
. With the above, it is easy to model a UTXO-based with reference states on top of a Fabric transaction.
It is time to deep dive. Let us begin by modelling the state the parties want to track.
Business States
We just need to model a business state that represents the amount of money the borrower still owes the lender.
The state must be uniquely identifiable. Moreover, the state
should be owned or controllable by both the lender and the borrower. This means that any operation on the state
should be agreed
between the lender and the borrower.
Here is a way to code the above description.
// IOU models the IOU state
type IOU struct {
// Amount the borrower owes the lender
Amount uint
// Unique identifier of this state
LinearID string
// The list of owners of this state
Parties []view.Identity
}
func (i *IOU) SetLinearID(id string) string {
if len(i.LinearID) == 0 {
i.LinearID = id
}
return i.LinearID
}
func (i *IOU) Owners() state.Identities {
return i.Parties
}
Let us look more closely at the anatomy of this state:
- It has
three fields
:- The
Amount
the borrowers owes the lender; - A
unique identifier
to locate the state, and - The list of
owners
of the state.
- The
- It has
two methods
:SetLinearID
that allows the platform to assign automatically a unique identifier to the state. Indeed,IOU
implements theLinearState
interfaceOwners
that returns the identities of the owners of the state. Indeed,IOU
implements theOwnable
interface
Business Processes or Interactions
Create the IOU State
The very first operation is to create the IOU state. Let us assume that the borrower is the initiator of the interactive protocol to create this state. This is the view the borrower executes:
// Create contains the input to create an IOU state
type Create struct {
// Amount the borrower owes the lender
Amount uint
// Lender is the identity of the lender's FSC node
Lender view.Identity
// Approver is the identity of the approver's FSC node
Approver view.Identity
}
type CreateIOUView struct {
Create
}
func (i *CreateIOUView) Call(context view.Context) (interface{}, error) {
// use default identities if not specified
if i.Lender.IsNone() {
i.Lender = view2.GetIdentityProvider(context).Identity("lender")
}
if i.Approver.IsNone() {
i.Approver = view2.GetIdentityProvider(context).Identity("approver")
}
// As a first step operation, the borrower contacts the lender's FSC node
// to exchange the identities to use to assign ownership of the freshly created IOU state.
borrower, lender, err := state.ExchangeRecipientIdentities(context, i.Lender)
assert.NoError(err, "failed exchanging recipient identity")
// The borrower creates a new transaction
tx, err := state.NewTransaction(context)
assert.NoError(err, "failed creating a new transaction")
// Sets the namespace where the state should be stored
tx.SetNamespace("iou")
// Specifies the command this transaction wants to execute.
// In particular, the borrower wants to create a new IOU state owned by the borrower and the lender
// The approver will use this information to decide how validate the transaction
assert.NoError(tx.AddCommand("create", borrower, lender))
// The borrower prepares the IOU state
iou := &states.IOU{
Amount: i.Amount,
Parties: []view.Identity{borrower, lender},
}
// and add it to the transaction. At this stage, the ID gets set automatically.
assert.NoError(tx.AddOutput(iou))
// The borrower is ready to collect all the required signatures.
// Namely from the borrower itself, the lender, and the approver. In this order.
// All signatures are required.
_, err = context.RunView(state.NewCollectEndorsementsView(tx, borrower, lender, i.Approver))
assert.NoError(err)
// At this point the borrower can send the transaction to the ordering service and wait for finality.
_, err = context.RunView(state.NewOrderingAndFinalityView(tx))
assert.NoError(err)
// Return the state ID
return iou.LinearID, nil
}
The lender responds to request of endorsement from the borrower running the following view:
type CreateIOUResponderView struct{}
func (i *CreateIOUResponderView) Call(context view.Context) (interface{}, error) {
// As a first step, the lender responds to the request to exchange recipient identities.
lender, borrower, err := state.RespondExchangeRecipientIdentities(context)
assert.NoError(err, "failed exchanging recipient identities")
// When the leneder runs the CollectEndorsementsView, at some point, the borrower sends the assembled transaction
// to the lender. Therefore, the lender waits to receive the transaction.
tx, err := state.ReceiveTransaction(context)
assert.NoError(err, "failed receiving transaction")
// The lender can now inspect the transaction to ensure it is as expected.
// Here are examples of possible checks
// Namespaces are properly populated
assert.Equal(1, len(tx.Namespaces()), "expected only one namespace")
assert.Equal("iou", tx.Namespaces()[0], "expected the [iou] namespace, got [%s]", tx.Namespaces()[0])
// Commands are properly populated
assert.Equal(1, tx.Commands().Count(), "expected only a single command, got [%s]", tx.Commands().Count())
switch command := tx.Commands().At(0); command.Name {
case "create":
// If the create command is attached to the transaction then...
assert.Equal(0, tx.NumInputs(), "invalid number of inputs, expected 0, was [%d]", tx.NumInputs())
assert.Equal(1, tx.NumOutputs(), "invalid number of outputs, expected 1, was [%d]", tx.NumInputs())
iouState := &states.IOU{}
assert.NoError(tx.GetOutputAt(0, iouState))
assert.False(iouState.Amount < 5, "invalid amount, expected at least 5, was [%d]", iouState.Amount)
assert.Equal(2, iouState.Owners().Count(), "invalid state, expected 2 identities, was [%d]", iouState.Owners().Count())
assert.True(iouState.Owners().Contain(lender), "invalid state, it does not contain lender identity")
assert.True(command.Ids.Match([]view.Identity{lender, borrower}), "the command does not contain the lender and borrower identities")
assert.True(iouState.Owners().Match([]view.Identity{lender, borrower}), "the state does not contain the lender and borrower identities")
assert.NoError(tx.HasBeenEndorsedBy(borrower), "the borrower has not endorsed")
default:
return nil, errors.Errorf("invalid command, expected [create], was [%s]", command)
}
// The lender is ready to send back the transaction signed
_, err = context.RunView(state.NewEndorseView(tx))
assert.NoError(err)
// Finally, the lender waits the the transaction completes its lifecycle
return context.RunView(state.NewFinalityView(tx))
}
On the other hand, the approver runs the following view to respond to the request of signature from the borrower:
type ApproverView struct{}
func (i *ApproverView) Call(context view.Context) (interface{}, error) {
// When the borrower runs the CollectEndorsementsView, at some point, the borrower sends the assembled transaction
// to the approver. Therefore, the approver waits to receive the transaction.
tx, err := state.ReceiveTransaction(context)
assert.NoError(err, "failed receiving transaction")
// The approver can now inspect the transaction to ensure it is as expected.
// Here are examples of possible checks
// Namespaces are properly populated
assert.Equal(1, len(tx.Namespaces()), "expected only one namespace")
assert.Equal("iou", tx.Namespaces()[0], "expected the [iou] namespace, got [%s]", tx.Namespaces()[0])
// Commands are properly populated
assert.Equal(1, tx.Commands().Count(), "expected only a single command, got [%s]", tx.Commands().Count())
switch command := tx.Commands().At(0); command.Name {
case "create":
// If the create command is attached to the transaction then...
// No inputs expected. The single output at index 0 should be an IOU state
assert.Equal(0, tx.NumInputs(), "invalid number of inputs, expected 0, was [%d]", tx.NumInputs())
assert.Equal(1, tx.NumOutputs(), "invalid number of outputs, expected 1, was [%d]", tx.NumInputs())
iouState := &states.IOU{}
assert.NoError(tx.GetOutputAt(0, iouState))
assert.True(iouState.Amount >= 5, "invalid amount, expected at least 5, was [%d]", iouState.Amount)
assert.Equal(2, iouState.Owners().Count(), "invalid state, expected 2 identities, was [%d]", iouState.Owners().Count())
assert.False(iouState.Owners()[0].Equal(iouState.Owners()[1]), "owner identities must be different")
assert.True(iouState.Owners().Match(command.Ids), "invalid state, it does not contain command's identities")
assert.NoError(tx.HasBeenEndorsedBy(iouState.Owners()...), "signatures are missing")
case "update":
// If the update command is attached to the transaction then...
// The single input and output should be an IOU state
assert.Equal(1, tx.NumInputs(), "invalid number of inputs, expected 1, was [%d]", tx.NumInputs())
assert.Equal(1, tx.NumOutputs(), "invalid number of outputs, expected 1, was [%d]", tx.NumInputs())
inState := &states.IOU{}
assert.NoError(tx.GetInputAt(0, inState))
outState := &states.IOU{}
assert.NoError(tx.GetOutputAt(0, outState))
assert.Equal(inState.LinearID, outState.LinearID, "invalid state id, [%s] != [%s]", inState.LinearID, outState.LinearID)
assert.True(outState.Amount < inState.Amount, "invalid amount, [%d] expected to be less or equal [%d]", outState.Amount, inState.Amount)
assert.True(inState.Owners().Match(outState.Owners()), "invalid owners, input and output should have the same owners")
assert.NoError(tx.HasBeenEndorsedBy(inState.Owners()...), "signatures are missing")
default:
return nil, errors.Errorf("invalid command, expected [create] or [update], was [%s]", command)
}
// The approver is ready to send back the transaction signed
_, err = context.RunView(state.NewEndorseView(tx))
assert.NoError(err)
// Finally, the approver waits that the transaction completes its lifecycle
return context.RunView(state.NewFinalityView(tx))
}
Update the IOU State
Once the IOU state has been created, the parties can agree on the changes to the state to reflect the evolution of the amount the borrower owes the lender.
Indeed, this is the view the borrower executes to update the IOU state's amount field.
// Update contains the input to update an IOU state
type Update struct {
// LinearID is the unique identifier of the IOU state
LinearID string
// Amount is the new amount. It should smaller than the current amount
Amount uint
// Approver is the identity of the approver's FSC node
Approver view.Identity
}
type UpdateIOUView struct {
Update
}
func (u UpdateIOUView) Call(context view.Context) (interface{}, error) {
// use default identities if not specified
if u.Approver.IsNone() {
u.Approver = view2.GetIdentityProvider(context).Identity("approver")
}
// The borrower starts by creating a new transaction to update the IOU state
tx, err := state.NewTransaction(context)
assert.NoError(err)
// Sets the namespace where the state is stored
tx.SetNamespace("iou")
// To update the state, the borrower, first add a dependency to the IOU state of interest.
iouState := &states.IOU{}
assert.NoError(tx.AddInputByLinearID(u.LinearID, iouState))
// The borrower sets the command to the operation to be performed
assert.NoError(tx.AddCommand("update", iouState.Owners()...))
// Then, the borrower updates the amount,
iouState.Amount = u.Amount
// and add the modified IOU state as output of the transaction.
err = tx.AddOutput(iouState)
assert.NoError(err)
// The borrower is ready to collect all the required signatures.
// Namely from the borrower itself, the lender, and the approver. In this order.
// All signatures are required.
_, err = context.RunView(state.NewCollectEndorsementsView(tx, iouState.Owners()[0], iouState.Owners()[1], u.Approver))
assert.NoError(err)
// At this point the borrower can send the transaction to the ordering service and wait for finality.
_, err = context.RunView(state.NewOrderingAndFinalityView(tx))
assert.NoError(err)
// Return the state ID
return iouState.LinearID, nil
}
On the other hand, the lender responds to request of endorsement from the borrower running the following view:
type UpdateIOUResponderView struct{}
func (i *UpdateIOUResponderView) Call(context view.Context) (interface{}, error) {
// When the borrower runs the CollectEndorsementsView, at some point, the borrower sends the assembled transaction
// to the lender. Therefore, the lender waits to receive the transaction.
tx, err := state.ReceiveTransaction(context)
assert.NoError(err, "failed receiving transaction")
// The lender can now inspect the transaction to ensure it is as expected.
// Here are examples of possible checks
// Namespaces are properly populated
assert.Equal(1, len(tx.Namespaces()), "expected only one namespace")
assert.Equal("iou", tx.Namespaces()[0], "expected the [iou] namespace, got [%s]", tx.Namespaces()[0])
switch command := tx.Commands().At(0); command.Name {
case "update":
// If the update command is attached to the transaction then...
// One input and one output containing IOU states are expected
assert.Equal(1, tx.NumInputs(), "invalid number of inputs, expected 1, was %d", tx.NumInputs())
assert.Equal(1, tx.NumOutputs(), "invalid number of outputs, expected 1, was %d", tx.NumInputs())
inState := &states.IOU{}
assert.NoError(tx.GetInputAt(0, inState))
outState := &states.IOU{}
assert.NoError(tx.GetOutputAt(0, outState))
// Additional checks
// Same IDs
assert.Equal(inState.LinearID, outState.LinearID, "invalid state id, [%s] != [%s]", inState.LinearID, outState.LinearID)
// Valid Amount
assert.False(outState.Amount >= inState.Amount, "invalid amount, [%d] expected to be less or equal [%d]", outState.Amount, inState.Amount)
// Same owners
assert.True(inState.Owners().Match(outState.Owners()), "invalid owners, input and output should have the same owners")
assert.Equal(2, inState.Owners().Count(), "invalid state, expected 2 identities, was [%d]", inState.Owners().Count())
// Is the lender one of the owners?
lenderFound := fabric.GetLocalMembership(context).IsMe(inState.Owners()[0]) != fabric.GetLocalMembership(context).IsMe(inState.Owners()[1])
assert.True(lenderFound, "lender identity not found")
// Did the borrower sign?
assert.NoError(tx.HasBeenEndorsedBy(inState.Owners().Filter(
func(identity view.Identity) bool {
return !fabric.GetLocalMembership(context).IsMe(identity)
})...), "the borrower has not endorsed")
default:
return nil, errors.Errorf("invalid command, expected [create], was [%s]", command.Name)
}
// The lender is ready to send back the transaction signed
_, err = context.RunView(state.NewEndorseView(tx))
assert.NoError(err)
// Finally, the lender waits that the transaction completes its lifecycle
return context.RunView(state.NewFinalityView(tx))
}
We have seen already what the approver does.
Testing
To run the IOU
sample, one needs first to deploy the Fabric Smart Client nodes
and the Fabric network
.
Once these networks are deployed, one can invoke views on the smart client nodes to test the IOU
sample.
So, first step is to describe the topology of the networks we need.
Describe the topology of the networks
To test the above views, we first describe the topology of the networks we need. Namely, Fabric and FSC networks.
For Fabric, we can define a topology with the following characteristics:
- Three organization: Org1, Org2, and Org3;
- Single channel;
- A namespace
iou
whose changes can be endorsed by endorsers from Org1.
For the FSC network, we have a topology with:
- 3 FCS nodes. One for the approver, one for the borrower, and one for the lender.
- The approver's FSC node has an additional Fabric identity belonging to Org1. Therefore, the approver is an endorser of the Fabric namespace we defined above.
We can describe the network topology programmatically as follows:
func Topology() []api.Topology {
// Define a Fabric topology with:
// 1. Three organization: Org1, Org2, and Org3
// 2. A namespace whose changes can be endorsed by Org1.
fabricTopology := fabric.NewDefaultTopology()
fabricTopology.AddOrganizationsByName("Org1", "Org2", "Org3")
fabricTopology.SetNamespaceApproverOrgs("Org1")
fabricTopology.AddNamespaceWithUnanimity("iou", "Org1")
fabricTopology.EnableGRPCLogging()
fabricTopology.EnableLogPeersToFile()
fabricTopology.SetLogging("info", "")
// Define an FSC topology with 3 FCS nodes.
// One for the approver, one for the borrower, and one for the lender.
fscTopology := fsc.NewTopology()
fscTopology.SetLogging("debug", "")
fscTopology.EnableLogToFile()
// Add the approver FSC node.
approver := fscTopology.AddNodeByName("approver")
// This option equips the approver's FSC node with an identity belonging to Org1.
// Therefore, the approver is an endorser of the Fabric namespace we defined above.
approver.AddOptions(
fabric.WithOrganization("Org1"),
fabric.WithX509Identity("alice"),
)
approver.RegisterResponder(&views.ApproverView{}, &views.CreateIOUView{})
approver.RegisterResponder(&views.ApproverView{}, &views.UpdateIOUView{})
// Add the borrower's FSC node
borrower := fscTopology.AddNodeByName("borrower")
borrower.AddOptions(fabric.WithOrganization("Org2"))
borrower.RegisterViewFactory("create", &views.CreateIOUViewFactory{})
borrower.RegisterViewFactory("update", &views.UpdateIOUViewFactory{})
borrower.RegisterViewFactory("query", &views.QueryViewFactory{})
// Add the lender's FSC node
lender := fscTopology.AddNodeByName("lender")
lender.AddOptions(
fabric.WithOrganization("Org3"),
fabric.WithX509Identity("bob"),
)
lender.RegisterResponder(&views.CreateIOUResponderView{}, &views.CreateIOUView{})
lender.RegisterResponder(&views.UpdateIOUResponderView{}, &views.UpdateIOUView{})
lender.RegisterViewFactory("query", &views.QueryViewFactory{})
return []api.Topology{fabricTopology, fscTopology}
}
Boostrap the networks
Bootstrap of the networks requires both Fabric Docker images and Fabric binaries. To ensure you have the required images you can use the following Makefile target in the project root directory:
make fabric-docker-images
To ensure you have the required fabric binary files and set the FAB_BINS
environment variable to the correct place you can do the following in the project root directory
make download-fabric
export FAB_BINS=$PWD/../fabric/bin
To help us bootstrap the networks and then invoke the business views, the iou
command line tool is provided.
To build it, we need to run the following command from the folder $FSC_PATH/samples/fabric/iou
.
($FSC_PATH
refers to the Fabric Smart Client repository in your filesystem see getting started)
go build -o iou
If the compilation is successful, we can run the iou
command line tool as follows:
./iou network start --path ./testdata
The above command will start the Fabric network and the FSC network,
and store all configuration files under the ./testdata
directory.
The CLI will also create the folder ./cmd
that contains a go main file for each FSC node.
The CLI compiles these go main files and then runs them.
If everything is successful, you will see something like the following (note you may have to scroll up to find this output)
2022-02-08 11:53:03.560 UTC [fsc.integration] Start -> INFO 027 _____ _ _ ____
2022-02-08 11:53:03.561 UTC [fsc.integration] Start -> INFO 028 | ____| | \ | | | _ \
2022-02-08 11:53:03.561 UTC [fsc.integration] Start -> INFO 029 | _| | \| | | | | |
2022-02-08 11:53:03.561 UTC [fsc.integration] Start -> INFO 02a | |___ | |\ | | |_| |
2022-02-08 11:53:03.561 UTC [fsc.integration] Start -> INFO 02b |_____| |_| \_| |____/
2022-02-08 11:53:03.561 UTC [fsc.integration] Serve -> INFO 02c All GOOD, networks up and running
To shut down the networks, just press CTRL-C.
If you want to restart the networks after the shutdown, you can just re-run the above command.
If you don't delete the ./testdata
directory, the network will be started from the previous state.
Before restarting the networks, one can modify the business views to add new functionalities, to fix bugs, and so on. Upon restarting the networks, the new business views will be available. Later on, we will see an example of this.
To clean up all artifacts, we can run the following command:
./iou network clean --path ./testdata
The ./testdata
and ./cmd
folders will be deleted.
Invoke the business views
If you reached this point, you can now invoke the business views on the FSC nodes.
To create an IOU, you can run the following command in a new terminal window:
./iou view -c ./testdata/fsc/nodes/borrower/client-config.yaml -f create -i '{"Amount":10}'
The above command invoke the create
view on the borrower's FSC node. The -c
option specifies the client configuration file.
The -f
option specifies the view name. The -i
option specifies the input data.
In the specific case, we are creating an IOU with amount 10. The lender and the approver are the default ones.
If everything is successful, you will see something like the following:
"bd90b6c8-0a54-4719-8caa-00759bad7d69"
The above is the IOU ID that we will use to update the IOU or query it.
Indeed, once the IOU is created, you can query the IOUs by running the following command (substituting the IOU LinearID output from the previous command):
./iou view -c ./testdata/fsc/nodes/borrower/client-config.yaml -f query -i '{"LinearID":"bd90b6c8-0a54-4719-8caa-00759bad7d69"}'
The above command will query the IOU with the linear ID bd90b6c8-0a54-4719-8caa-00759bad7d69
on the borrower's FSC node.
If everything is successful, you will the current amount contained in the IOU state: 10.
If you want to query the IOU start on the lender node, you can run the following command:
./iou view -c ./testdata/fsc/nodes/lender/client-config.yaml -f query -i '{"LinearID":"bd90b6c8-0a54-4719-8caa-00759bad7d69"}'
To update the IOU, you can run the following command:
./iou view -c ./testdata/fsc/nodes/borrower/client-config.yaml -f update -i '{"LinearID":"bd90b6c8-0a54-4719-8caa-00759bad7d69","Amount":8}'
The above command will update the IOU with the linear ID bd90b6c8-0a54-4719-8caa-00759bad7d69
. The new amount will be 8.
Modify the business views and restart the networks
Suppose you want to change the behaviour of a business view and see it in actions, one can do the following:
- Stop the networks;
- Update the business views;
- Restart the networks;
Let's see how to do this with a concrete example. When the borrower does not owe the lender anything anymore,
the borrower updates the IOU state to 0. The state still exists though. What we can do instead is to delete the state.
We can do that by, in the borrower's business view UpdateIOUView
, replacing the line
err = tx.AddOutput(iouState)
with
if iouState.Amount == 0 {
err = tx.Delete(iouState)
} else {
err = tx.AddOutput(iouState)
}
Now, we need to update the business view of the lender and the approver to take in account the new behaviour.
For the lender, we modify UpdateIOUResponderView
to skip checks of the output state if it is deleted using the following code:
output := tx.Outputs().At(0)
if !output.IsDelete() {
outState := &states.IOU{}
assert.NoError(tx.GetOutputAt(0, outState))
// Additional checks
// Same IDs
assert.Equal(inState.LinearID, outState.LinearID, "invalid state id, [%s] != [%s]", inState.LinearID, outState.LinearID)
// Valid Amount
assert.False(outState.Amount >= inState.Amount, "invalid amount, [%d] expected to be less or equal [%d]", outState.Amount, inState.Amount)
// Same owners
assert.True(inState.Owners().Match(outState.Owners()), "invalid owners, input and output should have the same owners")
assert.Equal(2, inState.Owners().Count(), "invalid state, expected 2 identities, was [%d]", inState.Owners().Count())
}
For the approver, we update the validation code for the update
command in ApproverView
:
output := tx.Outputs().At(0)
if !output.IsDelete() {
outState := &states.IOU{}
assert.NoError(tx.GetOutputAt(0, outState))
assert.Equal(inState.LinearID, outState.LinearID, "invalid state id, [%s] != [%s]", inState.LinearID, outState.LinearID)
assert.True(outState.Amount < inState.Amount, "invalid amount, [%d] expected to be less or equal [%d]", outState.Amount, inState.Amount)
assert.True(inState.Owners().Match(outState.Owners()), "invalid owners, input and output should have the same owners")
}
Now, we can just restart the networks, the Fabric Smart Client nodes will be rebuilt and the new behaviour available. You can test by yourself.
Hyperledger Explorer
Hyperledger Explorer is a user-friendly Web application tool used to view, invoke, deploy or query blocks, transactions and associated data, network information (name, status, list of nodes), chaincodes and transaction families, as well as any other relevant information stored in the ledger.
You can enable it by using the Monitoring Platform
as follows:
// Create a new topology for the monitoring infrastructure
monitoringTopology := monitoring.NewTopology()
// Enable Hyperledger Explorer
monitoringTopology.EnableHyperledgerExplorer()
return []api.Topology{fabricTopology, fscTopology, monitoringTopology}
To use it, make sure you have all the required docker images.
To pull them, just run make monitoring-docker-images
from the FSC root folder.
Last but not least, you need to stop your networks, if they are running, cleanup and start again from scratch. (In the future, this cleanup step will not be needed).
If everything is working fine, you can access the explorer at the following URL: 127.0.0.1:8080 (User: admin
, Pwd: admin
).
Here is an example of what you can see:
Monitoring with Prometheus and Grafana
Prometheus is a free software application used for event monitoring and alerting. It records real-time metrics in a time series database built using a HTTP pull model, with flexible queries and real-time alerting, Wikipedia.
Grafana is a multi-platform open source analytics and interactive visualization web application. It provides charts, graphs, and alerts for the web when connected to supported data sources, Wikipedia.
You can enable them and start monitoring your networks by configuring the monitoring platorm as follows:
// Create a new topology for the monitoring infrastructure
monitoringTopology := monitoring.NewTopology()
// Enable Prometheus and Grafana
monitoringTopology.EnablePrometheusGrafana()
return []api.Topology{fabricTopology, fscTopology, monitoringTopology}
To use it, make sure you have all the required docker images.
To pull them, just run make monitoring-docker-images
from the FSC root folder.
Last but not least, you need to stop your networks, if they are running, cleanup and start again from scratch. (In the future, this cleanup step will not be needed).
If everything is working fine, you can access
- Prometheus at the following URL: 127.0.0.1:9090, and
- Grafana at the following URL: 127.0.0.1:9090 (User:
admin
, Pwd:admin
).
Here is an example of what you can see:
Documentation ¶
There is no documentation for this package.