Skip to content

mlabs-haskell/cardano-open-oracle-protocol

Repository files navigation

Table of Contents

Cardano open oracle protocol

COOP logo

Introduction

The Cardano open oracle protocol (COOP) is a protocol complemented by an open-source SDK for publishing and consuming on-chain data using Cardano CIP-31 reference inputs. Reference inputs allow a data provider to publish a data point once and multiple consumers to use the data point in on-chain dApp scripts, without interfering with each other.

The purpose of this project is to allow developers in the Cardano ecosystem to host and run their own COOP Publisher and integrate it into their broader Oracle offerings.

Development of the COOP is led by MLabs with feedback and direction provided by the Orcfax oracle project which will implement the COOP on its platform.

This project was graciously funded by the Cardano Treasury in Catalyst Fund 8.

Documentation

The protocol is described in further detail in the following documents

  • Design document contains information about the overall goals of this project,
  • Plutus protocol contains information about the wallets, tokens, minting policies, validators and transactions used in COOP and their relationship,
  • Frontend protocol contains information about how users must interact with the COOP Publisher in order to publish new Fact Statements and garbage collect obsolete Fact Statements,
  • Backend protocol contains information on the back-end operations needed to serve the Frontend protocol,
  • Mapping between JSON and Plutus Data contains information about how JSON encodings map into PlutusData encoding that can be used 'onchain'.

Getting Started

Installing Nix

The COOP repository relies heavily on the Nix Package Manager for both development and package distribution.

To install run the following command:

