Pin off-chain data to a custom blockchain transaction¶
This guide describes how to associate an arbitrary off-chain payload with a blockchain transaction on a contract of your own design. A hash of the payload will be recorded as part of the blockchain transaction, and on the receiving side, FireFly will ensure that both the on-chain and off-chain pieces are received and aggregated together.
NOTE: This is an advanced FireFly feature. Before following any of the steps in this guide, you should be very familiar and comfortable with the basic features of how broadcast messages and private messages work, be proficient at custom contract development on your blockchain of choice, and understand the fundamentals of how FireFly interacts with custom contracts.
Designing a compatible contract¶
In order to allow pinning a FireFly message batch with a custom contract transaction, your contract must meet certain criteria.
First, any external method of the contract that will be used for associating with off-chain payloads must provide an extra parameter for passing the encoded batch data. This must be the last parameter in the method signature. This convention is chosen partly to align with the Ethereum ERC5750 standard, but should serve as a straightforward guideline for nearly any blockchain.
Second, this method must emit a BatchPin
event that can be received and parsed by FireFly. Exactly how
the data is unpacked and used to emit this event will differ for each blockchain.
Ethereum¶
import "@hyperledger/firefly-contracts/contracts/IBatchPin.sol";
contract CustomPin {
IBatchPin firefly;
function setFireFlyAddress(address addr) external {
firefly = IBatchPin(addr);
}
function sayHello(bytes calldata data) external {
require(
address(firefly) != address(0),
"CustomPin: FireFly address has not been set"
);
/* do custom things */
firefly.pinBatchData(data);
}
}
- The method in question will receive packed "batch pin" data in its last method parameter (in the
form of ABI-encoded
bytes
). The method must invoke thepinBatchData
method of the FireFly Multiparty Contract and pass along this data payload. It is generally good practice to trigger this as a final step before returning, after the method has performed its own logic. - This also implies that the contract must know the on-chain location of the
FireFly Multiparty Contract. How this is achieved is up to your individual implementation -
the example above shows exposing a method to set the address. An application may leverage the fact that
this location is available by querying the FireFly
/status
API (undermultiparty.contract.location
as of FireFly v1.1.0). However, the application must also consider how appropriately secure this functionality, and how to update this location if a multiparty "network action" is used to migrate the network onto a new FireFly multiparty contract.
Fabric¶
package chaincode
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
"github.com/hyperledger/firefly/custompin_sample/batchpin"
)
type SmartContract struct {
contractapi.Contract
}
func (s *SmartContract) MyCustomPin(ctx contractapi.TransactionContextInterface, data string) error {
event, err := batchpin.BuildEventFromString(ctx, data)
if err != nil {
return err
}
bytes, err := json.Marshal(event)
if err != nil {
return fmt.Errorf("failed to marshal event: %s", err)
}
return ctx.GetStub().SetEvent("BatchPin", bytes)
}
- The method in question will received packed "batch pin" data in its last method parameter (in the
form of a JSON-encoded
string
). The method must unpack this argument into a JSON object. - The contract must directly set a
BatchPin
event in the same format that is used by the FireFly Multiparty Contract.
Initializing FireFly¶
Once you have a contract designed, you can initialize your environment using the blockchain of your choice.
No special initialization arguments are needed for Ethereum.
If you are using Fabric, you must pass the --custom-pin-support
argument when initializing your
FireFly stack. This will ensure that the BatchPin
event listener listens to events from all chaincode
deployed on the default channel, instead of only listening to events from the pre-deployed FireFly chaincode.
Invoking the contract¶
You can follow the normal steps for Ethereum or Fabric to define your contract interface and API in FireFly. When invoking the contract, you can include a message payload alongside the other parameters.
POST
http://localhost:5000/api/v1/namespaces/default/apis/custom-pin/invoke/sayHello
Listening for events¶
All parties that receive the message will receive a message_confirmed
on their event listeners.
This event confirms that the off-chain payload has been received (via data exchange or shared storage) and
that the blockchain transaction has been received and sequenced. It is guaranteed that these message_confirmed
events will be ordered based on the sequence of the on-chain transactions, regardless of when the off-chain
payload becomes available. This means that all parties will order messages on a given topic in exactly the
same order, allowing for deterministic but decentralized event-driven architecture.