sh <(curl -L https://nixos.org/nix/install) --daemon

and follow the instructions.

$ nix --version
nix (Nix) 2.8.0

NOTE: The repository should work with Nix version greater or equal to 2.8.0.

Make sure to enable Nix Flakes and IFD by editing either ~/.config/nix/nix.conf or /etc/nix/nix.conf on your machine and add the following configuration entries:

experimental-features = nix-command flakes
allow-import-from-derivation = true

Optionally, to improve build speed, it is possible to set up binary caches maintained by IOHK and Plutonomicon by setting additional configuration entries:

substituters = https://cache.nixos.org https://iohk.cachix.org https://cache.iog.io https://public-plutonomicon.cachix.org
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= iohk.cachix.org-1:DpRUyj7h7V830dp/i6Nti+NEO2/nhblbov/8MW7Rqoo= public-plutonomicon.cachix.org-1:3AKJMhCLn32gri1drGuaZmFrmnue+KkKrhhubQk/CWc=

Building and developing

Once Nix is installed, you should be able to seamlessly use the repository to develop, build and run packages.

Download the Git repository:

git clone https://github.com/mlabs-haskell/cardano-open-oracle-protocol.git

To facilitate seamlessly moving between directories and associated Nix development shells we use direnv and nix-direnv:

To install both using nixpkgs:

nix profile install nixpkgs#direnv
nix profile install nixpkgs#nix-direnv

Your shell and editors should pick up on the .envrc files in different directories and prepare the environment accordingly. Use direnv allow to enable the direnv environment and direnv reload to reload it when necessary. Otherwise, each .envrc file in COOP sub-directories contains a proper Nix target you can use with the nix develop command. For example, nix develop #dev-pab will build a Nix development shell that has everything needed for developing and compiling the coop-pab component.

Additionally, throughout the repository one can use the pre-commit tool:

$ pre-commit run --all
cabal-fmt................................................................Passed
fourmolu.................................................................Passed
hlint....................................................................Passed
markdownlint.............................................................Passed
nix-linter...............................................................Passed
nixpkgs-fmt..............................................................Passed
shellcheck...............................................................Passed

to run all the code quality tooling specified in the pre-commit-check config file. These pre-commit checks need to pass for a git commit to be successful.

Tutorial

This tutorial demonstrates how to create and operate your own COOP Publisher, and how users can eventually use your service to publish new Fact Statements provided.

While working through the Tutorial feel free to explore and inspect various Bash functions and command line tools used. For example, using the type standard Bash function one can discover the definition of some other Bash functions:

[coop-env ~ coop-tutorial] $ type coop-get-state
coop-get-state is a function
coop-get-state ()
{
    coop-pab-cli get-state --any-wallet $GOD_PKH;
    cat $COOP_PAB_DIR/coop-state.json | json_pp
}

Additionally, each cli tool provided by COOP support a --help flag that provides the detailed explanation of the purpose of commands and their options:

[coop-env ~ coop-tutorial] $ coop-plutus-cli --help
[coop-env ~ coop-tutorial] $ coop-pab-cli --help
[coop-env ~ coop-tutorial] $ coop-publisher-cli --help
[coop-env ~ coop-tutorial] $ json-fs-store-cli --help

Since we're going to be running some services, it's useful to know which ports are used by which processes, for example:

[coop-env ~ coop-tutorial] $ netstat -ntuap | grep LISTEN | grep -E "local-cluster|cardano-node|json-*|coop-*"

As we run the different commands in the tutorial Nix will continue to show the working folder as:

[coop-env ~ coop-tutorial] $

We can orient ourselves by looking for that prompt. If you find yourself in another directory simply navigate back to coop-tutorial from the root of this repository.

1. Preparing the environment

A Nix environment is provided with all the tools necessary to run, operate and use COOP.

Prepare the directories and open a provided Nix environment:

mkdir coop-tutorial
cd coop-tutorial
nix develop github:mlabs-haskell/cardano-open-oracle-protocol#coop-env

NOTE: If you have downloaded this repository, and have created a tutorial directory outside of it, you can use a relative path to initialize the nix environment.

Take for example, a tutorial folder sitting at the same level:

mlabs/
├── coop-open-oracle-protocol
└── coop-tutorial

From within coop-tutorial/ you can initialize nix as follows: nix develop ../cardano-open-oracle-protocol#coop-env.

You should be given the following prompt:

[coop-env ~ coop-tutorial] $

The environment should now have the following tools available:

and some other convenience utilities including some Bash functions that wrap the invocation of the bove-mentioned services and command line tools.

2. Environment variables and directories

Throughout the tutorial, various environment variables will need to be set, and various directories will be created. The creation of environment variables and directories are shown in their respective contexts in the steps below. They are listed for convenience below.

CLUSTER_DIR=".local-cluster"
WALLETS=".wallets"
COOP_PAB_DIR=".coop-pab-cli"
JS_STORE_DIR=".json-fs-store"
COOP_PUBLISHER_DIR=".coop-publisher-cli"
mkdir $WALLETS
mkdir $COOP_PAB_DIR
mkdir $JS_STORE_DIR
mkdir $COOP_PUBLISHER_DIR
mkdir $CLUSTER_DIR $CLUSTER_DIR/scripts $CLUSTER_DIR/txs

3. Running a local Cardano network

Let's first start by preparing and running a local Cardano network using the local-cluster utility tool from Plutip library.

You can use the provided run-cluster Bash function to run these commands for you (inspect the content with type run-cluster). You should see the cluster running with instructions on how to stop it if necessary:

run-cluster
...
Cluster is running. Ctrl-C to stop.

This creates the directories needed for the local-cluster to work and starts a Cardano network with 10 wallets (made available in the $WALLETS directory) that will be used in the Protocol.

Let's leave the local-cluster process running in the foreground of the current shell and open a new [coop-env ~ coop-tutorial] shell session to continue with the tutorial.

The local-cluster created some wallets, let's assign them to environment variables that will be referenced throughout this tutorial:

make-exports
show-env | grep PKH

This will output confirmation of the variables and the stored public key hashes (PKH) of the wallets.

declare -x AA_PKH="319a165e8cb4c2c3eb898334ac3579eed75bcbb9a274f9ff259e74e3"
declare -x AUTH_PKH="46103a3b0671460efa35aee0f97c27d0a6b97bf59271663db7cd3d04"
declare -x CERT_RDMR_PKH="07c0bed25705dbaeb17ff53553035ddace3fa7a12ca75315ece8583b"
declare -x FEE_PKH="e6250649bed46e3b9343664f543e8ec3ba3eb01128be6b82a0491799"
declare -x GOD_PKH="c12aacc2604e89cd5dac1fb1e324ad552df1b18e2bd4230e8e15cfd5"
declare -x SUBMITTER_PKH="b7e59f40866e6ec88635343b9cc285043d344afbbe001ae645db0553"

Output shows some named wallets with their base16 public keys hash identifier. The SUBMITTER_PKH is the only wallet not used by the COOP Publisher that belongs to the user. We want to hide this wallet from the local-cluster to emulate a real-world (distributed) scenario where a third-party user will eventually sign the resulting COOP transaction:

mv $WALLETS/signing-key-"$SUBMITTER_PKH".skey $WALLETS/my-signing-key-"$SUBMITTER_PKH".skey

NOTE: If the $SUBMITTER_PKH signing key is not renamed, when the Publisher gRPC service is invoked (below) it will use the key to automatically sign a COOP transaction and submit it to the Cardano Network. This can be useful in test-scenarios or when using COOP in an appropriately secured environment, purely as a publishing mechanism, i.e. in a centralized or federated model the COOP node signs and submits its own transactions. In this scenario the FEE_PKH can also be set to that of the SUBMITTED_PKH so that fees are automatically returned.

All other essential wallets are owned by the COOP Publisher and are used throughout its lifecycle. We'll revisit their role as we progress through the tutorial.

The make-exports and show-env are provided Bash functions that wrap the parsing of local-cluster information and set the appropriate environment variables.

4. Initializing the Protocol

We're ready now to perform the COOP Plutus protocol genesis using the coop-pab-cli command line tool. We prepare the working directory and run the CLI. You can use Bash function coop-genesis to do the same:

export COOP_PAB_DIR=.coop-pab-cli && mkdir $COOP_PAB_DIR
coop-pab-cli deploy --god-wallet $GOD_PKH --aa-wallet $AA_PKH

We should see confirmation the command executed successfully.

[CONTRACT] [INFO [Any]] deployCoop: Finished

At this point, a $COOP_PAB_DIR/coop-deployment.json file was created that contains all the Plutus scripts associated with the COOP Publisher.

NOTE: The coop-deployment.json file is intended to be shared with the users of the Protocol to enable them to assert proper script addresses and token authenticity.

The God wallet can be discarded after the Protocol Genesis and the Authentication Authority aka AA wallet takes the role as the root wallet of the Protocol that has the ability to issue new Authentication tokens to Authenticator wallets. More on that later...

NOTE: The Authentication Authority wallets MUST be kept safe as their compromise impacts the integrity of the entire system. Trust in a particular COOP Publisher eventually reduces to this wallet.

Continuing, we should be able to already inspect the state of the Protocol by using a provided coop-get-state bash function:

coop-get-state

We'll see the command runs successfully with JSON output following that.

getState: Success
{
  "cs'certificates": [],
  "cs'currentTime": [
    {
      "getPOSIXTime": 1668599399000
    },
    {
      "getPOSIXTime": 1668599400000
    }
  ],
  "cs'factStatements": []
}

As we can see there's currently nothing of interest there. The cs'certificates list contains Certificates available in the Protocol, and the cs'factStatements contains a list of all the published Fact Statements. cs'currentTime is included for convenience to observe the on-chain time.

Now, it's time to issue Authentication tokens to Authenticator wallets (you can use Bash script coop-mint-cert-redeemers):

coop-pab-cli mint-cert-redeemers \
 --cert-rdmr-wallet $CERT_RDMR_PKH \
 --cert-rdmrs-to-mint 100

Which should show that the certificate redeemer tokens were successfully minted:

CONTRACT] [INFO [Any]] mintCertR: Finished
mintCertRdmrs: Minted $CERT-RDMR tokens with AssetClass

We will now mint the certificate $CERT and authentication $AUTH tokens (Bash function coop-mint-authentication):

NOW=$(get-onchain-time) && coop-pab-cli mint-auth \
 --aa-wallet $AA_PKH \
 --certificate-valid-from $NOW \
 --certificate-valid-to "$(expr $NOW + 60 \* 60 \* 1000)" \
 --auth-wallet $AUTH_PKH

Which should also be successfully minted.

mintAuth: Minted $CERT
mintAuth: Minted $AUTH

NOTE: Validity is measured in milliseconds (ms). 60 * 60 * 1000 is 3600000 ms which means our certificates in this example are valid for one hour.

The coop-pab-cli mint-cert-redeemers issues Certificate redeemer tokens to a special wallet that will be used in coop-pab-cli garbage-collect command to 'garbage collect' obsolete Certificates and is a prerequisite to coop-pab-cli mint-auth transaction. These tokens are never depleted.

The coop-pab-cli mint-auth is the most involved command in the protocol, it's intended to be used by the COOP Publisher operator on a regular basis to issue new 'ephemeral' Authentication tokens that are used to authenticate the publishing of each new Fact Statement. Once depleted, they have to be replenished with this command and it's up to the Operator to manage when and how many are issued, a decision based on considering the security exposure of the Authenticator wallets and the publishing request load.

The command takes in the Authentication Authority wallet that authorizes the issuance of new authentication tokens to an Authenticator wallet, setting the certificate validity to 1 HOUR from 'now', after which this authentication batch, meaning both Certificates and associated Authentication tokens become invalid and can be discarded.

NOTE: Authentication tokens that are associated with an expired Certificate cannot be used in the Protocol.

Since all the Authentication tokens are sent in a batch to a single UTxO held by the Authenticator wallets we provide a convenience utility to redistribute these tokens in separate UTxOs (Bash function coop-redist-auth):

coop-pab-cli redistribute-auth --auth-wallet $AUTH_PKH

This will output the following:

redistributeAuth: Redistributed outputs for Authenticator

Authentication tokens are spend by Fact Statement Publishing transactions to denote the 'authenticity' of the information provided in produced Fact Statement UTxOs. They are also associated with a Certificate that provides information on the time validity of Authentication tokens used in a Fact Statement Publishing transactions.

NOTE: Authenticator wallets are so called 'hot-wallets' used when servicing Fact Statement Publishing requests, as such the Protocol designed a mitigation using Certificates that limit the impact a compromised Authenticator wallet can have on the integrity of the Protocol.

Before we proceed, let's check in on the state of our Protocol now that we actually introduced our first action:

coop-get-state

State will now look as follows:

getState: Success
{
   "cs'certificates" : [
      {
         "cert'id" : "7279672bf427c10d43492f41ab3af02a8bcb97d9777539fc5eae0b108850c3ce",
         "cert'redeemerAc" : {
            "unAssetClass" : [
               {
                  "unCurrencySymbol" : "6b14c29615e356edfce1eeb652b703daa7c246bd52fa8d87c17aafaf"
               },
               "c639e2f8b64d6a0bdf1d48de48d832c57342e7980d6a4e98df92ef8c2c54ce75"
            ]
         },
         "cert'validity" : {
            "ivFrom" : [
               {
                  "contents" : {
                     "getPOSIXTime" : 1668599835000
                  },
                  "tag" : "Finite"
               },
               true
            ],
            "ivTo" : [
               {
                  "contents" : {
                     "getPOSIXTime" : 1668603435000
                  },
                  "tag" : "Finite"
               },
               true
            ]
         }
      }
   ],
   "cs'currentTime" : [
      {
         "getPOSIXTime" : 1668601830000
      },
      {
         "getPOSIXTime" : 1668601831000
      }
   ],
   "cs'factStatements" : []
}

As we can see a new Certificate has been successfully issued.

5. Running a TxBuilder gRPC service

We're finally ready to run the first COOP service, namely the TxBuilder gRPC back-end service that has the responsibility of building the COOP Cardano transactions:

The provided generate-keys Bash function will initialize the TLS keys and certificates used by the gRPC service. The service needs access to Authenticator wallets as it provides signatures for the transactions, and a Fee wallet to send the service fees to.

generate-keys $COOP_PAB_DIR

Now we are ready to run the service (use Bash function coop-run-tx-builder-grpc):

coop-pab-cli tx-builder-grpc --auth-wallet $AUTH_PKH --fee-wallet $FEE_PKH

NOTE: A Fee wallet is where the COOP Publisher receives the fees after a successful Fact Statement Publishing.

You can inspect and interact with the service using the gRPC utilities provided in the environment (grpcurl and grpcui).

Let's leave the tx-builder-grpc process running in the foreground of the current shell and open a new [coop-env ~ coop-tutorial] shell session to continue with the tutorial.

6. Running a FactStatementStore gRPC service

You can use Bash function run-js-fs-store to execute commands described in this section.

COOP provides a low-scale implementation of the FactStatementStore gRPC back-end service, namely the JSON Fact Statement Store that, as the name suggests, enables COOP Publisher operators to conveniently maintain a store of JSON encoded Fact Statements that users can refer to and eventually publish.

First, let's prepare and initialize the service:

export JS_STORE_DIR=.json-fs-store
mkdir $JS_STORE_DIR
sqlite3 -batch $JS_STORE_DIR/json-store.db ""
json-fs-store-cli genesis --db $JS_STORE_DIR/json-store.db
generate-keys $JS_STORE_DIR

Let's also add some actual Fact Statements into the store, while we're here:

json-fs-store-cli insert-fact-statement --db $JS_STORE_DIR/json-store.db \
 --fact_statement_id "id1" \
 --json '["apples", "oranges", "pears"]'
json-fs-store-cli insert-fact-statement --db $JS_STORE_DIR/json-store.db \
 --fact_statement_id "id2" \
 --json '{"name": "Drazen Popovic", "age": 35}'
json-fs-store-cli insert-fact-statement --db $JS_STORE_DIR/json-store.db \
 --fact_statement_id "id3" \
 --json '"Lorem ipsum"'

Take a look at the values written by inspecting the fact statement store:

echo "SELECT * FROM fact_statements" | sqlite3 $JS_STORE_DIR/json-store.db

You expectedly should see:

id1|["apples", "oranges", "pears"]
id2|{"name": "Drazen Popovic", "age": 35}
id3|"Lorem ipsum"

Now we simply start the service:

json-fs-store-cli fact-statement-store-grpc --db $JS_STORE_DIR/json-store.db

You can inspect and interact with the service using the gRPC utilities provided in the environment (grpcurl and grpcui).

Let's leave the fact-statement-store-grpc process running in the foreground of the current shell and open a new [coop-env ~ coop-tutorial] shell session to continue with the tutorial. We're almost there!

7. Running a Publisher gRPC service

The Publisher gRPC is the principal fronted service that COOP users interact with as described in the COOP Frontend protocol. This service relies on the back-end services that we've already set up, namely the TxBuilder gRPC service and the FactStatementStore gRPC service.

It's straightforward to run (you can use Bash function run-publisher):

export COOP_PUBLISHER_DIR=.coop-publisher-cli
generate-keys $COOP_PUBLISHER_DIR
coop-publisher-cli publisher-grpc

The default command line arguments are sufficient for our scenario.

You can inspect and interact with the service using the gRPC utilities provided in the environment (grpcurl and grpcui).

Let's leave the publisher-grpc process running in the foreground of the current shell and open a new [coop-env ~ coop-tutorial] shell session to continue with the tutorial. That's the last shell I promise, now we get to finally publish some fact statements.

8. Publishing a Fact Statement

With the COOP Publisher fully set up, we're ready to have our users publish some Fact Statements (SeePublishing a Fact Statement).

The users find out the Fact Statement Identifiers in a way not prescribed by COOP. The Oracle provides some kind of access to their Fact Statement Store, for example by an additional API with some search features, or even allows users to request a new Fact Statement to be collected/computed and inserted into the store. Regardless, the users must approach the COOP Publisher with a Fact Statement Identifier that the back-end can eventually retrieve from the Fact Statement Store.

NOTE: COOP Publisher works with Fact Statements available in some Oracle's Fact Statement Store. Each Fact Statement in a store should get their own unique identifier, but this responsibility falls under a concrete Fact Statement Store operator.

With that, we already know there are 3 Fact Statements in our Fact Statement Store we've set up, namely id1, id2, and id3. Let's publish all three of these Fact Statements:

[coop-env ~ coop-tutorial] $ REQ=$(cat <<EOF
    {
        "fsInfos": [
            {
                "fsId": "$(echo -ne id1 | base64)",
                "gcAfter": {
                    "extended": "NEG_INF"
                }
            },
            {
                "fsId": "$(echo -ne id2 | base64)",
                "gcAfter": {
                    "extended": "NEG_INF"
                }
            },
            {
                "fsId": "$(echo -ne id3 | base64)",
                "gcAfter": {
                    "extended": "NEG_INF"
                }
            }
        ],
        "submitter": {
            "base16": "$SUBMITTER_PKH"
        }
    }
EOF
)

This prepares a request to be issued with grpcurl. The request lists all the Fact Statement Identifiers we wish to publish, along with their desired validity time (after which they can be 'garbage collected'). In this particular case, we've set the time to NEG_INF meaning we can garbage collect it at any time after publishing.

NOTE: Users can specify validity time for the Fact Statement UTxOs they created and adjust it to the needs of the dApps they are referenced with. Some Fact Statements are going to be short lived, and some long lived, that largely depends on how the Fact Statement is used by a Cardano dApp. Protocol enables Submitters to 'garbage collect' obsolete Fact Statement UTxOs and reclaim the Min UTxO Ada held within.

Validity time is specified using a Unix timestamp in milliseconds. When interacting with the GRPC service ensure that your tooling can convert its native timestamps to milliseconds.

Let's issue a request against the Publisher gRPC service:

RESP=$(echo $REQ | grpcurl -insecure -import-path $COOP_PROTO \
 -proto $COOP_PROTO/publisher-service.proto -d @ \
 localhost:5080 coop.publisher.Publisher/createMintFsTx)

And inspect the response:

echo "$RESP" | jq '.info'

The jq result snippet will show the base64 encoded hashes of the fact-store statement IDs:

{
  "txBuilderInfo": {
    "publishedFsIds": [
      "aWQx",
      "aWQy",
      "aWQz"
    ]
  }
}

With no errors:

echo "$RESP" | jq '.error'

We should see the result as:

null

The Publisher gRPC service successfully serviced the request and returned a CBOR-encoded Cardano transaction in the mintFsTx field of the response. Let's format the transaction so cardano-cli can understand it:

echo "$RESP" | jq '.mintFsTx | .cborHex = .cborBase16 | del(.cborBase16) | .description = "" | .type = "Tx BabbageEra"' \
 > transaction-to-sign.json

NOTE: Any Cardano wallet could be used as COOP provides a raw CBOR encoded transaction, we just used cardano-cli for convenience to demonstrate the concept.

Finally, we can sign the transaction:

cardano-cli transaction sign \
 --tx-file transaction-to-sign.json \
 --signing-key-file $WALLETS/my-signing-key-"$SUBMITTER_PKH".skey \
 --out-file transaction-to-submit.json

And submit it:

cardano-cli transaction submit \
 --tx-file transaction-to-submit.json  --mainnet

All being well, the command should be successful:

Transaction successfully submitted.

The transaction was successfully submitted which means we should be able to see that reflected in the state of the Protocol:

coop-get-state && jq ".[\"cs'factStatements\"]" $COOP_PAB_DIR/coop-state.json
[
  {
    "fd'fs": {
      "contents": [
        {
          "contents": "6170706c6573",
          "tag": "B"
        },
        {
          "contents": "6f72616e676573",
          "tag": "B"
        },
        {
          "contents": "7065617273",
          "tag": "B"
        }
      ],
      "tag": "List"
    },
    "fd'fsId": "696431",
    "fs'gcAfter": {
      "tag": "NegInf"
    },
    "fs'submitter": {
      "getPubKeyHash": "51bf399017a57f8873bf155f818feca7384c4618e5e6367450582325"
    }
  },
  {
    "fd'fs": {
      "contents": [
        [
          {
            "contents": "616765",
            "tag": "B"
          },
          {
            "contents": 35,
            "tag": "I"
          }
        ],
        [
          {
            "contents": "6e616d65",
            "tag": "B"
          },
          {
            "contents": "4472617a656e20506f706f766963",
            "tag": "B"
          }
        ]
      ],
      "tag": "Map"
    },
    "fd'fsId": "696432",
    "fs'gcAfter": {
      "tag": "NegInf"
    },
    "fs'submitter": {
      "getPubKeyHash": "51bf399017a57f8873bf155f818feca7384c4618e5e6367450582325"
    }
  },
  {
    "fd'fs": {
      "contents": "4c6f72656d20697073756d",
      "tag": "B"
    },
    "fd'fsId": "696433",
    "fs'gcAfter": {
      "tag": "NegInf"
    },
    "fs'submitter": {
      "getPubKeyHash": "51bf399017a57f8873bf155f818feca7384c4618e5e6367450582325"
    }
  }
]

Indeed, all the Fact Statements have been successfully published and can be used by any dApp by simply referencing the desired Fact Statement UTxOs. You can see the fs'submitter field set to the SUBMITTER_PKH which is important for when the Submitter decides to garbage collect the obsolete Fact Statement UTxOs. With that said, let's try and do exactly that...

9. Garbage collecting obsolete Fact Statement UTxOs

The fs'gcAfter field of the Fact Statement UTxO datums denotes when that UTxO can be spent by the Submitter (denoted in fs'submitter field) that created that UTxO.

NOTE: The fs'gcAfter validity time is a property of the Fact Statement UTxO, not the Fact Statement itself. It's merely used to enable Submitters manage reclaiming the Min UTxO Ada they had to pay for each Fact Statement UTxO they created.

Since, for the purpose of this tutorial, we've created the Fact Statement UTxOs that are considered 'immediately obsolete', we can proceed and garbage collect them, and thus reclaim the Min UTxO Ada amount locked within.

REQ=$(cat <<EOF
    {
        "fsIds": [
                 "$(echo -ne 'id1' | base64)",
                 "$(echo -ne 'id2' | base64)",
                 "$(echo -ne 'id3' | base64)"
                 ],
        "submitter": {
            "base16": "$SUBMITTER_PKH"
        }
    }
EOF
)
RESP=$(echo $REQ \
 | grpcurl -insecure -import-path $COOP_PROTO \
 -proto $COOP_PROTO/publisher-service.proto -d @ \
 localhost:5080 coop.publisher.Publisher/createGcFsTx)

Inspect the result with:

echo "$RESP" | jq '.info'

We should see the base64 encoded fact-store statement IDs as before are now obsolete:

{
  "txBuilderInfo": {
    "obsoleteFsIds": [
      "aWQx",
      "aWQy",
      "aWQz"
    ]
  }
}

The Publisher gRPC service successfully serviced the request and returned a CBOR-encoded Cardano transaction in the gcFsTx field of the response. Let's format the transaction so cardano-cli can understand it, then sign and submit it:

echo "$RESP" | jq '.gcFsTx | .cborHex = .cborBase16 | del(.cborBase16) | .description = "" | .type = "TxBodyBabbage"' > transaction-to-sign.json
cardano-cli transaction sign \
 --tx-body-file transaction-to-sign.json \
 --signing-key-file $WALLETS/my-signing-key-"$SUBMITTER_PKH".skey \
 --out-file transaction-to-submit.json
cardano-cli transaction submit \
 --tx-file transaction-to-submit.json  --mainnet

The transaction should be successful:

Transaction successfully submitted.

Great! Let's check the state of the Protocol now...

coop-get-state && jq ".[\"cs'factStatements\"]" $COOP_PAB_DIR/coop-state.json

We should see an empty list:

[]

As expected, there are no more Fact Statements available in the system.

10. Garbage collecting obsolete Certificate UTxOs

The COOP Publisher operators can also manage the reclaiming of Min UTxO Ada they had to pay for each Certificate UTxO they created when issuing new Authentication tokens with coop-pab-cli mint-auth.

Again, inspecting the state with coop-get-state we see there's an obsolete Certificate UTxO that can be garbage collected.

coop-get-state

We should see the state looks as follows:

getState: Success
{
   "cs'certificates" : [
      {
         "cert'id" : "7279672bf427c10d43492f41ab3af02a8bcb97d9777539fc5eae0b108850c3ce",
         "cert'redeemerAc" : {
            "unAssetClass" : [
               {
                  "unCurrencySymbol" : "6b14c29615e356edfce1eeb652b703daa7c246bd52fa8d87c17aafaf"
               },
               "c639e2f8b64d6a0bdf1d48de48d832c57342e7980d6a4e98df92ef8c2c54ce75"
            ]
         },
         "cert'validity" : {
            "ivFrom" : [
               {
                  "contents" : {
                     "getPOSIXTime" : 1668599835000
                  },
                  "tag" : "Finite"
               },
               true
            ],
            "ivTo" : [
               {
                  "contents" : {
                     "getPOSIXTime" : 1668603435000
                  },
                  "tag" : "Finite"
               },
               true
            ]
         }
      }
   ],
   "cs'currentTime" : [
      {
         "getPOSIXTime" : 1668601830000
      },
      {
         "getPOSIXTime" : 1668601831000
      }
   ],
   "cs'factStatements" : []
}

Let's garbage collect it then...

coop-pab-cli garbage-collect --cert-rdmr-wallet $CERT_RDMR_PKH

Garbage collection should complete successfully:

[CONTRACT] [INFO [Any]] burnCerts: Finished
garbageCollect: Collected $CERT UTxOs from @CertV using $CERT-RDMR tokens

This is where Certificate redeemer wallets come into play as they hold the tokens that the verifying Plutus script checks when validating the consumption of its outputs.

coop-get-state

Results in:

getState: Success
{
   "cs'certificates" : [ ],
   "cs'currentTime" : [
      {
         "getPOSIXTime" : 1668608405000
      },
      {
         "getPOSIXTime" : 1668608406000
      }
   ],
   "cs'factStatements" : []
}

And we've made the full circle :)

11. Referencing published Fact Statement in Consumer dApps

The COOP Publisher must announce the deployment file created after COOP Plutus protocol genesis. This file contains the Fact Statement minting policy script which is the Currency Symbol the consuming dApps use to assert the authenticity and provenance of the referenced Fact Statement UTxOs.

An example Consumer validator script was provided to demonstrate how to authenticate Fact Statement UTxOs on-chain. The script performs a simple assertion on the Value of a referenced UTxO to make sure it contains a CurrencySymbol of the $FS tokens it trusts

The second part of the script demonstrates how to parse a Plutus JSON Fact Statement.