diff --git a/404.html b/404.html index ac14d81b..6650b11f 100644 --- a/404.html +++ b/404.html @@ -5,13 +5,13 @@ Page Not Found | GraphOps Docs - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/698cb0b5.0cfa8968.js b/assets/js/698cb0b5.0cfa8968.js new file mode 100644 index 00000000..e220052f --- /dev/null +++ b/assets/js/698cb0b5.0cfa8968.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6245],{3905:(e,t,n)=>{n.d(t,{Zo:()=>s,kt:()=>h});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var p=a.createContext({}),d=function(e){var t=a.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},s=function(e){var t=d(e.components);return a.createElement(p.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,p=e.parentName,s=l(e,["components","mdxType","originalType","parentName"]),u=d(n),m=r,h=u["".concat(p,".").concat(m)]||u[m]||c[m]||o;return n?a.createElement(h,i(i({ref:t},s),{},{components:n})):a.createElement(h,i({ref:t},s))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,i=new Array(o);i[0]=m;var l={};for(var p in t)hasOwnProperty.call(t,p)&&(l[p]=t[p]);l.originalType=e,l[u]="string"==typeof e?e:r,i[1]=l;for(var d=2;d{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>l,toc:()=>d});var a=n(7462),r=(n(7294),n(3905));const o={sidebar_position:3},i="One-shot CLI",l={unversionedId:"graphcast/radios/one-shot",id:"graphcast/radios/one-shot",title:"One-shot CLI",description:"The source code for the one-shot CLI is available on GitHub.",source:"@site/docs/graphcast/radios/one-shot.md",sourceDirName:"graphcast/radios",slug:"/graphcast/radios/one-shot",permalink:"/graphcast/radios/one-shot",draft:!1,editUrl:"https://github.com/graphops/docs/edit/main/docs/graphcast/radios/one-shot.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"gnSidebar",previous:{title:"Subgraph Radio",permalink:"/graphcast/radios/subgraph-radio"}},p={},d=[{value:"Introduction",id:"introduction",level:2},{value:"Run with Docker",id:"run-with-docker",level:2},{value:"(or) Run using a pre-built binary",id:"or-run-using-a-pre-built-binary",level:2},{value:"(or) Run using a pre-built binary",id:"or-run-using-a-pre-built-binary-1",level:2}],s={toc:d},u="wrapper";function c(e){let{components:t,...n}=e;return(0,r.kt)(u,(0,a.Z)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"one-shot-cli"},"One-shot CLI"),(0,r.kt)("p",null,"The source code for the one-shot CLI is available ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/one-shot-cli"},"on GitHub"),"."),(0,r.kt)("h2",{id:"introduction"},"Introduction"),(0,r.kt)("p",null,"The one-shot CLI enables sending one-off messages. Currently, its default behaviour is to construct and send a message of type ",(0,r.kt)("inlineCode",{parentName:"p"},"VersionUpgradeMessage"),", which is used for the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.graphops.xyz/graphcast/design-principles#subgraph-upgrade-pre-sync"},"Subgraph Upgrade Pre-sync feature")," of Subgraph Radio."),(0,r.kt)("p",null,"The one-shot CLI is configured using config variables. You will need to prepare the following config variables (either as env variables or passing CLI args when running the CLI):"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Name"),(0,r.kt)("th",{parentName:"tr",align:null},"Description and Examples"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY")),(0,r.kt)("td",{parentName:"tr",align:null},"Private key to the Graphcast ID wallet (precendence over mnemonics).",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY=YOUR_PRIVATE_KEY"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"MNEMONIC")),(0,r.kt)("td",{parentName:"tr",align:null},"Mnemonic to the Graphcast ID wallet (first address of the wallet is used; Only one of private key or mnemonic is needed).",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"MNEMONIC=YOUR_MNEMONIC"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"GRAPH_ACCOUNT")),(0,r.kt)("td",{parentName:"tr",align:null},"Graph account corresponding to Graphcast operator.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"GRAPH_ACCOUNT=YOUR_GRAPH_ACCOUNT"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"REGISTRY_SUBGRAPH")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph endpoint to the Graphcast Registry.",(0,r.kt)("br",null),"Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-goerli"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"NETWORK_SUBGRAPH")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph endpoint to The Graph network subgraph.",(0,r.kt)("br",null),"Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"GRAPHCAST_NETWORK")),(0,r.kt)("td",{parentName:"tr",align:null},"Supported Graphcast networks: mainnet, testnet.",(0,r.kt)("br",null),"Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"testnet"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"IDENTIFIER")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph deployment hash is used to be the message content identifier.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"IDENTIFIER=YOUR_IDENTIFIER"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"NEW_HASH")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph deployment hash for the upgrade version of the subgraph.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"NEW_HASH=YOUR_NEW_HASH"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SUBGRAPH_ID")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph id shared by the old and new deployment.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"SUBGRAPH_ID=YOUR_SUBGRAPH_ID"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"INDEX_NETWORK")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph id shared by the old and new deployment.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"INDEX_NETWORK=YOUR_INDEX_NETWORK"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"MIGRATION_TIME")),(0,r.kt)("td",{parentName:"tr",align:null},"UNIX timestamp that the developer plan on migrating the usage.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"MIGRATION_TIME=YOUR_MIGRATION_TIME"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"ID_VALIDATION")),(0,r.kt)("td",{parentName:"tr",align:null},"Identity validation mechanism for message signers (options: no-check, valid-address, graphcast-registered, graph-network-account, registered-indexer, indexer).",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"ID_VALIDATION=valid-address"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"LOG_LEVEL")),(0,r.kt)("td",{parentName:"tr",align:null},"Logging configuration to set as RUST_LOG.",(0,r.kt)("br",null),"Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"info"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"LOG_FORMAT")),(0,r.kt)("td",{parentName:"tr",align:null},"Support logging formats: pretty, json, full, compact.",(0,r.kt)("br",null),"Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"pretty"))))),(0,r.kt)("p",null,"The one-shot CLI code is very extensible and could be altered to send any kind of Graphcast-compatible message to the network."),(0,r.kt)("h2",{id:"run-with-docker"},"Run with Docker"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Pull the one-shot CLI image")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"docker pull ghcr.io/graphops/one-shot-cli:latest\n")),(0,r.kt)("ol",{start:2},(0,r.kt)("li",{parentName:"ol"},"Run the image, providing the required configuration variables. Here's a sample configuration:")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},'docker run ghcr.io/graphops/one-shot-cli \\\n --private-key "1b098c0f518c046dc01bc9cf785cb670e6e059d99a294319598583f546f420c8" \\\n --graph-account "0xedfecf44f942d53b0b8c9559560bc6cb3d6d2c1d" \\\n --identifier "QmXoFPYJmtSraJDTEGDXLAP52FjVQmLBJqrj6DUevk47o" \\\n --new-hash "Qmf7mgYnJktiYTvXGwBhbR52lXpjCZgFLA5Y9kugHtZmWU" \\\n --subgraph-id "J5s8MTaDrNwxHVeB2okoVnZ2eULeYc47L6LKef6BLWwX" \\\n --index-network "goerli" \\\n --migration-time 1692114708\n')),(0,r.kt)("h2",{id:"or-run-using-a-pre-built-binary"},"(or) Run using a pre-built binary"),(0,r.kt)("p",null,"We also provide pre-built binaries for Ubuntu and MacOS, which you can find in the ",(0,r.kt)("inlineCode",{parentName:"p"},"Assets")," section on each release in the ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/one-shot-cli/releases"},"releases page")," on Github. Simply download the binary, make it executable (",(0,r.kt)("inlineCode",{parentName:"p"},"chmod a+x ./one-shot-cli-{TAG}-{SYSTEM}"),") and then run it (using ",(0,r.kt)("inlineCode",{parentName:"p"},"./one-shot-cli-{TAG}-{SYSTEM}"),"), like this:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},'./one-shot-cli-0.0.1-macos \\\n --private-key "1b098c0f518c046dc01bc9cf785cb670e6e059d99a294319598583f546f420c8" \\\n --graph-account "0xedfecf44f942d53b0b8c9559560bc6cb3d6d2c1d" \\\n --identifier "QmXoFPYJmtSraJDTEGDXLAP52FjVQmLBJqrj6DUevk47o" \\\n --new-hash "Qmf7mgYnJktiYTvXGwBhbR52lXpjCZgFLA5Y9kugHtZmWU" \\\n --subgraph-id "J5s8MTaDrNwxHVeB2okoVnZ2eULeYc47L6LKef6BLWwX" \\\n --index-network "goerli" \\\n --migration-time 1692114708\n')),(0,r.kt)("h2",{id:"or-run-using-a-pre-built-binary-1"},"(or) Run using a pre-built binary"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Clone the repo")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"git clone https://github.com/graphops/one-shot-cli.git\n")),(0,r.kt)("ol",{start:2},(0,r.kt)("li",{parentName:"ol"},"Navigate to the project directory")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"cd one-shot-cli\n")),(0,r.kt)("ol",{start:3},(0,r.kt)("li",{parentName:"ol"},"Run the CLI")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},'cargo run --release -- --private-key "1b098c0f518c046dc01bc9cf785cb670e6e059d99a294319598583f546f420c8" \\\n --graph-account "0xedfecf44f942d53b0b8c9559560bc6cb3d6d2c1d" \\\n --identifier "QmXoFPYJmtSraJDTEGDXLAP52FjVQmLBJqrj6DUevk47o" \\\n --new-hash "Qmf7mgYnJktiYTvXGwBhbR52lXpjCZgFLA5Y9kugHtZmWU" \\\n --subgraph-id "J5s8MTaDrNwxHVeB2okoVnZ2eULeYc47L6LKef6BLWwX" \\\n --index-network "goerli" \\\n --migration-time 1692114708\n')))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/698cb0b5.0d885c52.js b/assets/js/698cb0b5.0d885c52.js deleted file mode 100644 index aac184d0..00000000 --- a/assets/js/698cb0b5.0d885c52.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6245],{3905:(e,t,n)=>{n.d(t,{Zo:()=>s,kt:()=>h});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var p=a.createContext({}),d=function(e){var t=a.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},s=function(e){var t=d(e.components);return a.createElement(p.Provider,{value:t},e.children)},m="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},c=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,p=e.parentName,s=i(e,["components","mdxType","originalType","parentName"]),m=d(n),c=r,h=m["".concat(p,".").concat(c)]||m[c]||u[c]||o;return n?a.createElement(h,l(l({ref:t},s),{},{components:n})):a.createElement(h,l({ref:t},s))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,l=new Array(o);l[0]=c;var i={};for(var p in t)hasOwnProperty.call(t,p)&&(i[p]=t[p]);i.originalType=e,i[m]="string"==typeof e?e:r,l[1]=i;for(var d=2;d{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>u,frontMatter:()=>o,metadata:()=>i,toc:()=>d});var a=n(7462),r=(n(7294),n(3905));const o={sidebar_position:3},l="One-shot CLI",i={unversionedId:"graphcast/radios/one-shot",id:"graphcast/radios/one-shot",title:"One-shot CLI",description:"The source code for the one-shot CLI is available on GitHub as a member of the Subgraph Radio workspace.",source:"@site/docs/graphcast/radios/one-shot.md",sourceDirName:"graphcast/radios",slug:"/graphcast/radios/one-shot",permalink:"/graphcast/radios/one-shot",draft:!1,editUrl:"https://github.com/graphops/docs/edit/main/docs/graphcast/radios/one-shot.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"gnSidebar",previous:{title:"Subgraph Radio",permalink:"/graphcast/radios/subgraph-radio"}},p={},d=[{value:"Introduction",id:"introduction",level:2}],s={toc:d},m="wrapper";function u(e){let{components:t,...n}=e;return(0,r.kt)(m,(0,a.Z)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"one-shot-cli"},"One-shot CLI"),(0,r.kt)("p",null,"The source code for the one-shot CLI is available ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/subgraph-radio/tree/dev/one-shot"},"on GitHub")," as a member of the Subgraph Radio workspace."),(0,r.kt)("h2",{id:"introduction"},"Introduction"),(0,r.kt)("p",null,"The one-shot CLI enables sending one-off messages. Currently, its default behaviour is to construct and send a message of type ",(0,r.kt)("inlineCode",{parentName:"p"},"VersionUpgradeMessage"),", which is used for the ",(0,r.kt)("a",{parentName:"p",href:"graphcast/design-principles#subgraph-versioning"},"Subgraph Versioning functionality")," of Subgraph Radio."),(0,r.kt)("p",null,"The one-shot CLI is configured using config variables. You will need to prepare the following config variables (either as env variables or passing CLI args when running the CLI):"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Name"),(0,r.kt)("th",{parentName:"tr",align:null},"Description and Examples"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY")),(0,r.kt)("td",{parentName:"tr",align:null},"Private key to the Graphcast ID wallet (precendence over mnemonics).",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY=YOUR_PRIVATE_KEY"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"MNEMONIC")),(0,r.kt)("td",{parentName:"tr",align:null},"Mnemonic to the Graphcast ID wallet (first address of the wallet is used; Only one of private key or mnemonic is needed).",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"MNEMONIC=YOUR_MNEMONIC"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"GRAPH_ACCOUNT")),(0,r.kt)("td",{parentName:"tr",align:null},"Graph account corresponding to Graphcast operator.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"GRAPH_ACCOUNT=YOUR_GRAPH_ACCOUNT"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"REGISTRY_SUBGRAPH")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph endpoint to the Graphcast Registry.",(0,r.kt)("br",null),"Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-goerli"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"NETWORK_SUBGRAPH")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph endpoint to The Graph network subgraph.",(0,r.kt)("br",null),"Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"GRAPHCAST_NETWORK")),(0,r.kt)("td",{parentName:"tr",align:null},"Supported Graphcast networks: mainnet, testnet.",(0,r.kt)("br",null),"Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"testnet"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"IDENTIFIER")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph deployment hash is used to be the message content identifier.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"IDENTIFIER=YOUR_IDENTIFIER"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"NEW_HASH")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph deployment hash for the upgrade version of the subgraph.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"NEW_HASH=YOUR_NEW_HASH"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SUBGRAPH_ID")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph id shared by the old and new deployment.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"SUBGRAPH_ID=YOUR_SUBGRAPH_ID"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"INDEX_NETWORK")),(0,r.kt)("td",{parentName:"tr",align:null},"Subgraph id shared by the old and new deployment.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"INDEX_NETWORK=YOUR_INDEX_NETWORK"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"MIGRATION_TIME")),(0,r.kt)("td",{parentName:"tr",align:null},"UNIX timestamp that the developer plan on migrating the usage.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"MIGRATION_TIME=YOUR_MIGRATION_TIME"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"ID_VALIDATION")),(0,r.kt)("td",{parentName:"tr",align:null},"Identity validation mechanism for message signers (options: no-check, valid-address, graphcast-registered, graph-network-account, registered-indexer, indexer).",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"ID_VALIDATION=valid-address"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"LOG_LEVEL")),(0,r.kt)("td",{parentName:"tr",align:null},"Logging configuration to set as RUST_LOG.",(0,r.kt)("br",null),"Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"info"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"LOG_FORMAT")),(0,r.kt)("td",{parentName:"tr",align:null},"Support logging formats: pretty, json, full, compact.",(0,r.kt)("br",null),"Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"pretty"))))),(0,r.kt)("p",null,"The one-shot CLI code is very extensible and could be altered to send any kind of Graphcast-compatible message to the network."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/86e365b5.96579636.js b/assets/js/86e365b5.96579636.js deleted file mode 100644 index 25127b7c..00000000 --- a/assets/js/86e365b5.96579636.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9635],{3905:(e,t,a)=>{a.d(t,{Zo:()=>p,kt:()=>m});var n=a(7294);function i(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function r(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(i[a]=e[a]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(i[a]=e[a])}return i}var s=n.createContext({}),d=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},p=function(e){var t=d(e.components);return n.createElement(s.Provider,{value:t},e.children)},h="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},c=n.forwardRef((function(e,t){var a=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),h=d(a),c=i,m=h["".concat(s,".").concat(c)]||h[c]||u[c]||r;return a?n.createElement(m,o(o({ref:t},p),{},{components:a})):n.createElement(m,o({ref:t},p))}));function m(e,t){var a=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=a.length,o=new Array(r);o[0]=c;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[h]="string"==typeof e?e:i,o[1]=l;for(var d=2;d{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>d});var n=a(7462),i=(a(7294),a(3905));const r={sidebar_position:1},o="Subgraph Radio",l={unversionedId:"graphcast/radios/subgraph-radio",id:"graphcast/radios/subgraph-radio",title:"Subgraph Radio",description:"The source code for the Subgraph Radio is available on GitHub and Docker builds are automatically published as GitHub Packages. Subgraph Radio is also published as a crate on crates.io.",source:"@site/docs/graphcast/radios/subgraph-radio.md",sourceDirName:"graphcast/radios",slug:"/graphcast/radios/subgraph-radio",permalink:"/graphcast/radios/subgraph-radio",draft:!1,editUrl:"https://github.com/graphops/docs/edit/main/docs/graphcast/radios/subgraph-radio.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"gnSidebar",previous:{title:"Listener Radio",permalink:"/graphcast/radios/listener-radio"},next:{title:"One-shot CLI",permalink:"/graphcast/radios/one-shot"}},s={},d=[{value:"Introduction",id:"introduction",level:2},{value:"Basic Configuration",id:"basic-configuration",level:3},{value:"Run with Docker",id:"run-with-docker",level:3},{value:"(or) Run with docker-compose",id:"or-run-with-docker-compose",level:3},{value:"(or) Run as part of StakeSquid's docker-compose setup",id:"or-run-as-part-of-stakesquids-docker-compose-setup",level:3},{value:"(or) Run using a pre-built binary",id:"or-run-using-a-pre-built-binary",level:3},{value:"Advanced Configuration",id:"advanced-configuration",level:2},{value:"Configurations explained",id:"configurations-explained",level:3},{value:"COVERAGE (topic)",id:"coverage-topic",level:4},{value:"Identity validaiton",id:"identity-validaiton",level:4},{value:"Gossip protocol",id:"gossip-protocol",level:4},{value:"State management",id:"state-management",level:4},{value:"Monitoring the Radio",id:"monitoring-the-radio",level:2},{value:"Notifications",id:"notifications",level:3},{value:"Prometheus & Grafana",id:"prometheus--grafana",level:3},{value:"HTTP Server",id:"http-server",level:2},{value:"How it works",id:"how-it-works",level:2},{value:"Fetching active allocations",id:"fetching-active-allocations",level:3},{value:"Gathering and comparing normalised POIs",id:"gathering-and-comparing-normalised-pois",level:3},{value:"Developing the Subgraph Radio",id:"developing-the-subgraph-radio",level:2},{value:"Building the image using the Dockerfile locally",id:"building-the-image-using-the-dockerfile-locally",level:4},{value:"Prerequisites",id:"prerequisites",level:5},{value:"Running the Subgraph Radio inside a Docker container",id:"running-the-subgraph-radio-inside-a-docker-container",level:5},{value:"Building Subgraph Radio locally",id:"building-subgraph-radio-locally",level:3},{value:"Prerequisites",id:"prerequisites-1",level:4},{value:"Running the Subgraph Radio natively",id:"running-the-subgraph-radio-natively",level:4}],p={toc:d},h="wrapper";function u(e){let{components:t,...a}=e;return(0,i.kt)(h,(0,n.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"subgraph-radio"},"Subgraph Radio"),(0,i.kt)("p",null,"The source code for the Subgraph Radio is available ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/graphops/subgraph-radio"},"on GitHub")," and Docker builds are automatically published as ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/graphops/subgraph-radio/pkgs/container/subgraph-radio"},"GitHub Packages"),". Subgraph Radio is also published as a crate ",(0,i.kt)("a",{parentName:"p",href:"https://crates.io/crates/subgraph-radio"},"on crates.io"),"."),(0,i.kt)("h2",{id:"introduction"},"Introduction"),(0,i.kt)("p",null,"Subgraph Radio is an optional component of the Graph Protocol Indexer Stack. It uses the Graphcast Network to facilitate the exchange of Subgraph data and information among Indexers and other participants in the network."),(0,i.kt)("p",null,"An essential aspect of earning indexing rewards as an Indexer is the generation of valid Proof of Indexing hashes (POIs). These POIs provide evidence of the Indexer's possession of correct data. Submitting invalid POIs could lead to a ",(0,i.kt)("a",{parentName:"p",href:"https://thegraph.com/docs/en/network/indexing/#what-are-disputes-and-where-can-i-view-them"},"Dispute")," and possible slashing by the protocol. With Subgraph Radio's POI functionality, Indexers gain confidence knowing that their POIs are continually cross-verified against those of other participating Indexers. Should there be a discrepancy in POIs, Subgraph Radio functions as an early warning system, alerting the Indexer within minutes."),(0,i.kt)("p",null,"All POIs generated through Subgraph Radio are public (normalized), meaning they are hashed with a ",(0,i.kt)("inlineCode",{parentName:"p"},"0x0")," Indexer Address and can be compared between Indexers. However, these public POIs are not valid for on-chain reward submission. Subgraph Radio groups and weighs public POIs according to the aggregate stake in GRT attesting to each. The normalized POI with the most substantial aggregate attesting stake is deemed canonical and used for comparisons with your local Indexer POIs."),(0,i.kt)("p",null,"For enhanced security, we recommend running Subgraph Radio with an independent Graphcast ID linked to your Indexer account. This Graphcast ID is an Ethereum account authorized to sign POI attestations on behalf of your Indexer. By default, Subgraph Radio validates messages received from any signer, that can be resolved to an Indexer address, regardless of whether or not they are registered on the Graphcast registry (though this behavior can be altered by setting the ID_VALIDATION config variable). Learn how to register a Graphcast ID ",(0,i.kt)("a",{parentName:"p",href:"https://docs.graphops.xyz/graphcast/sdk/registry#register-a-graphcast-id"},"here"),"."),(0,i.kt)("h3",{id:"basic-configuration"},"Basic Configuration"),(0,i.kt)("p",null,"The Subgraph Radio is configured using environment variables. You will need to prepare the following environment variables:"),(0,i.kt)("table",null,(0,i.kt)("thead",{parentName:"table"},(0,i.kt)("tr",{parentName:"thead"},(0,i.kt)("th",{parentName:"tr",align:null},"Name"),(0,i.kt)("th",{parentName:"tr",align:null},"Description and examples"),(0,i.kt)("th",{parentName:"tr",align:null}))),(0,i.kt)("tbody",{parentName:"table"},(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY")),(0,i.kt)("td",{parentName:"tr",align:null},"Private key of the Graphcast ID wallet or the Indexer Operator wallet (precendence over ",(0,i.kt)("inlineCode",{parentName:"td"},"MNEMONICS"),").",(0,i.kt)("br",null),"Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")),(0,i.kt)("td",{parentName:"tr",align:null})),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"INDEXER_ADDRESS")),(0,i.kt)("td",{parentName:"tr",align:null},"Indexer address for Graphcast message verification, in all lowercase.",(0,i.kt)("br",null),"Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"0xabcdcabdabcdabcdcabdabcdabcdcabdabcdabcd")),(0,i.kt)("td",{parentName:"tr",align:null})),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"GRAPH_NODE_STATUS_ENDPOINT")),(0,i.kt)("td",{parentName:"tr",align:null},"URL to a Graph Node Indexing Status endpoint.",(0,i.kt)("br",null),"Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"http://index-node:8030/graphql")),(0,i.kt)("td",{parentName:"tr",align:null})),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"REGISTRY_SUBGRAPH")),(0,i.kt)("td",{parentName:"tr",align:null},"URL to the Graphcast Registry subgraph for your network. Check ",(0,i.kt)("a",{parentName:"td",href:"../sdk/registry#subgraph-apis"},"APIs")," for your preferred network"),(0,i.kt)("td",{parentName:"tr",align:null})),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"NETWORK_SUBGRAPH")),(0,i.kt)("td",{parentName:"tr",align:null},"URL to the Graph Network subgraph. Check ",(0,i.kt)("a",{parentName:"td",href:"../sdk/registry#subgraph-apis"},"APIs")," for your preferred network"),(0,i.kt)("td",{parentName:"tr",align:null})),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"GRAPHCAST_NETWORK")),(0,i.kt)("td",{parentName:"tr",align:null},"The Graphcast Messaging fleet and pubsub namespace to use.",(0,i.kt)("br",null),"Mainnet: ",(0,i.kt)("inlineCode",{parentName:"td"},"mainnet"),(0,i.kt)("br",null),"Goerli: ",(0,i.kt)("inlineCode",{parentName:"td"},"testnet")),(0,i.kt)("td",{parentName:"tr",align:null})))),(0,i.kt)("h3",{id:"run-with-docker"},"Run with Docker"),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},"Pull the Subgraph Radio image")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-bash"},"docker pull ghcr.io/graphops/subgraph-radio:latest\n")),(0,i.kt)("ol",{start:2},(0,i.kt)("li",{parentName:"ol"},"Run the image, providing the required environment variables. Here's a sample mainnet configuration:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-bash"},'docker run \\\n -e GRAPHCAST_NETWORK="mainnet" \\\n -e REGISTRY_SUBGRAPH="https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-mainnet" \\\n -e NETWORK_SUBGRAPH="https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet" \\\n -e PRIVATE_KEY="PRIVATE_KEY" \\\n -e GRAPH_NODE_STATUS_ENDPOINT="http://graph-node:8030/graphql" \\\n -e RUST_LOG="warn,hyper=warn,graphcast_sdk=info,subgraph_radio=info" \\\n -e INDEXER_ADDRESS="INDEXER_ADDRESS" \\\n ghcr.io/graphops/subgraph-radio:latest\n')),(0,i.kt)("h3",{id:"or-run-with-docker-compose"},"(or) Run with docker-compose"),(0,i.kt)("p",null,"You can append this service definition to your ",(0,i.kt)("inlineCode",{parentName:"p"},"docker-compose")," manifest and customise the definitions:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml"},'services:\n # ... your other service definitions\n subgraph-radio:\n image: ghcr.io/graphops/subgraph-radio:latest\n container_name: subgraph-radio\n restart: unless-stopped\n environment:\n GRAPHCAST_NETWORK: "mainnet"\n REGISTRY_SUBGRAPH: "https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-mainnet"\n NETWORK_SUBGRAPH: "https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet"\n PRIVATE_KEY: "PRIVATE_KEY"\n GRAPH_NODE_STATUS_ENDPOINT: "http://graph-node:8030/graphql"\n RUST_LOG: "warn,hyper=warn,graphcast_sdk=info,subgraph_radio=info"\n INDEXER_ADDRESS: "INDEXER_ADDRESS"\n logging:\n driver: local\n')),(0,i.kt)("h3",{id:"or-run-as-part-of-stakesquids-docker-compose-setup"},"(or) Run as part of ",(0,i.kt)("a",{parentName:"h3",href:"https://github.com/StakeSquid"},"StakeSquid"),"'s docker-compose setup"),(0,i.kt)("p",null,"Subgraph Radio is included as an optional component in both the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/StakeSquid/graphprotocol-mainnet-docker"},"mainnet")," and ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/StakeSquid/graphprotocol-testnet-docker"},"testnet")," versions of StakeSquid's guide."),(0,i.kt)("h3",{id:"or-run-using-a-pre-built-binary"},"(or) Run using a pre-built binary"),(0,i.kt)("p",null,"We also provide pre-built binaries for Ubuntu and MacOS, which you can find in the ",(0,i.kt)("inlineCode",{parentName:"p"},"Assets")," section on each release in the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/graphops/subgraph-radio/releases"},"releases page")," on Github. Simply download the binary, make it executable (",(0,i.kt)("inlineCode",{parentName:"p"},"chmod a+x ./subgraph-radio-{TAG}-{SYSTEM}"),") and then run it (using ",(0,i.kt)("inlineCode",{parentName:"p"},"./subgraph-radio-{TAG}-{SYSTEM}"),")."),(0,i.kt)("h2",{id:"advanced-configuration"},"Advanced Configuration"),(0,i.kt)("p",null,"In the configuration table below is the full list of environment variables you can set, along with example values."),(0,i.kt)("p",null,"See ",(0,i.kt)("a",{parentName:"p",href:"#basic-configuration"},"Basic Configuration")," above. The following environment variables are optional:"),(0,i.kt)("table",null,(0,i.kt)("thead",{parentName:"table"},(0,i.kt)("tr",{parentName:"thead"},(0,i.kt)("th",{parentName:"tr",align:null},"Name (Optional variables)"),(0,i.kt)("th",{parentName:"tr",align:null},"Description and examples"))),(0,i.kt)("tbody",{parentName:"table"},(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"MNEMONIC")),(0,i.kt)("td",{parentName:"tr",align:null},"Mnemonic to the Graphcast ID wallet or the Indexer Operator wallet (first address of the wallet is used; Only one of ",(0,i.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY")," or ",(0,i.kt)("inlineCode",{parentName:"td"},"MNEMONIC")," is needed). Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"claptrap armchair violin..."))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"COLLECT_MESSAGE_DURATION")),(0,i.kt)("td",{parentName:"tr",align:null},"Seconds that the Subgraph Radio will wait to collect remote POI attestations before making a comparison with the local POI. Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"120")," for 2 minutes.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"COVERAGE")),(0,i.kt)("td",{parentName:"tr",align:null},'Toggle for topic coverage level. Possible values: "comprehensive", "on-chain", "minimal". Default is set to "comprehensive" coverage.')),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"TOPICS")),(0,i.kt)("td",{parentName:"tr",align:null},"Comma separated static list of content topics (subgraphs) to subscribe to. Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz,QmUwCFhXM3f6qH9Ls9Y6gDNURBH7mxsn6JcectgxAz6CwU,QmQ1Lyh3U6YgVP6YX1RgRz6c8GmKkEpokLwPvEtJx6cF1y"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"WAKU_HOST")),(0,i.kt)("td",{parentName:"tr",align:null},"Interface onto which to bind the bundled Waku node. Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"WAKU_PORT")),(0,i.kt)("td",{parentName:"tr",align:null},"P2P port on which the bundled Waku node will operate. Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"60000"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"WAKU_NODE_KEY")),(0,i.kt)("td",{parentName:"tr",align:null},"Static Waku Node Key.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"BOOT_NODE_ADDRESSES")),(0,i.kt)("td",{parentName:"tr",align:null},"Peer addresses to use as Waku boot nodes. Example: ",(0,i.kt)("inlineCode",{parentName:"td"},'"addr1, addr2, addr3"'))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"SLACK_TOKEN")),(0,i.kt)("td",{parentName:"tr",align:null},"Slack Token to use for notifications. Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"xoxp-0123456789-0123456789-0123456789-0123456789"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"TELEGRAM_TOKEN")),(0,i.kt)("td",{parentName:"tr",align:null},"Telegram Bot Token to use for notifications. Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"TELEGRAM_CHAT_ID")),(0,i.kt)("td",{parentName:"tr",align:null},"The ID of the Telegram chat to send messages to. Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"-1001234567890"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"SLACK_CHANNEL")),(0,i.kt)("td",{parentName:"tr",align:null},"Name of Slack channel to send messages to (has to be a public channel). Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"poir-notifications"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"WAKU_LOG_LEVEL")),(0,i.kt)("td",{parentName:"tr",align:null},"Waku node logging configuration. Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"INFO")," (is also the default)")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"RUST_LOG")),(0,i.kt)("td",{parentName:"tr",align:null},"Rust tracing configuration. Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"graphcast_sdk=debug,subgraph_radio=debug"),", defaults to ",(0,i.kt)("inlineCode",{parentName:"td"},"info")," for everything")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"DISCORD_WEBHOOK")),(0,i.kt)("td",{parentName:"tr",align:null},"Discord webhook URL for notifications. Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"https://discord.com/api/webhooks/123456789012345678/AbCDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"METRICS_PORT")),(0,i.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose Prometheus metrics on this (off by default). Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"3001"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"METRICS_HOST")),(0,i.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose Prometheus metrics on this (off by default). Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"SERVER_HOST")),(0,i.kt)("td",{parentName:"tr",align:null},"If ",(0,i.kt)("inlineCode",{parentName:"td"},"SERVER_PORT")," is set, the Radio will expose an API service on the given host and port. Default: ",(0,i.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"SERVER_PORT")),(0,i.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose an API service on the given port (off by default). Example: ",(0,i.kt)("inlineCode",{parentName:"td"},"8080"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"LOG_FORMAT")),(0,i.kt)("td",{parentName:"tr",align:null},"Options: ",(0,i.kt)("inlineCode",{parentName:"td"},"pretty")," - verbose and human readable; ",(0,i.kt)("inlineCode",{parentName:"td"},"json")," - not verbose and parsable; ",(0,i.kt)("inlineCode",{parentName:"td"},"compact")," - not verbose and not parsable; ",(0,i.kt)("inlineCode",{parentName:"td"},"full")," - verbose and not parsible. Default value: ",(0,i.kt)("inlineCode",{parentName:"td"},"pretty"),".")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"PERSISTENCE_FILE_PATH")),(0,i.kt)("td",{parentName:"tr",align:null},"Relative path. If set, the Radio will periodically store states of the program to the file in json format (off by default).")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"DISCV5_ENRS")),(0,i.kt)("td",{parentName:"tr",align:null},"Comma separated ENRs for Waku Discv5 bootstrapping. Defaults to empty list.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"DISCV5_PORT")),(0,i.kt)("td",{parentName:"tr",align:null},"Discoverable UDP port. Default: ",(0,i.kt)("inlineCode",{parentName:"td"},"9000"))),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"ID_VALIDATION")),(0,i.kt)("td",{parentName:"tr",align:null},"Defines the level of validation for message signers used during radio operation. Options include: ",(0,i.kt)("inlineCode",{parentName:"td"},"no-check"),", ",(0,i.kt)("inlineCode",{parentName:"td"},"valid-address"),", ",(0,i.kt)("inlineCode",{parentName:"td"},"graphcast-registered"),", ",(0,i.kt)("inlineCode",{parentName:"td"},"graph-network-account"),", ",(0,i.kt)("inlineCode",{parentName:"td"},"registered-indexer"),", ",(0,i.kt)("inlineCode",{parentName:"td"},"indexer"),". Default: ",(0,i.kt)("inlineCode",{parentName:"td"},"indexer"))))),(0,i.kt)("h3",{id:"configurations-explained"},"Configurations explained"),(0,i.kt)("h4",{id:"coverage-topic"},"COVERAGE (topic)"),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"COVERAGE")," is used to specify the topic coverage level. It controls the range of topics (subgraph ipfs hashes) the Indexer subscribes to in order to process data and participate in the network."),(0,i.kt)("p",null,"There are three coverage levels available:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"comprehensive"),": Subscribe to on-chain topics, user-defined static topics, and subgraph deployments syncing on graph node. This level is useful for Indexers who want to compare public POIs for all deployments syncing on their graph node even if they don't have an active allocations open (their stake will not be taken into account in attestation)."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"on-chain"),": Subscribe to on-chain topics and user-defined static topics. This is the default coverage level and is suitable for indexers who only want to compare data for deployments with active allocations."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"minimal"),": Only subscribe to user-defined static topics. This level is for Indexers who want to limit their participation to specific topics of interest.")),(0,i.kt)("h4",{id:"identity-validaiton"},"Identity validaiton"),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"ID_VALIDATION")," is used to define level of validation for message signers used during radio operation. We recommend ",(0,i.kt)("inlineCode",{parentName:"p"},"registered-indexer")," for most strict identity validation, while ",(0,i.kt)("inlineCode",{parentName:"p"},"indexer")," is a viable option for those who want to use the network before considering Grapchast ID registration. You can choose a sender identity validation mechanism for your radio, based on your use case and security preferences."),(0,i.kt)("p",null,"Available Options:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"no-check"),": Does not perform check on the message signature and does not verify the signer. All messages should pass the sender check."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"valid-address"),": Requires the signer to be a valid Ethereum address. Messages should be traceable to an Ethers wallet."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"graphcast-registered"),": Requires the signer to be registered on the Graphcast Registry."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"graph-network-account"),": signer must be a Graph account."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"registered-indexer"),": signer must be registered at Graphcast Registry and correspond to an Indexer satisfying the indexer minimum stake requirement."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"indexer"),": signer must be registered at Graphcast Registry or is a Graph Account, and correspond to an Indexer satisfying the indexer minimum stake requirement.")),(0,i.kt)("h4",{id:"gossip-protocol"},"Gossip protocol"),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"WAKU_HOST")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"WAKU_PORT")," specify where the bundled Waku node runs. If you want to run multiple Radios, or multiple instances of the same Radio, you should run them on different ports."),(0,i.kt)("p",null,"If you want to customize the log level, you can toggle ",(0,i.kt)("inlineCode",{parentName:"p"},"RUST_LOG")," environment variable. Here's an example configuration to get more verbose logging:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},'RUST_LOG="warn,hyper=warn,graphcast_sdk=debug,subgraph_radio=debug"\n')),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"Discv5")," is an ambient node discovery network for establishing a decentralized network of interconnected Graphcast Radios. Discv5, when used in Graphcast Radios, serves as a dedicated peer-to-peer discovery protocol that empowers Radios to form an efficient, decentralized network. Without Discv5, the traffic within the Graphcast network would largely rely on centrally hosted boot nodes, leading to a less distributed architecture. However, with Discv5, Radios are capable of directly routing messages among themselves, significantly enhancing network decentralization and reducing reliance on the central nodes. If you want to learn more about Discv5, check out the ",(0,i.kt)("a",{parentName:"p",href:"https://rfc.vac.dev/spec/33/"},"official spec"),"."),(0,i.kt)("h4",{id:"state-management"},"State management"),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"PERSISTENCE_FILE_PATH")," configuration variable allows the Radio to maintain operational continuity across sessions. When the file path is set, it triggers the Radio to periodically store its state, including local attestations, remote messages and POI comparison results in a JSON-formatted file at the specified path. This facilitates seamless session transitions and minimizes data loss. In the event of a system disruption, the state can be reloaded from this file, ensuring the Radio can resume operation effectively."),(0,i.kt)("h2",{id:"monitoring-the-radio"},"Monitoring the Radio"),(0,i.kt)("h3",{id:"notifications"},"Notifications"),(0,i.kt)("p",null,"If the Radio operator has set up a Slack, Discord and/or Telegram bot integration and the Radio finds a POI mismatch, it sends alerts to the designated channels. The operator can also inspect the logs to see if the Radio is functioning properly, if it's sending and receiving messages, if it's comparing normalised POIs, if there is a found POI mismatch, etc."),(0,i.kt)("h3",{id:"prometheus--grafana"},"Prometheus & Grafana"),(0,i.kt)("p",null,"The Subgraph Radio exposes metrics that can then be scraped by a Prometheus server and displayed in Grafana. In order to use them you have to have a local Prometheus server running and scraping metrics on the provided port. You can specify the metrics host and port by using the environment variables ",(0,i.kt)("inlineCode",{parentName:"p"},"METRICS_PORT")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"METRICS_HOST"),". We also provide a ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/graphops/subgraph-radio/blob/main/subgraph-radio-grafana.json"},"Grafana dashboard config JSON file")," which you can use to visualise the metrics in Grafana."),(0,i.kt)("h2",{id:"http-server"},"HTTP Server"),(0,i.kt)("p",null,"The Radio spins up an HTTP server with a GraphQL API when ",(0,i.kt)("inlineCode",{parentName:"p"},"SERVER_HOST")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"SERVER_PORT")," environment variables are set. The supported routes are:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"/health")," for health status"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"/api/v1/graphql")," for GET and POST requests with GraphQL playground interface")),(0,i.kt)("p",null,"The GraphQL API now includes several advanced queries:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"radioPayloadMessages")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"localAttestations")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"comparisonResults")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"stakeRatio")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"senderRatio"))),(0,i.kt)("p",null,"Below are some example queries:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-graphql"},'Query {\n radioPayloadMessages{\n identifier\n nonce\n blockNumber\n network\n signature\n }\n localAttestations{\n deployment\n blockNumber\n attestation{\n ppoi\n }\n }\n comparisonResults(identifier:"Qm...."){\n deployment\n blockNumber\n resultType\n localAttestation{\n ppoi\n }\n attestations{\n ppoi\n }\n }\n stakeRatio(filter: {deployment: "__", blockNumber: "___"}){\n deployment\n blockNumber\n compareRatio\n }\n senderRatio{\n deployment\n blockNumber\n compareRatio\n }\n}\n')),(0,i.kt)("p",null,"You can customize the returned data from the ",(0,i.kt)("inlineCode",{parentName:"p"},"stakeRatio")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"senderRatio")," queries by providing optional filters as arguments:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"deployment")," - If provided, only attestations for the specified deployment will be included in the comparison."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"block")," - If provided, only attestations for the specified block number will be included in the comparison."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"filter")," - A more complex filter that can include deployment, block_number, and result_type fields. This filter is used to further refine the set of attestations included in the comparison.\nHere's an example of a query with filters:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-graphql"},'Query {\n stakeRatio(filter: {deployment: "Qm....", blockNumber: 12345, result_type: "Type_Name"}){\n deployment\n blockNumber\n compareRatio\n }\n}\n')),(0,i.kt)("p",null,"In this example, the ",(0,i.kt)("inlineCode",{parentName:"p"},"stakeRatio"),' query will return the stake ratios only for attestations from deployment "Qm...." and block number 12345, and only for the specified result type.'),(0,i.kt)("p",null,"Note: The ",(0,i.kt)("inlineCode",{parentName:"p"},"result_type")," field of the filter corresponds to the ",(0,i.kt)("inlineCode",{parentName:"p"},"resultType")," field in the ",(0,i.kt)("inlineCode",{parentName:"p"},"comparisonResults")," query. This field represents the type of comparison result."),(0,i.kt)("p",null,"The API also includes the ",(0,i.kt)("inlineCode",{parentName:"p"},"senderRatio")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"stakeRatio")," endpoints, which return more detailed insights into the state of the Radio."),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"senderRatio")," provides an overview of the consensus status of the attestations from remote messages. It gives a ratio string that signifies the number of indexers with the same public POI as the local Radio. The results are presented as ",(0,i.kt)("inlineCode",{parentName:"p"},"x/y!/z")," where:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"x"),", ",(0,i.kt)("inlineCode",{parentName:"li"},"y"),", and ",(0,i.kt)("inlineCode",{parentName:"li"},"z")," are sorted by descending stake weights"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"!")," indicates the entry that corresponds to the local result.")),(0,i.kt)("p",null,"For example,",(0,i.kt)("inlineCode",{parentName:"p"}," 2/0!")," means there are two indexers attesting with a higher sum of stake weight and no other indexer shares the same public POIs as the local Radio. ",(0,i.kt)("inlineCode",{parentName:"p"},"8!")," means there are eight other indexers agreeing with the local Radio."),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"stakeRatio")," offers similar functionality to senderRatio but the results are based on the stake weight. It orders the attestations by stake weight, then computes the ratio of unique senders."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-graphql"},"Query {\n senderRatio{\n deployment\n blockNumber\n compareRatio\n }\n stakeRatio{\n deployment\n blockNumber\n compareRatio\n }\n}\n")),(0,i.kt)("p",null,"These queries provide a clear aggregation of the attestations from remote messages, giving a concise understanding of the Radio's state. The optional filters - deployment, block, and filter - can be used to refine the results."),(0,i.kt)("h2",{id:"how-it-works"},"How it works"),(0,i.kt)("h3",{id:"fetching-active-allocations"},"Fetching active allocations"),(0,i.kt)("p",null,"The Subgraph Radio is responsible for reading active allocations of the Radio operator's corresponding Indexer. It periodically polls the Graph Node for new blocks on all relevant networks and constructs Graphcast topics on each allocation identified by subgraph deployment IPFS hash."),(0,i.kt)("admonition",{type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"The relevant networks are those corresponding to the subgraphs that have active allocations.")),(0,i.kt)("p",null,"The Radio fetches new active allocations at a regular interval to ensure that it is processing the latest information. Chainheads for these networks are updated with data from the Graph Node, and the Radio ensures that it is always using the latest chainhead when processing messages."),(0,i.kt)("mermaid",{value:"sequenceDiagram\n participant Network Subgraph\n participant Graph Node\n participant Subgraph Radio\n participant Graphcast Network\n actor Human\n loop Track allocated deployments\n Subgraph Radio->>+Network Subgraph: Get latest allocated deployments\n Network Subgraph->>-Subgraph Radio: Return allocated deployments\n loop Monitor allocated deployments and chain heads\n Subgraph Radio->>+Graph Node: Get indexing statuses for allocated deployments\n Graph Node->>-Subgraph Radio: Return matching indexing statuses\n activate Subgraph Radio\n Subgraph Radio->>Subgraph Radio: Update chain heads\n deactivate Subgraph Radio\n loop For each deployment that we are tracking\n opt If deployment reached trigger block is healthy\n Subgraph Radio->>+Graph Node: Fetch POI for deployment\n Graph Node->>-Subgraph Radio: Normalized POI\n activate Subgraph Radio\n Subgraph Radio->>Subgraph Radio: Generate signed POI Attestation\n deactivate Subgraph Radio\n Subgraph Radio--\x3e>Graphcast Network: Broadcast POI Attestation to Graphcast Network\n end\n opt If stored remote attestations and collect message duration passed\n activate Subgraph Radio\n Subgraph Radio->>Subgraph Radio: Compute consensus remote POI\n deactivate Subgraph Radio\n opt If local POI mismatches consensus remote POI\n Subgraph Radio--\x3e>Human: Send POI divergence warning notification\n end\n end\n opt If VersionUpgradeMessage is received\n Graphcast Network--\x3e>Subgraph Radio: VersionUpgradeMessage\n activate Subgraph Radio\n Subgraph Radio--\x3e>Human: Send Version Upgrade notification\n deactivate Subgraph Radio\n end\n end\n end\n end"}),(0,i.kt)("h3",{id:"gathering-and-comparing-normalised-pois"},"Gathering and comparing normalised POIs"),(0,i.kt)("p",null,"At a given interval, the Radio fetches the normalised POI for each deployment. This interval is defined in blocks different for each network. It then saves those public POIs, and as other Indexers running the Radio start doing the same, messages start propagating through the network. The Radio saves each message and processes them on a given interval."),(0,i.kt)("p",null,"The messages include a nonce (UNIX timestamp), block number, signature (used to derive the sender's on-chain Indexer address) and network. Before saving an entry to the map, the Radio operator verifies through the Graph network subgraph for the sender's on-chain identity and amount of tokens staked, which is used during comparisons later on."),(0,i.kt)("mermaid",{value:"flowchart LR\n a[Fetch deployment status] --\x3e b[If healthy & synced]\n b --\x3e|No| eee{End}\n b --\x3e|Yes| c[If reached trigger block]\n c --\x3e|No| eee\n c --\x3e|Yes| d[Fetch POI for deployment]\n d --\x3e|Broadcast| n(Graphcast\\nNetwork)\n n --\x3e|Receive remote POI| o[Other Indexers]\n n --\x3e x{End}"}),(0,i.kt)("p",null,"At another interval, the Radio compares the local public POIs with the collected remote ones. The remote POIs are sorted so that for each subgraph (on each block), the POI that is backed by the most on-chain stake is selected. This means that the combined stake of all Indexers that attested to it is considered, not just the highest staking Indexer. The top POI is then compared with the local POIs for that subgraph at that block to determine consensus."),(0,i.kt)("p",null,"If there is a mismatch and if the Radio operator has set up a Slack, Discord and/or Telegram bot integration, the Radio will send alerts to the designated channels."),(0,i.kt)("p",null,"After a successful comparison, the attestations that have been checked are removed from the store."),(0,i.kt)("mermaid",{value:"flowchart LR\n q[Fetch deployment status] --\x3e g[Has collection window expired?]\n g --\x3e|Yes| t[Compute consensus remote POI]\n g --\x3e|No| p{End}\n c --\x3e|Aggregate| t\n a[Receive POI attestations] --\x3e b[Has collection window expired?]\n b --\x3e|Yes| eee{End}\n b --\x3e|No| c[Store remote attestation\\nfor deployment]\n t --\x3e l[Does local POI match remote consensus POI?]\n l --\x3e|No| i[Send notification]\n l --\x3e|Yes| d{End}"}),(0,i.kt)("h2",{id:"developing-the-subgraph-radio"},"Developing the Subgraph Radio"),(0,i.kt)("h4",{id:"building-the-image-using-the-dockerfile-locally"},"Building the image using the Dockerfile locally"),(0,i.kt)("p",null,"If you want to make any changes to the Subgraph Radio codebase, you can use this option."),(0,i.kt)("h5",{id:"prerequisites"},"Prerequisites"),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},"Clone this repo and ",(0,i.kt)("inlineCode",{parentName:"li"},"cd")," into it"),(0,i.kt)("li",{parentName:"ol"},"Create a ",(0,i.kt)("inlineCode",{parentName:"li"},".env")," file that includes at least the required environment variables. To see the full list of environment variables you can provide, check out the ",(0,i.kt)("a",{parentName:"li",href:"#configuration"},"Configuration")," section.")),(0,i.kt)("h5",{id:"running-the-subgraph-radio-inside-a-docker-container"},"Running the Subgraph Radio inside a Docker container"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-bash"},"docker-compose up -d\n")),(0,i.kt)("h3",{id:"building-subgraph-radio-locally"},"Building Subgraph Radio locally"),(0,i.kt)("p",null,"To have full control over the Subgraph Radio code and run it directly on your machine (without Docker) you can use this option."),(0,i.kt)("h4",{id:"prerequisites-1"},"Prerequisites"),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},"Clone this repo and ",(0,i.kt)("inlineCode",{parentName:"li"},"cd")," into it"),(0,i.kt)("li",{parentName:"ol"},"Make sure you have the following installed:")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://www.rust-lang.org/tools/install"},"Rust")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://go.dev/doc/install"},"Go")),(0,i.kt)("li",{parentName:"ul"},"Build tools (e.g. the ",(0,i.kt)("inlineCode",{parentName:"li"},"build-essentials")," package for Debian-based Linux distributions or ",(0,i.kt)("a",{parentName:"li",href:"https://mac.install.guide/commandlinetools/index.html"},"Xcode Command Line Tools")," for MacOS)"),(0,i.kt)("li",{parentName:"ul"},"C compiler (e.g. the ",(0,i.kt)("inlineCode",{parentName:"li"},"clang")," package for Debian-based Linux distribution or ",(0,i.kt)("a",{parentName:"li",href:"https://mac.install.guide/commandlinetools/index.html"},"Xcode Command Line Tools")," for MacOS)"),(0,i.kt)("li",{parentName:"ul"},"OpenSSL (e.g. the ",(0,i.kt)("inlineCode",{parentName:"li"},"libssl-dev")," package for Debian-based Linux distribution or ",(0,i.kt)("inlineCode",{parentName:"li"},"openssl")," for MacOS)"),(0,i.kt)("li",{parentName:"ul"},"PostreSQL libraries and headers (e.g. the ",(0,i.kt)("inlineCode",{parentName:"li"},"libpq-dev")," package for Debian-based Linux distribution or ",(0,i.kt)("inlineCode",{parentName:"li"},"postgresql")," for MacOS)")),(0,i.kt)("ol",{start:3},(0,i.kt)("li",{parentName:"ol"},"You have ",(0,i.kt)("strong",{parentName:"li"},"Graph Node")," syncing your indexer's on-chain allocations."),(0,i.kt)("li",{parentName:"ol"},"You have created a ",(0,i.kt)("inlineCode",{parentName:"li"},".env")," file that includes at least the required environment variables. To see the full list of environment variables you can provide, check out the ",(0,i.kt)("a",{parentName:"li",href:"#configuration"},"Configuration")," section.")),(0,i.kt)("h4",{id:"running-the-subgraph-radio-natively"},"Running the Subgraph Radio natively"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"cargo run --release\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/86e365b5.eaa7086f.js b/assets/js/86e365b5.eaa7086f.js new file mode 100644 index 00000000..4d77a078 --- /dev/null +++ b/assets/js/86e365b5.eaa7086f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9635],{3905:(e,t,a)=>{a.d(t,{Zo:()=>p,kt:()=>m});var n=a(7294);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var s=n.createContext({}),d=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},p=function(e){var t=d(e.components);return n.createElement(s.Provider,{value:t},e.children)},h="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},c=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),h=d(a),c=r,m=h["".concat(s,".").concat(c)]||h[c]||u[c]||i;return a?n.createElement(m,o(o({ref:t},p),{},{components:a})):n.createElement(m,o({ref:t},p))}));function m(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=a.length,o=new Array(i);o[0]=c;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[h]="string"==typeof e?e:r,o[1]=l;for(var d=2;d{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>d});var n=a(7462),r=(a(7294),a(3905));const i={sidebar_position:1},o="Subgraph Radio",l={unversionedId:"graphcast/radios/subgraph-radio",id:"graphcast/radios/subgraph-radio",title:"Subgraph Radio",description:"The source code for the Subgraph Radio is available on GitHub and Docker builds are automatically published as GitHub Packages. Subgraph Radio is also published as a crate on crates.io.",source:"@site/docs/graphcast/radios/subgraph-radio.md",sourceDirName:"graphcast/radios",slug:"/graphcast/radios/subgraph-radio",permalink:"/graphcast/radios/subgraph-radio",draft:!1,editUrl:"https://github.com/graphops/docs/edit/main/docs/graphcast/radios/subgraph-radio.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"gnSidebar",previous:{title:"Listener Radio",permalink:"/graphcast/radios/listener-radio"},next:{title:"One-shot CLI",permalink:"/graphcast/radios/one-shot"}},s={},d=[{value:"Introduction",id:"introduction",level:2},{value:"Basic Configuration",id:"basic-configuration",level:3},{value:"Run with Docker",id:"run-with-docker",level:3},{value:"(or) Run with docker-compose",id:"or-run-with-docker-compose",level:3},{value:"(or) Run as part of StakeSquid's docker-compose setup",id:"or-run-as-part-of-stakesquids-docker-compose-setup",level:3},{value:"(or) Run using a pre-built binary",id:"or-run-using-a-pre-built-binary",level:3},{value:"Advanced Configuration",id:"advanced-configuration",level:2},{value:"Configurations explained",id:"configurations-explained",level:3},{value:"COVERAGE (topic)",id:"coverage-topic",level:4},{value:"Identity validaiton",id:"identity-validaiton",level:4},{value:"Gossip protocol",id:"gossip-protocol",level:4},{value:"State management",id:"state-management",level:4},{value:"Subgraph Upgrade Pre-sync feature configuration variables",id:"subgraph-upgrade-pre-sync-feature-configuration-variables",level:4},{value:"Configuration options",id:"configuration-options",level:3},{value:"Using Environment Variables",id:"using-environment-variables",level:4},{value:"Using CLI arguments",id:"using-cli-arguments",level:4},{value:"Using a TOML/YAML file",id:"using-a-tomlyaml-file",level:4},{value:"Monitoring the Radio",id:"monitoring-the-radio",level:2},{value:"Notifications",id:"notifications",level:3},{value:"Prometheus & Grafana",id:"prometheus--grafana",level:3},{value:"HTTP Server",id:"http-server",level:2},{value:"How it works",id:"how-it-works",level:2},{value:"Fetching active allocations",id:"fetching-active-allocations",level:3},{value:"Gathering and comparing normalised POIs",id:"gathering-and-comparing-normalised-pois",level:3},{value:"Developing the Subgraph Radio",id:"developing-the-subgraph-radio",level:2},{value:"Building the image using the Dockerfile locally",id:"building-the-image-using-the-dockerfile-locally",level:4},{value:"Prerequisites",id:"prerequisites",level:5},{value:"Running the Subgraph Radio inside a Docker container",id:"running-the-subgraph-radio-inside-a-docker-container",level:5},{value:"Building Subgraph Radio locally",id:"building-subgraph-radio-locally",level:3},{value:"Prerequisites",id:"prerequisites-1",level:4},{value:"Running the Subgraph Radio natively",id:"running-the-subgraph-radio-natively",level:4}],p={toc:d},h="wrapper";function u(e){let{components:t,...a}=e;return(0,r.kt)(h,(0,n.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"subgraph-radio"},"Subgraph Radio"),(0,r.kt)("p",null,"The source code for the Subgraph Radio is available ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/subgraph-radio"},"on GitHub")," and Docker builds are automatically published as ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/subgraph-radio/pkgs/container/subgraph-radio"},"GitHub Packages"),". Subgraph Radio is also published as a crate ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io/crates/subgraph-radio"},"on crates.io"),"."),(0,r.kt)("h2",{id:"introduction"},"Introduction"),(0,r.kt)("p",null,"Subgraph Radio is an optional component of the Graph Protocol Indexer Stack. It uses the Graphcast Network to facilitate the exchange of Subgraph data and information among Indexers and other participants in the network."),(0,r.kt)("p",null,"An essential aspect of earning indexing rewards as an Indexer is the generation of valid Proof of Indexing hashes (POIs). These POIs provide evidence of the Indexer's possession of correct data. Submitting invalid POIs could lead to a ",(0,r.kt)("a",{parentName:"p",href:"https://thegraph.com/docs/en/network/indexing/#what-are-disputes-and-where-can-i-view-them"},"Dispute")," and possible slashing by the protocol. With Subgraph Radio's POI feature, Indexers gain confidence knowing that their POIs are continually cross-verified against those of other participating Indexers. Should there be a discrepancy in POIs, Subgraph Radio functions as an early warning system, alerting the Indexer within minutes."),(0,r.kt)("p",null,"All POIs generated through Subgraph Radio are public (normalized), meaning they are hashed with a ",(0,r.kt)("inlineCode",{parentName:"p"},"0x0")," Indexer Address and can be compared between Indexers. However, these public POIs are not valid for on-chain reward submission. Subgraph Radio groups and weighs public POIs according to the aggregate stake in GRT attesting to each. The normalized POI with the most substantial aggregate attesting stake is deemed canonical and used for comparisons with your local Indexer POIs."),(0,r.kt)("p",null,"For enhanced security, we recommend running Subgraph Radio with an independent Graphcast ID linked to your Indexer account. This Graphcast ID is an Ethereum account authorized to sign POI attestations on behalf of your Indexer. By default, Subgraph Radio validates messages received from any signer, that can be resolved to an Indexer address, regardless of whether or not they are registered on the Graphcast registry (though this behavior can be altered by setting the ID_VALIDATION config variable). Learn how to register a Graphcast ID ",(0,r.kt)("a",{parentName:"p",href:"https://docs.graphops.xyz/graphcast/sdk/registry#register-a-graphcast-id"},"here"),"."),(0,r.kt)("h3",{id:"basic-configuration"},"Basic Configuration"),(0,r.kt)("p",null,"The Subgraph Radio can be configured using environment variables, CLI arguments, as well as a TOML or YAML configuration file. Take a look at the ",(0,r.kt)("a",{parentName:"p",href:"#configuration-options"},"configuration options")," to learn more. In all cases, users will need to prepare the following configuration variables:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Name"),(0,r.kt)("th",{parentName:"tr",align:null},"Description and examples"),(0,r.kt)("th",{parentName:"tr",align:null}))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY")),(0,r.kt)("td",{parentName:"tr",align:null},"Private key of the Graphcast ID wallet or the Indexer Operator wallet (precendence over ",(0,r.kt)("inlineCode",{parentName:"td"},"MNEMONICS"),").",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")),(0,r.kt)("td",{parentName:"tr",align:null})),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"INDEXER_ADDRESS")),(0,r.kt)("td",{parentName:"tr",align:null},"Indexer address for Graphcast message verification, in all lowercase.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"0xabcdcabdabcdabcdcabdabcdabcdcabdabcdabcd")),(0,r.kt)("td",{parentName:"tr",align:null})),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"GRAPH_NODE_STATUS_ENDPOINT")),(0,r.kt)("td",{parentName:"tr",align:null},"URL to a Graph Node Indexing Status endpoint.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"http://index-node:8030/graphql")),(0,r.kt)("td",{parentName:"tr",align:null})),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"REGISTRY_SUBGRAPH")),(0,r.kt)("td",{parentName:"tr",align:null},"URL to the Graphcast Registry subgraph for your network. Check ",(0,r.kt)("a",{parentName:"td",href:"../sdk/registry#subgraph-apis"},"APIs")," for your preferred network"),(0,r.kt)("td",{parentName:"tr",align:null})),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"NETWORK_SUBGRAPH")),(0,r.kt)("td",{parentName:"tr",align:null},"URL to the Graph Network subgraph. Check ",(0,r.kt)("a",{parentName:"td",href:"../sdk/registry#subgraph-apis"},"APIs")," for your preferred network"),(0,r.kt)("td",{parentName:"tr",align:null})),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"GRAPHCAST_NETWORK")),(0,r.kt)("td",{parentName:"tr",align:null},"The Graphcast Messaging fleet and pubsub namespace to use.",(0,r.kt)("br",null),"Mainnet: ",(0,r.kt)("inlineCode",{parentName:"td"},"mainnet"),(0,r.kt)("br",null),"Goerli: ",(0,r.kt)("inlineCode",{parentName:"td"},"testnet")),(0,r.kt)("td",{parentName:"tr",align:null})))),(0,r.kt)("h3",{id:"run-with-docker"},"Run with Docker"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Pull the Subgraph Radio image")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"docker pull ghcr.io/graphops/subgraph-radio:latest\n")),(0,r.kt)("ol",{start:2},(0,r.kt)("li",{parentName:"ol"},"Run the image, providing the required environment variables. Here's a sample mainnet configuration:")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},'docker run \\\n -e GRAPHCAST_NETWORK="mainnet" \\\n -e REGISTRY_SUBGRAPH="https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-mainnet" \\\n -e NETWORK_SUBGRAPH="https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet" \\\n -e PRIVATE_KEY="PRIVATE_KEY" \\\n -e GRAPH_NODE_STATUS_ENDPOINT="http://graph-node:8030/graphql" \\\n -e RUST_LOG="warn,hyper=warn,graphcast_sdk=info,subgraph_radio=info" \\\n -e INDEXER_ADDRESS="INDEXER_ADDRESS" \\\n ghcr.io/graphops/subgraph-radio:latest\n')),(0,r.kt)("h3",{id:"or-run-with-docker-compose"},"(or) Run with docker-compose"),(0,r.kt)("p",null,"You can append this service definition to your ",(0,r.kt)("inlineCode",{parentName:"p"},"docker-compose")," manifest and customise the definitions:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-yaml"},'services:\n # ... your other service definitions\n subgraph-radio:\n image: ghcr.io/graphops/subgraph-radio:latest\n container_name: subgraph-radio\n restart: unless-stopped\n environment:\n GRAPHCAST_NETWORK: "mainnet"\n REGISTRY_SUBGRAPH: "https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-mainnet"\n NETWORK_SUBGRAPH: "https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet"\n PRIVATE_KEY: "PRIVATE_KEY"\n GRAPH_NODE_STATUS_ENDPOINT: "http://graph-node:8030/graphql"\n RUST_LOG: "warn,hyper=warn,graphcast_sdk=info,subgraph_radio=info"\n INDEXER_ADDRESS: "INDEXER_ADDRESS"\n logging:\n driver: local\n')),(0,r.kt)("h3",{id:"or-run-as-part-of-stakesquids-docker-compose-setup"},"(or) Run as part of ",(0,r.kt)("a",{parentName:"h3",href:"https://github.com/StakeSquid"},"StakeSquid"),"'s docker-compose setup"),(0,r.kt)("p",null,"Subgraph Radio is included as an optional component in both the ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/StakeSquid/graphprotocol-mainnet-docker"},"mainnet")," and ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/StakeSquid/graphprotocol-testnet-docker"},"testnet")," versions of StakeSquid's guide."),(0,r.kt)("h3",{id:"or-run-using-a-pre-built-binary"},"(or) Run using a pre-built binary"),(0,r.kt)("p",null,"We also provide pre-built binaries for Ubuntu and MacOS, which you can find in the ",(0,r.kt)("inlineCode",{parentName:"p"},"Assets")," section on each release in the ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/subgraph-radio/releases"},"releases page")," on Github. Simply download the binary, make it executable (",(0,r.kt)("inlineCode",{parentName:"p"},"chmod a+x ./subgraph-radio-{TAG}-{SYSTEM}"),") and then run it (using ",(0,r.kt)("inlineCode",{parentName:"p"},"./subgraph-radio-{TAG}-{SYSTEM}"),")."),(0,r.kt)("h2",{id:"advanced-configuration"},"Advanced Configuration"),(0,r.kt)("p",null,"In the configuration table below is the full list of environment variables you can set, along with example values."),(0,r.kt)("p",null,"See ",(0,r.kt)("a",{parentName:"p",href:"#basic-configuration"},"Basic Configuration")," above. The following environment variables are optional:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Name (Optional variables)"),(0,r.kt)("th",{parentName:"tr",align:null},"Description and examples"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"MNEMONIC")),(0,r.kt)("td",{parentName:"tr",align:null},"Mnemonic to the Graphcast ID wallet or the Indexer Operator wallet (first address of the wallet is used; Only one of ",(0,r.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY")," or ",(0,r.kt)("inlineCode",{parentName:"td"},"MNEMONIC")," is needed). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"claptrap armchair violin..."))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"COLLECT_MESSAGE_DURATION")),(0,r.kt)("td",{parentName:"tr",align:null},"Seconds that the Subgraph Radio will wait to collect remote POI attestations before making a comparison with the local POI. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"120")," for 2 minutes.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"COVERAGE")),(0,r.kt)("td",{parentName:"tr",align:null},'Toggle for topic coverage level. Possible values: "comprehensive", "on-chain", "minimal", "none". Default is set to "comprehensive" coverage.')),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"TOPICS")),(0,r.kt)("td",{parentName:"tr",align:null},"Comma separated static list of content topics (subgraphs) to subscribe to. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz,QmUwCFhXM3f6qH9Ls9Y6gDNURBH7mxsn6JcectgxAz6CwU,QmQ1Lyh3U6YgVP6YX1RgRz6c8GmKkEpokLwPvEtJx6cF1y"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_HOST")),(0,r.kt)("td",{parentName:"tr",align:null},"Interface onto which to bind the bundled Waku node. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"P2P port on which the bundled Waku node will operate. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"60000"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_NODE_KEY")),(0,r.kt)("td",{parentName:"tr",align:null},"Static Waku Node Key.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"BOOT_NODE_ADDRESSES")),(0,r.kt)("td",{parentName:"tr",align:null},"Peer addresses to use as Waku boot nodes. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},'"addr1, addr2, addr3"'))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SLACK_TOKEN")),(0,r.kt)("td",{parentName:"tr",align:null},"Slack Token to use for notifications. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"xoxp-0123456789-0123456789-0123456789-0123456789"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"TELEGRAM_TOKEN")),(0,r.kt)("td",{parentName:"tr",align:null},"Telegram Bot Token to use for notifications. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"TELEGRAM_CHAT_ID")),(0,r.kt)("td",{parentName:"tr",align:null},"The ID of the Telegram chat to send messages to. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"-1001234567890"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SLACK_CHANNEL")),(0,r.kt)("td",{parentName:"tr",align:null},"Name of Slack channel to send messages to (has to be a public channel). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"poir-notifications"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_LOG_LEVEL")),(0,r.kt)("td",{parentName:"tr",align:null},"Waku node logging configuration. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"INFO")," (is also the default)")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"RUST_LOG")),(0,r.kt)("td",{parentName:"tr",align:null},"Rust tracing configuration. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"graphcast_sdk=debug,subgraph_radio=debug"),", defaults to ",(0,r.kt)("inlineCode",{parentName:"td"},"info")," for everything")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"DISCORD_WEBHOOK")),(0,r.kt)("td",{parentName:"tr",align:null},"Discord webhook URL for notifications. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"https://discord.com/api/webhooks/123456789012345678/AbCDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"METRICS_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose Prometheus metrics on this (off by default). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"3001"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"METRICS_HOST")),(0,r.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose Prometheus metrics on this (off by default). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SERVER_HOST")),(0,r.kt)("td",{parentName:"tr",align:null},"If ",(0,r.kt)("inlineCode",{parentName:"td"},"SERVER_PORT")," is set, the Radio will expose an API service on the given host and port. Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SERVER_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose an API service on the given port (off by default). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"8080"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"LOG_FORMAT")),(0,r.kt)("td",{parentName:"tr",align:null},"Options: ",(0,r.kt)("inlineCode",{parentName:"td"},"pretty")," - verbose and human readable; ",(0,r.kt)("inlineCode",{parentName:"td"},"json")," - not verbose and parsable; ",(0,r.kt)("inlineCode",{parentName:"td"},"compact")," - not verbose and not parsable; ",(0,r.kt)("inlineCode",{parentName:"td"},"full")," - verbose and not parsible. Default value: ",(0,r.kt)("inlineCode",{parentName:"td"},"pretty"),".")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"PERSISTENCE_FILE_PATH")),(0,r.kt)("td",{parentName:"tr",align:null},"Relative path. If set, the Radio will periodically store states of the program to the file in json format (off by default).")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"DISCV5_ENRS")),(0,r.kt)("td",{parentName:"tr",align:null},"Comma separated ENRs for Waku Discv5 bootstrapping. Defaults to empty list.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"DISCV5_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"Discoverable UDP port. Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"9000"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"ID_VALIDATION")),(0,r.kt)("td",{parentName:"tr",align:null},"Defines the level of validation for message signers used during radio operation. Options include: ",(0,r.kt)("inlineCode",{parentName:"td"},"no-check"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"valid-address"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"graphcast-registered"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"graph-network-account"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"registered-indexer"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"indexer"),". Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"indexer"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"INDEXER_MANAGEMENT_SERVER_ENDPOINT")),(0,r.kt)("td",{parentName:"tr",align:null},"URL to the Indexer management server of Indexer Agent. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"http://localhost:18000"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"AUTO_UPGRADE")),(0,r.kt)("td",{parentName:"tr",align:null},'Toggle for the types of subgraphs for which the Radio will send offchain syncing commands to the indexer management server. Default to upgrade all syncing deployments. Possible values: "comprehensive", "on-chain", "minimal", "none". Default is set to "comprehensive" coverage.')))),(0,r.kt)("h3",{id:"configurations-explained"},"Configurations explained"),(0,r.kt)("h4",{id:"coverage-topic"},"COVERAGE (topic)"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"COVERAGE")," is used to specify the topic coverage level. It controls the range of topics (subgraph ipfs hashes) the Indexer subscribes to in order to process data and participate in the network."),(0,r.kt)("p",null,"There are three coverage levels available:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"comprehensive"),": Subscribe to on-chain topics, user-defined static topics, and subgraph deployments syncing on graph node. This level is useful for Indexers who want to compare public POIs for all deployments syncing on their graph node even if they don't have an active allocations open (their stake will not be taken into account in attestation)."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"on-chain"),": Subscribe to on-chain topics and user-defined static topics. This is the default coverage level and is suitable for indexers who only want to compare data for deployments with active allocations."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"minimal"),": Only subscribe to user-defined static topics. This level is for Indexers who want to limit their participation to specific topics of interest.")),(0,r.kt)("h4",{id:"identity-validaiton"},"Identity validaiton"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"ID_VALIDATION")," is used to define level of validation for message signers used during radio operation. We recommend ",(0,r.kt)("inlineCode",{parentName:"p"},"registered-indexer")," for most strict identity validation, while ",(0,r.kt)("inlineCode",{parentName:"p"},"indexer")," is a viable option for those who want to use the network before considering Grapchast ID registration. You can choose a sender identity validation mechanism for your radio, based on your use case and security preferences."),(0,r.kt)("p",null,"Available Options:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"no-check"),": Does not perform check on the message signature and does not verify the signer. All messages should pass the sender check."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"valid-address"),": Requires the signer to be a valid Ethereum address. Messages should be traceable to an Ethers wallet."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"graphcast-registered"),": Requires the signer to be registered on the Graphcast Registry."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"graph-network-account"),": signer must be a Graph account."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"registered-indexer"),": signer must be registered at Graphcast Registry and correspond to an Indexer satisfying the indexer minimum stake requirement."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"indexer"),": signer must be registered at Graphcast Registry or is a Graph Account, and correspond to an Indexer satisfying the indexer minimum stake requirement.")),(0,r.kt)("h4",{id:"gossip-protocol"},"Gossip protocol"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"WAKU_HOST")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"WAKU_PORT")," specify where the bundled Waku node runs. If you want to run multiple Radios, or multiple instances of the same Radio, you should run them on different ports."),(0,r.kt)("p",null,"If you want to customize the log level, you can toggle ",(0,r.kt)("inlineCode",{parentName:"p"},"RUST_LOG")," environment variable. Here's an example configuration to get more verbose logging:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},'RUST_LOG="warn,hyper=warn,graphcast_sdk=debug,subgraph_radio=debug"\n')),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Discv5")," is an ambient node discovery network for establishing a decentralized network of interconnected Graphcast Radios. Discv5, when used in Graphcast Radios, serves as a dedicated peer-to-peer discovery protocol that empowers Radios to form an efficient, decentralized network. Without Discv5, the traffic within the Graphcast network would largely rely on centrally hosted boot nodes, leading to a less distributed architecture. However, with Discv5, Radios are capable of directly routing messages among themselves, significantly enhancing network decentralization and reducing reliance on the central nodes. If you want to learn more about Discv5, check out the ",(0,r.kt)("a",{parentName:"p",href:"https://rfc.vac.dev/spec/33/"},"official spec"),"."),(0,r.kt)("h4",{id:"state-management"},"State management"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"PERSISTENCE_FILE_PATH")," configuration variable allows the Radio to maintain operational continuity across sessions. When the file path is set, it triggers the Radio to periodically store its state, including local attestations, remote messages and POI comparison results in a JSON-formatted file at the specified path. This facilitates seamless session transitions and minimizes data loss. In the event of a system disruption, the state can be reloaded from this file, ensuring the Radio can resume operation effectively."),(0,r.kt)("h4",{id:"subgraph-upgrade-pre-sync-feature-configuration-variables"},"Subgraph Upgrade Pre-sync feature configuration variables"),(0,r.kt)("p",null,"The subgraph upgrade pre-sync feature provides a way for Subgraph Developers to signal when they plan on releasing a new subgraph version, thereby allowing Indexers to start syncing the subgraph in advance. If the Radio operator has set up the notification system, they will get notified whenever a new subgraph upgrade intent message is received."),(0,r.kt)("p",null,"If the ",(0,r.kt)("inlineCode",{parentName:"p"},"INDEXER_MANAGEMEN_SERVER_ENDPOINT")," configuration variable has been set, the Radio will send a request to the Indexer Agent to start offchain syncing the new Subgraph deployment."),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"AUTO_UPGRADE")," variable can be toggled to change the coverage level of subgraphs for which the Radio will send offchain syncing commands to the indexer management server."),(0,r.kt)("h3",{id:"configuration-options"},"Configuration options"),(0,r.kt)("p",null,"To configure Subgraph Radio, you can use the following methods:"),(0,r.kt)("h4",{id:"using-environment-variables"},"Using Environment Variables"),(0,r.kt)("p",null,"Example .env file:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},'PRIVATE_KEY="a2b3c1d4e5f6890e7f6g5h4i3j2k1l0m"\nGRAPH_NODE_STATUS_ENDPOINT="http://127.0.0.42:8030/graphql"\nREGISTRY_SUBGRAPH="https://api.thegraph.com/subgraphs/name/randomuser/graphcast-registry-mainnet"\nNETWORK_SUBGRAPH="https://api.thegraph.com/subgraphs/name/graphprotocol/graph-mainnet"\nGRAPHCAST_NETWORK=mainnet\nINDEXER_ADDRESS="0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"\n')),(0,r.kt)("h4",{id:"using-cli-arguments"},"Using CLI arguments"),(0,r.kt)("p",null,"Pass the configuration options directly as command-line arguments."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},'docker run ghcr.io/graphops/subgraph-radio \\\n --private-key "a2b3c1d4e5f6890e7f6g5h4i3j2k1l0m" \\\n --graph-node-status-endpoint "http://127.0.0.42:8030/graphql" \\\n --registry-subgraph "https://api.thegraph.com/subgraphs/name/randomuser/graphcast-registry-mainnet" \\\n --network-subgraph "https://api.thegraph.com/subgraphs/name/graphprotocol/graph-mainnet" \\\n --graphcast-network mainnet \\\n --indexer-address "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"\n')),(0,r.kt)("h4",{id:"using-a-tomlyaml-file"},"Using a TOML/YAML file"),(0,r.kt)("p",null,"Example TOML configuration file (config.toml):"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-toml"},"[graph_stack]\ngraph_node_status_endpoint = 'http://127.0.0.42:8030/graphql'\nindexer_address = '0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6'\nregistry_subgraph = 'https://api.thegraph.com/subgraphs/name/randomuser/graphcast-registry-mainnet'\nnetwork_subgraph = 'https://api.thegraph.com/subgraphs/name/graphprotocol/graph-mainnet'\nprivate_key = 'a2b3c1d4e5f6890e7f6g5h4i3j2k1l0m'\n")),(0,r.kt)("p",null,"Then you just need to have the ",(0,r.kt)("inlineCode",{parentName:"p"},"CONFIG_FILE")," set, either as an env variable - ",(0,r.kt)("inlineCode",{parentName:"p"},"CONFIG_FILE=path/to/config.toml")," or passed as a CLI arg - ",(0,r.kt)("inlineCode",{parentName:"p"},"--config-file path/to/config.toml"),"."),(0,r.kt)("p",null,"Example YAML configuration file (config.yaml):"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-yaml"},'graph_stack:\n graph_node_status_endpoint: "http://127.0.0.42:8030/graphql"\n indexer_address: "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"\n registry_subgraph: "https://api.thegraph.com/subgraphs/name/randomuser/graphcast-registry-mainnet"\n network_subgraph: "https://api.thegraph.com/subgraphs/name/graphprotocol/graph-mainnet"\n private_key: "a2b3c1d4e5f6890e7f6g5h4i3j2k1l0m"\n')),(0,r.kt)("p",null,"Then you just need to have the ",(0,r.kt)("inlineCode",{parentName:"p"},"CONFIG_FILE")," set, either as an env variable - ",(0,r.kt)("inlineCode",{parentName:"p"},"CONFIG_FILE=path/to/config.yaml")," or passed as a CLI arg - ",(0,r.kt)("inlineCode",{parentName:"p"},"--config-file path/to/config.yaml"),"."),(0,r.kt)("p",null,"We also have an ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/subgraph-radio/blob/dev/template.toml"},"extensive configuration file template")," in the repo."),(0,r.kt)("h2",{id:"monitoring-the-radio"},"Monitoring the Radio"),(0,r.kt)("h3",{id:"notifications"},"Notifications"),(0,r.kt)("p",null,"If the Radio operator has set up a Slack, Discord and/or Telegram bot integration and the Radio finds a POI mismatch, it sends alerts to the designated channels. The operator can also inspect the logs to see if the Radio is functioning properly, if it's sending and receiving messages, if it's comparing normalised POIs, if there is a found POI mismatch, etc."),(0,r.kt)("h3",{id:"prometheus--grafana"},"Prometheus & Grafana"),(0,r.kt)("p",null,"The Subgraph Radio exposes metrics that can then be scraped by a Prometheus server and displayed in Grafana. In order to use them you have to have a local Prometheus server running and scraping metrics on the provided port. You can specify the metrics host and port by using the environment variables ",(0,r.kt)("inlineCode",{parentName:"p"},"METRICS_PORT")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"METRICS_HOST"),". We also provide a ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/subgraph-radio/blob/dev/grafana.json"},"Grafana dashboard config JSON file")," which you can use to visualise the metrics in Grafana."),(0,r.kt)("h2",{id:"http-server"},"HTTP Server"),(0,r.kt)("p",null,"The Radio spins up an HTTP server with a GraphQL API when ",(0,r.kt)("inlineCode",{parentName:"p"},"SERVER_HOST")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"SERVER_PORT")," environment variables are set. The supported routes are:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"/health")," for health status"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"/api/v1/graphql")," for GET and POST requests with GraphQL playground interface")),(0,r.kt)("p",null,"The GraphQL API now includes several advanced queries:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"radioPayloadMessages")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"localAttestations")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"comparisonResults")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"comparisonRatio"))),(0,r.kt)("p",null,"Below are some example queries:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-graphql"},'query {\n radioPayloadMessages {\n identifier\n nonce\n signature\n graphAccount\n payload {\n identifier\n content\n }\n }\n localAttestations {\n deployment\n blockNumber\n attestation {\n ppoi\n }\n }\n comparisonResults(identifier: "Qm...") {\n deployment\n blockNumber\n resultType\n localAttestation {\n ppoi\n }\n attestations {\n senders\n stakeWeight\n ppoi\n }\n }\n comparisonRatio {\n deployment\n blockNumber\n stakeRatio\n }\n}\n')),(0,r.kt)("p",null,"You can customize the returned data from the ",(0,r.kt)("inlineCode",{parentName:"p"},"comparisonRatio")," query by providing optional arguments - ",(0,r.kt)("inlineCode",{parentName:"p"},"deployment"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"block")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"resultType"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-graphql"},'query {\n comparisonRatio(deployment: "Qm...", block: 17887350, resultType: MATCH) {\n deployment\n blockNumber\n stakeRatio\n }\n}\n')),(0,r.kt)("p",null,"In this example, the ",(0,r.kt)("inlineCode",{parentName:"p"},"stakeRatio"),' query will return the stake ratios only for attestations from deployment "Qm..." and block number 17887350, and only for the specified result type.'),(0,r.kt)("p",null,"Note: The ",(0,r.kt)("inlineCode",{parentName:"p"},"result_type")," field of the filter corresponds to the ",(0,r.kt)("inlineCode",{parentName:"p"},"resultType")," field in the ",(0,r.kt)("inlineCode",{parentName:"p"},"comparisonResults")," query. This field represents the type of comparison result."),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"comparisonRatio")," provides an overview of the consensus status of the attestations from remote messages. It gives a ratio string that signifies the number of indexers with the same public POI as the local Radio. The results are presented as ",(0,r.kt)("inlineCode",{parentName:"p"},"x/y!/z")," where:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"x"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"y"),", and ",(0,r.kt)("inlineCode",{parentName:"li"},"z")," are sorted by descending stake weights"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"!")," indicates the entry that corresponds to the local result.")),(0,r.kt)("p",null,"For example,",(0,r.kt)("inlineCode",{parentName:"p"}," 2/0!")," means there are two indexers attesting with a higher sum of stake weight and no other indexer shares the same public POIs as the local Radio. ",(0,r.kt)("inlineCode",{parentName:"p"},"8!")," means there are eight other indexers agreeing with the local Radio."),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"stakeRatio")," orders the attestations by stake weight, then computes the ratio of unique senders."),(0,r.kt)("p",null,"These queries provide a clear aggregation of the attestations from remote messages, giving a concise understanding of the Radio's state. The optional filters - deployment, block, and filter - can be used to refine the results."),(0,r.kt)("h2",{id:"how-it-works"},"How it works"),(0,r.kt)("h3",{id:"fetching-active-allocations"},"Fetching active allocations"),(0,r.kt)("p",null,"The Subgraph Radio is responsible for reading active allocations of the Radio operator's corresponding Indexer. It periodically polls the Graph Node for new blocks on all relevant networks and constructs Graphcast topics on each allocation identified by subgraph deployment IPFS hash."),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"The relevant networks are those corresponding to the subgraphs that have active allocations.")),(0,r.kt)("p",null,"The Radio fetches new active allocations at a regular interval to ensure that it is processing the latest information. Chainheads for these networks are updated with data from the Graph Node, and the Radio ensures that it is always using the latest chainhead when processing messages."),(0,r.kt)("mermaid",{value:"sequenceDiagram\n participant Network Subgraph\n participant Graph Node\n participant Subgraph Radio\n participant Graphcast Network\n actor Human\n participant Indexer Management Server\n loop Track allocated deployments\n Subgraph Radio->>+Network Subgraph: Get latest allocated deployments\n Network Subgraph->>-Subgraph Radio: Return allocated deployments\n loop Monitor allocated deployments and chain heads\n Subgraph Radio->>+Graph Node: Get indexing statuses for allocated deployments\n Graph Node->>-Subgraph Radio: Return matching indexing statuses\n activate Subgraph Radio\n Subgraph Radio->>Subgraph Radio: Update chain heads\n deactivate Subgraph Radio\n loop For each deployment that we are tracking\n opt If deployment reached trigger block is healthy\n Subgraph Radio->>+Graph Node: Fetch POI for deployment\n Graph Node->>-Subgraph Radio: Normalized POI\n activate Subgraph Radio\n Subgraph Radio->>Subgraph Radio: Generate signed POI Attestation\n deactivate Subgraph Radio\n Subgraph Radio--\x3e>Graphcast Network: Broadcast POI Attestation to Graphcast Network\n end\n opt If stored remote attestations and collect message duration passed\n activate Subgraph Radio\n Subgraph Radio->>Subgraph Radio: Compute consensus remote POI\n deactivate Subgraph Radio\n opt If local POI mismatches consensus remote POI\n Subgraph Radio--\x3e>Human: Send POI divergence warning notification\n end\n end\n opt If VersionUpgradeMessage is received\n Graphcast Network--\x3e>Subgraph Radio: VersionUpgradeMessage\n activate Subgraph Radio\n Subgraph Radio--\x3e>Human: Send Version Upgrade notification\n opt If Indexer Management Server endpoint provided\n Subgraph Radio->>Indexer Management Server: Sends offchain sync request for new deployment hash\n end\n deactivate Subgraph Radio\n end\n end\n end\n end"}),(0,r.kt)("h3",{id:"gathering-and-comparing-normalised-pois"},"Gathering and comparing normalised POIs"),(0,r.kt)("p",null,"At a given interval, the Radio fetches the normalised POI for each deployment. This interval is defined in blocks different for each network. It then saves those public POIs, and as other Indexers running the Radio start doing the same, messages start propagating through the network. The Radio saves each message and processes them on a given interval."),(0,r.kt)("p",null,"The messages include a nonce (UNIX timestamp), block number, signature (used to derive the sender's on-chain Indexer address) and network. Before saving an entry to the map, the Radio operator verifies through the Graph network subgraph for the sender's on-chain identity and amount of tokens staked, which is used during comparisons later on."),(0,r.kt)("mermaid",{value:"flowchart LR\n a[Fetch deployment status] --\x3e b[If healthy & synced]\n b --\x3e|No| eee{End}\n b --\x3e|Yes| c[If reached trigger block]\n c --\x3e|No| eee\n c --\x3e|Yes| d[Fetch POI for deployment]\n d --\x3e|Broadcast| n(Graphcast\\nNetwork)\n n --\x3e|Receive remote POI| o[Other Indexers]\n n --\x3e x{End}"}),(0,r.kt)("p",null,"At another interval, the Radio compares the local public POIs with the collected remote ones. The remote POIs are sorted so that for each subgraph (on each block), the POI that is backed by the most on-chain stake is selected. This means that the combined stake of all Indexers that attested to it is considered, not just the highest staking Indexer. The top POI is then compared with the local POIs for that subgraph at that block to determine consensus."),(0,r.kt)("p",null,"If there is a mismatch and if the Radio operator has set up a Slack, Discord and/or Telegram bot integration, the Radio will send alerts to the designated channels."),(0,r.kt)("p",null,"After a successful comparison, the attestations that have been checked are removed from the store."),(0,r.kt)("mermaid",{value:"flowchart LR\n q[Fetch deployment status] --\x3e g[Has collection window expired?]\n g --\x3e|Yes| t[Compute consensus remote POI]\n g --\x3e|No| p{End}\n c --\x3e|Aggregate| t\n a[Receive POI attestations] --\x3e b[Has collection window expired?]\n b --\x3e|Yes| eee{End}\n b --\x3e|No| c[Store remote attestation\\nfor deployment]\n t --\x3e l[Does local POI match remote consensus POI?]\n l --\x3e|No| i[Send notification]\n l --\x3e|Yes| d{End}"}),(0,r.kt)("h2",{id:"developing-the-subgraph-radio"},"Developing the Subgraph Radio"),(0,r.kt)("h4",{id:"building-the-image-using-the-dockerfile-locally"},"Building the image using the Dockerfile locally"),(0,r.kt)("p",null,"If you want to make any changes to the Subgraph Radio codebase, you can use this option."),(0,r.kt)("h5",{id:"prerequisites"},"Prerequisites"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Clone this repo and ",(0,r.kt)("inlineCode",{parentName:"li"},"cd")," into it"),(0,r.kt)("li",{parentName:"ol"},"Create a ",(0,r.kt)("inlineCode",{parentName:"li"},".env")," file that includes at least the required environment variables. To see the full list of environment variables you can provide, check out the ",(0,r.kt)("a",{parentName:"li",href:"#configuration"},"Configuration")," section.")),(0,r.kt)("h5",{id:"running-the-subgraph-radio-inside-a-docker-container"},"Running the Subgraph Radio inside a Docker container"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"docker-compose up -d\n")),(0,r.kt)("h3",{id:"building-subgraph-radio-locally"},"Building Subgraph Radio locally"),(0,r.kt)("p",null,"To have full control over the Subgraph Radio code and run it directly on your machine (without Docker) you can use this option."),(0,r.kt)("h4",{id:"prerequisites-1"},"Prerequisites"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Clone this repo and ",(0,r.kt)("inlineCode",{parentName:"li"},"cd")," into it"),(0,r.kt)("li",{parentName:"ol"},"Make sure you have the following installed:")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://www.rust-lang.org/tools/install"},"Rust")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://go.dev/doc/install"},"Go")),(0,r.kt)("li",{parentName:"ul"},"Build tools (e.g. the ",(0,r.kt)("inlineCode",{parentName:"li"},"build-essentials")," package for Debian-based Linux distributions or ",(0,r.kt)("a",{parentName:"li",href:"https://mac.install.guide/commandlinetools/index.html"},"Xcode Command Line Tools")," for MacOS)"),(0,r.kt)("li",{parentName:"ul"},"C compiler (e.g. the ",(0,r.kt)("inlineCode",{parentName:"li"},"clang")," package for Debian-based Linux distribution or ",(0,r.kt)("a",{parentName:"li",href:"https://mac.install.guide/commandlinetools/index.html"},"Xcode Command Line Tools")," for MacOS)"),(0,r.kt)("li",{parentName:"ul"},"OpenSSL (e.g. the ",(0,r.kt)("inlineCode",{parentName:"li"},"libssl-dev")," package for Debian-based Linux distribution or ",(0,r.kt)("inlineCode",{parentName:"li"},"openssl")," for MacOS)"),(0,r.kt)("li",{parentName:"ul"},"PostreSQL libraries and headers (e.g. the ",(0,r.kt)("inlineCode",{parentName:"li"},"libpq-dev")," package for Debian-based Linux distribution or ",(0,r.kt)("inlineCode",{parentName:"li"},"postgresql")," for MacOS)")),(0,r.kt)("ol",{start:3},(0,r.kt)("li",{parentName:"ol"},"You have ",(0,r.kt)("strong",{parentName:"li"},"Graph Node")," syncing your indexer's on-chain allocations."),(0,r.kt)("li",{parentName:"ol"},"You have created a ",(0,r.kt)("inlineCode",{parentName:"li"},".env")," file that includes at least the required environment variables. To see the full list of environment variables you can provide, check out the ",(0,r.kt)("a",{parentName:"li",href:"#configuration"},"Configuration")," section.")),(0,r.kt)("h4",{id:"running-the-subgraph-radio-natively"},"Running the Subgraph Radio natively"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"cargo run --release\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.017dd9f7.js b/assets/js/935f2afb.017dd9f7.js deleted file mode 100644 index a5e85df4..00000000 --- a/assets/js/935f2afb.017dd9f7.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"launchpadSidebar":[{"type":"link","label":"Introduction","href":"/launchpad/intro","docId":"launchpad/intro"},{"type":"link","label":"Prerequisites","href":"/launchpad/prerequisites","docId":"launchpad/prerequisites"},{"type":"link","label":"Quick Start","href":"/launchpad/quick-start","docId":"launchpad/quick-start"},{"type":"link","label":"Design Principles","href":"/launchpad/design-principles","docId":"launchpad/design-principles"},{"type":"link","label":"Client Side Tooling","href":"/launchpad/client-side-tooling","docId":"launchpad/client-side-tooling"},{"type":"link","label":"Server Side Stack","href":"/launchpad/server-side-stack","docId":"launchpad/server-side-stack"},{"type":"link","label":"Frequently Asked Questions","href":"/launchpad/faq","docId":"launchpad/faq"},{"type":"link","label":"Other Resources","href":"/launchpad/other-resources","docId":"launchpad/other-resources"},{"type":"category","label":"Guides","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"Arbitrum Archive Mainnet Node Guide","href":"/launchpad/guides/arbitrum-archive-kubernetes-guide","docId":"launchpad/guides/arbitrum-archive-kubernetes-guide"},{"type":"link","label":"Avalanche Archive Mainnet Node Guide","href":"/launchpad/guides/avalanche-archive-kubernetes","docId":"launchpad/guides/avalanche-archive-kubernetes"},{"type":"link","label":"Celo Archive Mainnet Node Guide","href":"/launchpad/guides/celo-archive-kubernetes-guide","docId":"launchpad/guides/celo-archive-kubernetes-guide"},{"type":"link","label":"Goerli Indexer Guide","href":"/launchpad/guides/goerli-indexer-guide","docId":"launchpad/guides/goerli-indexer-guide"},{"type":"link","label":"FCOS Installation","href":"/launchpad/guides/install-fcos","docId":"launchpad/guides/install-fcos"}]},{"type":"category","label":"Advanced Topics","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"Considerations for Kubernetes installation using FCOS","href":"/launchpad/advanced/advanced-kubernetes","docId":"launchpad/advanced/advanced-kubernetes"}]}],"gnSidebar":[{"type":"link","label":"Introduction","href":"/graphcast/intro","docId":"graphcast/intro"},{"type":"link","label":"Design Principles","href":"/graphcast/design-principles","docId":"graphcast/design-principles"},{"type":"category","label":"SDK","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"Introduction","href":"/graphcast/sdk/intro","docId":"graphcast/sdk/intro"},{"type":"link","label":"Radio Development","href":"/graphcast/sdk/radio-dev","docId":"graphcast/sdk/radio-dev"},{"type":"link","label":"Registry","href":"/graphcast/sdk/registry","docId":"graphcast/sdk/registry"}]},{"type":"category","label":"Radios","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"Listener Radio","href":"/graphcast/radios/listener-radio","docId":"graphcast/radios/listener-radio"},{"type":"link","label":"Subgraph Radio","href":"/graphcast/radios/subgraph-radio","docId":"graphcast/radios/subgraph-radio"},{"type":"link","label":"One-shot CLI","href":"/graphcast/radios/one-shot","docId":"graphcast/radios/one-shot"}]}],"mipsSidebar":[{"type":"link","label":"Introduction","href":"/mips-resources/intro","docId":"mips-resources/intro"},{"type":"link","label":"MIPs FAQs","href":"/mips-resources/mips-faq","docId":"mips-resources/mips-faq"}]},"docs":{"graphcast/design-principles":{"id":"graphcast/design-principles","title":"Design Principles","description":"There are two main components of Graphcast","sidebar":"gnSidebar"},"graphcast/intro":{"id":"graphcast/intro","title":"Introduction","description":"Is there something you\'d like to learn from or share with your fellow Indexers in an automated manner, but it\'s too much hassle or costs too much gas?","sidebar":"gnSidebar"},"graphcast/radios/listener-radio":{"id":"graphcast/radios/listener-radio","title":"Listener Radio","description":"The source code for Listener Radio is available on GitHub and Docker builds are automatically published as GitHub Packages.","sidebar":"gnSidebar"},"graphcast/radios/one-shot":{"id":"graphcast/radios/one-shot","title":"One-shot CLI","description":"The source code for the one-shot CLI is available on GitHub as a member of the Subgraph Radio workspace.","sidebar":"gnSidebar"},"graphcast/radios/subgraph-radio":{"id":"graphcast/radios/subgraph-radio","title":"Subgraph Radio","description":"The source code for the Subgraph Radio is available on GitHub and Docker builds are automatically published as GitHub Packages. Subgraph Radio is also published as a crate on crates.io.","sidebar":"gnSidebar"},"graphcast/sdk/intro":{"id":"graphcast/sdk/intro","title":"Introduction","description":"Graphcast SDK is a decentralized, distributed peer-to-peer (P2P) communication tool that enables users across the network to exchange information in real-time. It is designed to overcome the high cost of signaling or coordination between blockchain participants by enabling off-chain communication (gossip/cheap talk). This is particularly useful for applications where real-time communication is essential but the cost of on-chain transactions is prohibitive.","sidebar":"gnSidebar"},"graphcast/sdk/radio-dev":{"id":"graphcast/sdk/radio-dev","title":"Radio Development","description":"Do you want to build robust, peer-to-peer messaging apps that automatically exchange valuable data with other Indexers in real time? Do you have an idea for what data could be useful to share that could lead to greater communication efficiency in The Graph network as a whole? Then you want to build a Radio on top of the Graphcast network.","sidebar":"gnSidebar"},"graphcast/sdk/registry":{"id":"graphcast/sdk/registry","title":"Registry","description":"The Graphcast Registry contracts allow an address to set a GraphcastID by calling setGraphcastID(indexeraddress, graphcastIDaddress) as either an Indexer or an Indexer operator, or calling setGraphcastID(graphcastID_address) as the Indexer address. The relationship between an Indexer address to its GraphcastID is limited to 1:1, and cannot be set to itself. This restriction provides consistency and security for the Indexer identity to operate on Graphcast as one GraphcastID operating across Radio applications. To learn more about the registry, you can check out the Github repository.","sidebar":"gnSidebar"},"launchpad/advanced/advanced-kubernetes":{"id":"launchpad/advanced/advanced-kubernetes","title":"Considerations for Kubernetes installation using FCOS","description":"This guide provides a general walkthrough for installing Kubernetes using Fedora CoreOS (FCOS) as the base operating system.","sidebar":"launchpadSidebar"},"launchpad/client-side-tooling":{"id":"launchpad/client-side-tooling","title":"Client Side Tooling","description":"Launchpad comes with an opinionated set of tools on your local machine, layered over one another to provide a declarative workflow to manage your cluster software stack.","sidebar":"launchpadSidebar"},"launchpad/design-principles":{"id":"launchpad/design-principles","title":"Design Principles","description":"Design Principles","sidebar":"launchpadSidebar"},"launchpad/faq":{"id":"launchpad/faq","title":"Frequently Asked Questions","description":"Do I need a server for launchpad-starter?","sidebar":"launchpadSidebar"},"launchpad/guides/arbitrum-archive-kubernetes-guide":{"id":"launchpad/guides/arbitrum-archive-kubernetes-guide","title":"Arbitrum Archive Mainnet Node Guide","description":"This Quick Start guide has not yet been updated for Launchpad V2.","sidebar":"launchpadSidebar"},"launchpad/guides/avalanche-archive-kubernetes":{"id":"launchpad/guides/avalanche-archive-kubernetes","title":"Avalanche Archive Mainnet Node Guide","description":"This Quick Start guide has not yet been updated for Launchpad V2.","sidebar":"launchpadSidebar"},"launchpad/guides/celo-archive-kubernetes-guide":{"id":"launchpad/guides/celo-archive-kubernetes-guide","title":"Celo Archive Mainnet Node Guide","description":"This Quick Start guide has not yet been updated for Launchpad V2.","sidebar":"launchpadSidebar"},"launchpad/guides/goerli-indexer-guide":{"id":"launchpad/guides/goerli-indexer-guide","title":"Goerli Indexer Guide","description":"This guide is intended to be an end to end walk-through of setting up an Indexer running on the Graph Protocol Testnet on the Ethereum Goerli network.","sidebar":"launchpadSidebar"},"launchpad/guides/install-fcos":{"id":"launchpad/guides/install-fcos","title":"FCOS Installation","description":"Fedora CoreOS (FCOS) is an open-source container-focused operating system that is:","sidebar":"launchpadSidebar"},"launchpad/intro":{"id":"launchpad/intro","title":"Introduction","description":"Launchpad is a toolkit for running a Graph Protocol Indexer on Kubernetes. It aims to provide the fastest path to production multi-chain indexing, with sane security and performance defaults. It should work well whether you have a single node cluster or twenty. It is comprised of an opinionated set of tools on your local machine, layered over one another to provide a declarative workflow to manage your deployments stack.","sidebar":"launchpadSidebar"},"launchpad/other-resources":{"id":"launchpad/other-resources","title":"Other Resources","description":"Kubernetes","sidebar":"launchpadSidebar"},"launchpad/prerequisites":{"id":"launchpad/prerequisites","title":"Prerequisites","description":"You will need some things to use Launchpad for your infrastructure:","sidebar":"launchpadSidebar"},"launchpad/quick-start":{"id":"launchpad/quick-start","title":"Quick Start","description":"Make sure you have all the Prerequisites before starting.","sidebar":"launchpadSidebar"},"launchpad/server-side-stack":{"id":"launchpad/server-side-stack","title":"Server Side Stack","description":"Server Side Stack","sidebar":"launchpadSidebar"},"mips-resources/intro":{"id":"mips-resources/intro","title":"Introduction","description":"It\'s an exciting time to be participating in The Graph ecosystem! During Graph Day 2022 Yaniv Tal announced the sunsetting of the hosted service, a moment The Graph ecosystem has been working towards for many years.","sidebar":"mipsSidebar"},"mips-resources/mips-faq":{"id":"mips-resources/mips-faq","title":"MIPs FAQs","description":"desc","sidebar":"mipsSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.815abf75.js b/assets/js/935f2afb.815abf75.js new file mode 100644 index 00000000..44b4d102 --- /dev/null +++ b/assets/js/935f2afb.815abf75.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"launchpadSidebar":[{"type":"link","label":"Introduction","href":"/launchpad/intro","docId":"launchpad/intro"},{"type":"link","label":"Prerequisites","href":"/launchpad/prerequisites","docId":"launchpad/prerequisites"},{"type":"link","label":"Quick Start","href":"/launchpad/quick-start","docId":"launchpad/quick-start"},{"type":"link","label":"Design Principles","href":"/launchpad/design-principles","docId":"launchpad/design-principles"},{"type":"link","label":"Client Side Tooling","href":"/launchpad/client-side-tooling","docId":"launchpad/client-side-tooling"},{"type":"link","label":"Server Side Stack","href":"/launchpad/server-side-stack","docId":"launchpad/server-side-stack"},{"type":"link","label":"Frequently Asked Questions","href":"/launchpad/faq","docId":"launchpad/faq"},{"type":"link","label":"Other Resources","href":"/launchpad/other-resources","docId":"launchpad/other-resources"},{"type":"category","label":"Guides","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"Arbitrum Archive Mainnet Node Guide","href":"/launchpad/guides/arbitrum-archive-kubernetes-guide","docId":"launchpad/guides/arbitrum-archive-kubernetes-guide"},{"type":"link","label":"Avalanche Archive Mainnet Node Guide","href":"/launchpad/guides/avalanche-archive-kubernetes","docId":"launchpad/guides/avalanche-archive-kubernetes"},{"type":"link","label":"Celo Archive Mainnet Node Guide","href":"/launchpad/guides/celo-archive-kubernetes-guide","docId":"launchpad/guides/celo-archive-kubernetes-guide"},{"type":"link","label":"Goerli Indexer Guide","href":"/launchpad/guides/goerli-indexer-guide","docId":"launchpad/guides/goerli-indexer-guide"},{"type":"link","label":"FCOS Installation","href":"/launchpad/guides/install-fcos","docId":"launchpad/guides/install-fcos"}]},{"type":"category","label":"Advanced Topics","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"Considerations for Kubernetes installation using FCOS","href":"/launchpad/advanced/advanced-kubernetes","docId":"launchpad/advanced/advanced-kubernetes"}]}],"gnSidebar":[{"type":"link","label":"Introduction","href":"/graphcast/intro","docId":"graphcast/intro"},{"type":"link","label":"Design Principles","href":"/graphcast/design-principles","docId":"graphcast/design-principles"},{"type":"category","label":"SDK","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"Introduction","href":"/graphcast/sdk/intro","docId":"graphcast/sdk/intro"},{"type":"link","label":"Radio Development","href":"/graphcast/sdk/radio-dev","docId":"graphcast/sdk/radio-dev"},{"type":"link","label":"Registry","href":"/graphcast/sdk/registry","docId":"graphcast/sdk/registry"}]},{"type":"category","label":"Radios","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"Listener Radio","href":"/graphcast/radios/listener-radio","docId":"graphcast/radios/listener-radio"},{"type":"link","label":"Subgraph Radio","href":"/graphcast/radios/subgraph-radio","docId":"graphcast/radios/subgraph-radio"},{"type":"link","label":"One-shot CLI","href":"/graphcast/radios/one-shot","docId":"graphcast/radios/one-shot"}]}],"mipsSidebar":[{"type":"link","label":"Introduction","href":"/mips-resources/intro","docId":"mips-resources/intro"},{"type":"link","label":"MIPs FAQs","href":"/mips-resources/mips-faq","docId":"mips-resources/mips-faq"}]},"docs":{"graphcast/design-principles":{"id":"graphcast/design-principles","title":"Design Principles","description":"There are two main components of Graphcast","sidebar":"gnSidebar"},"graphcast/intro":{"id":"graphcast/intro","title":"Introduction","description":"Is there something you\'d like to learn from or share with your fellow Indexers in an automated manner, but it\'s too much hassle or costs too much gas?","sidebar":"gnSidebar"},"graphcast/radios/listener-radio":{"id":"graphcast/radios/listener-radio","title":"Listener Radio","description":"The source code for Listener Radio is available on GitHub and Docker builds are automatically published as GitHub Packages.","sidebar":"gnSidebar"},"graphcast/radios/one-shot":{"id":"graphcast/radios/one-shot","title":"One-shot CLI","description":"The source code for the one-shot CLI is available on GitHub.","sidebar":"gnSidebar"},"graphcast/radios/subgraph-radio":{"id":"graphcast/radios/subgraph-radio","title":"Subgraph Radio","description":"The source code for the Subgraph Radio is available on GitHub and Docker builds are automatically published as GitHub Packages. Subgraph Radio is also published as a crate on crates.io.","sidebar":"gnSidebar"},"graphcast/sdk/intro":{"id":"graphcast/sdk/intro","title":"Introduction","description":"Graphcast SDK is a decentralized, distributed peer-to-peer (P2P) communication tool that enables users across the network to exchange information in real-time. It is designed to overcome the high cost of signaling or coordination between blockchain participants by enabling off-chain communication (gossip/cheap talk). This is particularly useful for applications where real-time communication is essential but the cost of on-chain transactions is prohibitive.","sidebar":"gnSidebar"},"graphcast/sdk/radio-dev":{"id":"graphcast/sdk/radio-dev","title":"Radio Development","description":"Do you want to build robust, peer-to-peer messaging apps that automatically exchange valuable data with other Indexers in real time? Do you have an idea for what data could be useful to share that could lead to greater communication efficiency in The Graph network as a whole? Then you want to build a Radio on top of the Graphcast network.","sidebar":"gnSidebar"},"graphcast/sdk/registry":{"id":"graphcast/sdk/registry","title":"Registry","description":"The Graphcast Registry contracts allow an address to set a GraphcastID by calling setGraphcastID(indexeraddress, graphcastIDaddress) as either an Indexer or an Indexer operator, or calling setGraphcastID(graphcastID_address) as the Indexer address. The relationship between an Indexer address to its GraphcastID is limited to 1:1, and cannot be set to itself. This restriction provides consistency and security for the Indexer identity to operate on Graphcast as one GraphcastID operating across Radio applications. To learn more about the registry, you can check out the Github repository.","sidebar":"gnSidebar"},"launchpad/advanced/advanced-kubernetes":{"id":"launchpad/advanced/advanced-kubernetes","title":"Considerations for Kubernetes installation using FCOS","description":"This guide provides a general walkthrough for installing Kubernetes using Fedora CoreOS (FCOS) as the base operating system.","sidebar":"launchpadSidebar"},"launchpad/client-side-tooling":{"id":"launchpad/client-side-tooling","title":"Client Side Tooling","description":"Launchpad comes with an opinionated set of tools on your local machine, layered over one another to provide a declarative workflow to manage your cluster software stack.","sidebar":"launchpadSidebar"},"launchpad/design-principles":{"id":"launchpad/design-principles","title":"Design Principles","description":"Design Principles","sidebar":"launchpadSidebar"},"launchpad/faq":{"id":"launchpad/faq","title":"Frequently Asked Questions","description":"Do I need a server for launchpad-starter?","sidebar":"launchpadSidebar"},"launchpad/guides/arbitrum-archive-kubernetes-guide":{"id":"launchpad/guides/arbitrum-archive-kubernetes-guide","title":"Arbitrum Archive Mainnet Node Guide","description":"This Quick Start guide has not yet been updated for Launchpad V2.","sidebar":"launchpadSidebar"},"launchpad/guides/avalanche-archive-kubernetes":{"id":"launchpad/guides/avalanche-archive-kubernetes","title":"Avalanche Archive Mainnet Node Guide","description":"This Quick Start guide has not yet been updated for Launchpad V2.","sidebar":"launchpadSidebar"},"launchpad/guides/celo-archive-kubernetes-guide":{"id":"launchpad/guides/celo-archive-kubernetes-guide","title":"Celo Archive Mainnet Node Guide","description":"This Quick Start guide has not yet been updated for Launchpad V2.","sidebar":"launchpadSidebar"},"launchpad/guides/goerli-indexer-guide":{"id":"launchpad/guides/goerli-indexer-guide","title":"Goerli Indexer Guide","description":"This guide is intended to be an end to end walk-through of setting up an Indexer running on the Graph Protocol Testnet on the Ethereum Goerli network.","sidebar":"launchpadSidebar"},"launchpad/guides/install-fcos":{"id":"launchpad/guides/install-fcos","title":"FCOS Installation","description":"Fedora CoreOS (FCOS) is an open-source container-focused operating system that is:","sidebar":"launchpadSidebar"},"launchpad/intro":{"id":"launchpad/intro","title":"Introduction","description":"Launchpad is a toolkit for running a Graph Protocol Indexer on Kubernetes. It aims to provide the fastest path to production multi-chain indexing, with sane security and performance defaults. It should work well whether you have a single node cluster or twenty. It is comprised of an opinionated set of tools on your local machine, layered over one another to provide a declarative workflow to manage your deployments stack.","sidebar":"launchpadSidebar"},"launchpad/other-resources":{"id":"launchpad/other-resources","title":"Other Resources","description":"Kubernetes","sidebar":"launchpadSidebar"},"launchpad/prerequisites":{"id":"launchpad/prerequisites","title":"Prerequisites","description":"You will need some things to use Launchpad for your infrastructure:","sidebar":"launchpadSidebar"},"launchpad/quick-start":{"id":"launchpad/quick-start","title":"Quick Start","description":"Make sure you have all the Prerequisites before starting.","sidebar":"launchpadSidebar"},"launchpad/server-side-stack":{"id":"launchpad/server-side-stack","title":"Server Side Stack","description":"Server Side Stack","sidebar":"launchpadSidebar"},"mips-resources/intro":{"id":"mips-resources/intro","title":"Introduction","description":"It\'s an exciting time to be participating in The Graph ecosystem! During Graph Day 2022 Yaniv Tal announced the sunsetting of the hosted service, a moment The Graph ecosystem has been working towards for many years.","sidebar":"mipsSidebar"},"mips-resources/mips-faq":{"id":"mips-resources/mips-faq","title":"MIPs FAQs","description":"desc","sidebar":"mipsSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/db074018.30e23342.js b/assets/js/db074018.e53bd748.js similarity index 98% rename from assets/js/db074018.30e23342.js rename to assets/js/db074018.e53bd748.js index 245cbd94..25234f57 100644 --- a/assets/js/db074018.30e23342.js +++ b/assets/js/db074018.e53bd748.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7058],{3905:(e,t,a)=>{a.d(t,{Zo:()=>p,kt:()=>k});var n=a(7294);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var d=n.createContext({}),s=function(e){var t=n.useContext(d),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},p=function(e){var t=s(e.components);return n.createElement(d.Provider,{value:t},e.children)},m="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},c=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,i=e.originalType,d=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),m=s(a),c=r,k=m["".concat(d,".").concat(c)]||m[c]||u[c]||i;return a?n.createElement(k,o(o({ref:t},p),{},{components:a})):n.createElement(k,o({ref:t},p))}));function k(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=a.length,o=new Array(i);o[0]=c;var l={};for(var d in t)hasOwnProperty.call(t,d)&&(l[d]=t[d]);l.originalType=e,l[m]="string"==typeof e?e:r,o[1]=l;for(var s=2;s{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var n=a(7462),r=(a(7294),a(3905));const i={sidebar_position:1},o="Listener Radio",l={unversionedId:"graphcast/radios/listener-radio",id:"graphcast/radios/listener-radio",title:"Listener Radio",description:"The source code for Listener Radio is available on GitHub and Docker builds are automatically published as GitHub Packages.",source:"@site/docs/graphcast/radios/listener-radio.md",sourceDirName:"graphcast/radios",slug:"/graphcast/radios/listener-radio",permalink:"/graphcast/radios/listener-radio",draft:!1,editUrl:"https://github.com/graphops/docs/edit/main/docs/graphcast/radios/listener-radio.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"gnSidebar",previous:{title:"Registry",permalink:"/graphcast/sdk/registry"},next:{title:"Subgraph Radio",permalink:"/graphcast/radios/subgraph-radio"}},d={},s=[{value:"Introduction",id:"introduction",level:2},{value:"Quick Start",id:"quick-start",level:2},{value:"Basic Configuration",id:"basic-configuration",level:3},{value:"Example message table",id:"example-message-table",level:4},{value:"Advanced Configuration",id:"advanced-configuration",level:2},{value:"Configurations explained",id:"configurations-explained",level:3},{value:"COVERAGE (topic)",id:"coverage-topic",level:4},{value:"Identity validaiton",id:"identity-validaiton",level:4},{value:"Gossip protocol",id:"gossip-protocol",level:4},{value:"Monitoring the Radio",id:"monitoring-the-radio",level:2},{value:"Prometheus & Grafana",id:"prometheus--grafana",level:3},{value:"HTTP Server",id:"http-server",level:2}],p={toc:s},m="wrapper";function u(e){let{components:t,...a}=e;return(0,r.kt)(m,(0,n.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"listener-radio"},"Listener Radio"),(0,r.kt)("p",null,"The source code for Listener Radio is available ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/listener-radio"},"on GitHub")," and Docker builds are automatically published as ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/listener-radio/pkgs/container/listener-radio"},"GitHub Packages"),"."),(0,r.kt)("h2",{id:"introduction"},"Introduction"),(0,r.kt)("p",null,"This Radio shall monitor Graphcast network by the pubsub topic of ",(0,r.kt)("inlineCode",{parentName:"p"},"graphcast-v[version]-[network]"),". The Radio will not send messages to the network, but instead will record the messages and generate basic metrics for network monitoring."),(0,r.kt)("p",null,"Graphcast network is a complex system with numerous nodes and connections, and monitoring it is crucial for maintaining its performance, identifying potential issues, and ensuring its robustness and reliability."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Performance Optimization: to identify bottlenecks and areas of inefficiency."),(0,r.kt)("li",{parentName:"ul"},"Troubleshooting: to quickly diagnose issues within the network, reducing downtime and improving reliability."),(0,r.kt)("li",{parentName:"ul"},"Security: to immediately detect any unusual activity that might indicate a security breach."),(0,r.kt)("li",{parentName:"ul"},"Planning and Forecasting: Record valuable data that can be used for planning and forecasting purposes, helping us to make informed decisions about the network's future.")),(0,r.kt)("h2",{id:"quick-start"},"Quick Start"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Ensure a running Postgres instance"),(0,r.kt)("li",{parentName:"ul"},"Set Postgres url to ",(0,r.kt)("inlineCode",{parentName:"li"},"DATABASE_URL")," in ",(0,r.kt)("inlineCode",{parentName:"li"},".env")),(0,r.kt)("li",{parentName:"ul"},"Set general GraphcastAgent environmental variables shown in the below table"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"cargo run")," from source code (later should use Github actions to build source and dockerize")),(0,r.kt)("h3",{id:"basic-configuration"},"Basic Configuration"),(0,r.kt)("p",null,"You will need to prepare the following environment variables:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Name"),(0,r.kt)("th",{parentName:"tr",align:null},"Description and examples"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"DATABASE_URL")),(0,r.kt)("td",{parentName:"tr",align:null},"Postgres Database URL. The tool comes with automatic database migration, database url passed in must be exist and can be connected. ",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"postgresql://[username]:[password]@[pg_host]:[pg_port]/[db_name]"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY")),(0,r.kt)("td",{parentName:"tr",align:null},"Private key to the Graphcast ID wallet (Precendence over mnemonics).",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"GRAPH_NODE_STATUS_ENDPOINT")),(0,r.kt)("td",{parentName:"tr",align:null},"URL to a Graph Node Indexing Status endpoint.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"http://index-node:8030/graphql"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"REGISTRY_SUBGRAPH")),(0,r.kt)("td",{parentName:"tr",align:null},"URL to the Graphcast Registry subgraph for your network. Check ",(0,r.kt)("a",{parentName:"td",href:"../sdk/registry#subgraph-apis"},"APIs")," for your preferred network")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"NETWORK_SUBGRAPH")),(0,r.kt)("td",{parentName:"tr",align:null},"URL to the Graph Network subgraph. Check ",(0,r.kt)("a",{parentName:"td",href:"../sdk/registry#subgraph-apis"},"APIs")," for your preferred network")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"GRAPHCAST_NETWORK")),(0,r.kt)("td",{parentName:"tr",align:null},"The Graphcast Messaging fleet and pubsub namespace to use.",(0,r.kt)("br",null),"Mainnet: ",(0,r.kt)("inlineCode",{parentName:"td"},"mainnet"),(0,r.kt)("br",null),"Goerli: ",(0,r.kt)("inlineCode",{parentName:"td"},"testnet"))))),(0,r.kt)("h4",{id:"example-message-table"},"Example message table"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"id"),(0,r.kt)("th",{parentName:"tr",align:null},"message"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"1"),(0,r.kt)("td",{parentName:"tr",align:null},'{"nonce": 1686182179, "network": "mainnet", "payload": {"content": "0x3f...", "identifier": "QmVhiE4nax9i86UBnBmQCYDzvjWuwHShYh7aspGPQhU5Sj"}, "signature": "dff1...", "block_hash": "276e...", "identifier": "QmVhiE4nax9i86UBnBmQCYDzvjWuwHShYh7aspGPQhU5Sj", "block_number": 17431860}')),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"2"),(0,r.kt)("td",{parentName:"tr",align:null},'{"nonce": 1686182183, "network": "goerli", "payload": {"content": "0xc0...", "identifier": "QmacQnSgia4iDPWHpeY6aWxesRFdb8o5DKZUx96zZqEWrB"}, "signature": "dbd2...", "block_hash": "0198...", "identifier": "QmacQnSgia4iDPWHpeY6aWxesRFdb8o5DKZUx96zZqEWrB", "block_number": 9140860}')),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"..."),(0,r.kt)("td",{parentName:"tr",align:null},"...")))),(0,r.kt)("h2",{id:"advanced-configuration"},"Advanced Configuration"),(0,r.kt)("p",null,"In the configuration table below is the full list of environment variables you can set, along with example values."),(0,r.kt)("p",null,"See ",(0,r.kt)("a",{parentName:"p",href:"#basic-configuration"},"Basic Configuration")," above. The following environment variables are optional:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Name (Optional variables)"),(0,r.kt)("th",{parentName:"tr",align:null},"Description and examples"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"MNEMONIC")),(0,r.kt)("td",{parentName:"tr",align:null},"Mnemonic to the Graphcast ID wallet (first address of the wallet is used; Only one of ",(0,r.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY")," or ",(0,r.kt)("inlineCode",{parentName:"td"},"MNEMONIC")," is needed). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"claptrap armchair violin..."))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"COLLECT_MESSAGE_DURATION")),(0,r.kt)("td",{parentName:"tr",align:null},"Seconds that the Subgraph Radio will wait to collect remote POI attestations before making a comparison with the local POI. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"120")," for 2 minutes.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"COVERAGE")),(0,r.kt)("td",{parentName:"tr",align:null},'Toggle for topic coverage level. Possible values: "comprehensive", "on-chain", "minimal". Default is set to "on-chain" coverage.')),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"TOPICS")),(0,r.kt)("td",{parentName:"tr",align:null},"Comma separated static list of content topics (subgraphs) to subscribe to. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz,QmUwCFhXM3f6qH9Ls9Y6gDNURBH7mxsn6JcectgxAz6CwU,QmQ1Lyh3U6YgVP6YX1RgRz6c8GmKkEpokLwPvEtJx6cF1y"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_HOST")),(0,r.kt)("td",{parentName:"tr",align:null},"Interface onto which to bind the bundled Waku node. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"P2P port on which the bundled Waku node will operate. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"60000"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_NODE_KEY")),(0,r.kt)("td",{parentName:"tr",align:null},"Static Waku Node Key.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"BOOT_NODE_ADDRESSES")),(0,r.kt)("td",{parentName:"tr",align:null},"Peer addresses to use as Waku boot nodes. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},'"addr1, addr2, addr3"'))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SLACK_TOKEN")),(0,r.kt)("td",{parentName:"tr",align:null},"Slack Token to use for notifications. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"xoxp-0123456789-0123456789-0123456789-0123456789"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"TELEGRAM_TOKEN")),(0,r.kt)("td",{parentName:"tr",align:null},"Telegram Bot Token to use for notifications. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"TELEGRAM_CHAT_ID")),(0,r.kt)("td",{parentName:"tr",align:null},"The ID of the Telegram chat to send messages to. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"-1001234567890"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SLACK_CHANNEL")),(0,r.kt)("td",{parentName:"tr",align:null},"Name of Slack channel to send messages to (has to be a public channel). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"poir-notifications"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_LOG_LEVEL")),(0,r.kt)("td",{parentName:"tr",align:null},"Waku node logging configuration. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"INFO")," (is also the default)")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"RUST_LOG")),(0,r.kt)("td",{parentName:"tr",align:null},"Rust tracing configuration. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"graphcast_sdk=debug,subgraph_radio=debug"),", defaults to ",(0,r.kt)("inlineCode",{parentName:"td"},"info")," for everything")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"DISCORD_WEBHOOK")),(0,r.kt)("td",{parentName:"tr",align:null},"Discord webhook URL for notifications. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"https://discord.com/api/webhooks/123456789012345678/AbCDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"METRICS_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose Prometheus metrics on this (off by default). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"3001"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"METRICS_HOST")),(0,r.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose Prometheus metrics on this (off by default). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SERVER_HOST")),(0,r.kt)("td",{parentName:"tr",align:null},"If ",(0,r.kt)("inlineCode",{parentName:"td"},"SERVER_PORT")," is set, the Radio will expose an API service on the given host and port. Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SERVER_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose an API service on the given port (off by default). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"8080"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"LOG_FORMAT")),(0,r.kt)("td",{parentName:"tr",align:null},"Options: ",(0,r.kt)("inlineCode",{parentName:"td"},"pretty")," - verbose and human readable; ",(0,r.kt)("inlineCode",{parentName:"td"},"json")," - not verbose and parsable; ",(0,r.kt)("inlineCode",{parentName:"td"},"compact")," - not verbose and not parsable; ",(0,r.kt)("inlineCode",{parentName:"td"},"full")," - verbose and not parsible. Default value: ",(0,r.kt)("inlineCode",{parentName:"td"},"pretty"),".")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"PERSISTENCE_FILE_PATH")),(0,r.kt)("td",{parentName:"tr",align:null},"Relative path. If set, the Radio will periodically store states of the program to the file in json format (off by default).")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"DISCV5_ENRS")),(0,r.kt)("td",{parentName:"tr",align:null},"Comma separated ENRs for Waku Discv5 bootstrapping. Defaults to empty list.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"DISCV5_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"Discoverable UDP port. Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"9000"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"ID_VALIDATION")),(0,r.kt)("td",{parentName:"tr",align:null},"Defines the level of validation for message signers used during radio operation. Options include: ",(0,r.kt)("inlineCode",{parentName:"td"},"no-check"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"valid-address"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"graphcast-registered"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"graph-network-account"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"registered-indexer"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"indexer"))))),(0,r.kt)("h3",{id:"configurations-explained"},"Configurations explained"),(0,r.kt)("h4",{id:"coverage-topic"},"COVERAGE (topic)"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"COVERAGE")," is used to specify the topic coverage level. It controls the range of topics (subgraph ipfs hashes) the Indexer subscribes to in order to process data and participate in the network."),(0,r.kt)("p",null,"There are three coverage levels available:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"comprehensive"),": Subscribe to on-chain topics, user-defined static topics, and subgraph deployments syncing on graph node. This level is useful for Indexers who want to compare public POIs for all deployments syncing on their graph node even if they don't have an active allocations open (their stake will not be taken into account in attestation)."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"on-chain"),": Subscribe to on-chain topics and user-defined static topics. This is the default coverage level and is suitable for indexers who only want to compare data for deployments with active allocations."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"minimal"),": Only subscribe to user-defined static topics. This level is for Indexers who want to limit their participation to specific topics of interest.")),(0,r.kt)("h4",{id:"identity-validaiton"},"Identity validaiton"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"ID_VALIDATION")," is used to define level of validation for message signers used during radio operation."),(0,r.kt)("p",null,"Available Options:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"no-check"),": does not perform check on the message signature and does not verify the signer."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"valid-address"),": checks the signer to be a valid Ethereum address."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"graphcast-registered"),": checks the signer to be registered on Graphcast Registry."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"graph-network-account"),": checks the signer to be a Graph account."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"registered-indexer"),": checks the signer to be registered on Graphcast Registry and corresponds to an Indexer that satisfies the minimum stake requirement."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"indexer"),": checks the signer to be either registered on Graphcast Registry or to be a Graph Account, and corresponds to an Indexer satisfying the minimum stake requirement.")),(0,r.kt)("h4",{id:"gossip-protocol"},"Gossip protocol"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"WAKU_HOST")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"WAKU_PORT")," specify where the bundled Waku node runs. If you want to run multiple Radios, or multiple instances of the same Radio, you should run them on different ports."),(0,r.kt)("p",null,"If you want to customize the log level, you can toggle ",(0,r.kt)("inlineCode",{parentName:"p"},"RUST_LOG")," environment variable. Here's an example configuration to get more verbose logging:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},'RUST_LOG="warn,hyper=warn,graphcast_sdk=debug,subgraph_radio=debug"\n')),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Discv5")," is an ambient node discovery network for establishing a decentralized network of interconnected Graphcast Radios. Discv5, when used in Graphcast Radios, serves as a dedicated peer-to-peer discovery protocol that empowers Radios to form an efficient, decentralized network. Without Discv5, the traffic within the Graphcast network would largely rely on centrally hosted boot nodes, leading to a less distributed architecture. However, with Discv5, Radios are capable of directly routing messages among themselves, significantly enhancing network decentralization and reducing reliance on the central nodes. If you want to learn more about Discv5, check out the ",(0,r.kt)("a",{parentName:"p",href:"https://rfc.vac.dev/spec/33/"},"official spec"),"."),(0,r.kt)("h2",{id:"monitoring-the-radio"},"Monitoring the Radio"),(0,r.kt)("h3",{id:"prometheus--grafana"},"Prometheus & Grafana"),(0,r.kt)("p",null,"The exposed metrics can be scraped by a Prometheus server and displayed in Grafana. In order to use them you have to have a local Prometheus server running and scraping metrics on the provided port. You can specify the metrics host and port by using the environment variables ",(0,r.kt)("inlineCode",{parentName:"p"},"METRICS_PORT")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"METRICS_HOST"),"."),(0,r.kt)("h2",{id:"http-server"},"HTTP Server"),(0,r.kt)("p",null,"The Radio spins up an HTTP server with a GraphQL API when ",(0,r.kt)("inlineCode",{parentName:"p"},"SERVER_HOST")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"SERVER_PORT")," environment variables are set. The supported routes are:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"/health")," for health status"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"/api/v1/graphql")," for GET and POST requests with GraphQL playground interface")),(0,r.kt)("p",null,"The GraphQL API now includes:"),(0,r.kt)("p",null,"Below are an example query:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-graphql"},"Query {\n rows{\n id\n message{\n nonce\n network\n payload{\n content\n }\n }\n }\n\n messages{\n identifier\n nonce\n network\n blockNumber\n blockHash\n signature\n payload{\n identifier\n content\n }\n }\n}\n")),(0,r.kt)("p",null,"example mutation:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"mutation{\n deleteMessage(id:1)\n}\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7058],{3905:(e,t,a)=>{a.d(t,{Zo:()=>p,kt:()=>k});var n=a(7294);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var d=n.createContext({}),s=function(e){var t=n.useContext(d),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},p=function(e){var t=s(e.components);return n.createElement(d.Provider,{value:t},e.children)},m="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},c=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,i=e.originalType,d=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),m=s(a),c=r,k=m["".concat(d,".").concat(c)]||m[c]||u[c]||i;return a?n.createElement(k,o(o({ref:t},p),{},{components:a})):n.createElement(k,o({ref:t},p))}));function k(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=a.length,o=new Array(i);o[0]=c;var l={};for(var d in t)hasOwnProperty.call(t,d)&&(l[d]=t[d]);l.originalType=e,l[m]="string"==typeof e?e:r,o[1]=l;for(var s=2;s{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var n=a(7462),r=(a(7294),a(3905));const i={sidebar_position:1},o="Listener Radio",l={unversionedId:"graphcast/radios/listener-radio",id:"graphcast/radios/listener-radio",title:"Listener Radio",description:"The source code for Listener Radio is available on GitHub and Docker builds are automatically published as GitHub Packages.",source:"@site/docs/graphcast/radios/listener-radio.md",sourceDirName:"graphcast/radios",slug:"/graphcast/radios/listener-radio",permalink:"/graphcast/radios/listener-radio",draft:!1,editUrl:"https://github.com/graphops/docs/edit/main/docs/graphcast/radios/listener-radio.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"gnSidebar",previous:{title:"Registry",permalink:"/graphcast/sdk/registry"},next:{title:"Subgraph Radio",permalink:"/graphcast/radios/subgraph-radio"}},d={},s=[{value:"Introduction",id:"introduction",level:2},{value:"Quick Start",id:"quick-start",level:2},{value:"Basic Configuration",id:"basic-configuration",level:3},{value:"Example message table",id:"example-message-table",level:4},{value:"Advanced Configuration",id:"advanced-configuration",level:2},{value:"Configurations explained",id:"configurations-explained",level:3},{value:"COVERAGE (topic)",id:"coverage-topic",level:4},{value:"Identity validaiton",id:"identity-validaiton",level:4},{value:"Gossip protocol",id:"gossip-protocol",level:4},{value:"Monitoring the Radio",id:"monitoring-the-radio",level:2},{value:"Prometheus & Grafana",id:"prometheus--grafana",level:3},{value:"HTTP Server",id:"http-server",level:2}],p={toc:s},m="wrapper";function u(e){let{components:t,...a}=e;return(0,r.kt)(m,(0,n.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"listener-radio"},"Listener Radio"),(0,r.kt)("p",null,"The source code for Listener Radio is available ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/listener-radio"},"on GitHub")," and Docker builds are automatically published as ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/graphops/listener-radio/pkgs/container/listener-radio"},"GitHub Packages"),"."),(0,r.kt)("h2",{id:"introduction"},"Introduction"),(0,r.kt)("p",null,"This Radio shall monitor Graphcast network by the pubsub topic of ",(0,r.kt)("inlineCode",{parentName:"p"},"graphcast-v[version]-[network]"),". The Radio will not send messages to the network, but instead will record the messages and generate basic metrics for network monitoring."),(0,r.kt)("p",null,"Graphcast network is a complex system with numerous nodes and connections, and monitoring it is crucial for maintaining its performance, identifying potential issues, and ensuring its robustness and reliability."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Performance Optimization: to identify bottlenecks and areas of inefficiency."),(0,r.kt)("li",{parentName:"ul"},"Troubleshooting: to quickly diagnose issues within the network, reducing downtime and improving reliability."),(0,r.kt)("li",{parentName:"ul"},"Security: to immediately detect any unusual activity that might indicate a security breach."),(0,r.kt)("li",{parentName:"ul"},"Planning and Forecasting: Record valuable data that can be used for planning and forecasting purposes, helping us to make informed decisions about the network's future.")),(0,r.kt)("h2",{id:"quick-start"},"Quick Start"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Ensure a running Postgres instance"),(0,r.kt)("li",{parentName:"ul"},"Set Postgres url to ",(0,r.kt)("inlineCode",{parentName:"li"},"DATABASE_URL")," in ",(0,r.kt)("inlineCode",{parentName:"li"},".env")),(0,r.kt)("li",{parentName:"ul"},"Set general GraphcastAgent environmental variables shown in the below table"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"cargo run")," from source code (later should use Github actions to build source and dockerize")),(0,r.kt)("h3",{id:"basic-configuration"},"Basic Configuration"),(0,r.kt)("p",null,"You will need to prepare the following environment variables:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Name"),(0,r.kt)("th",{parentName:"tr",align:null},"Description and examples"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"DATABASE_URL")),(0,r.kt)("td",{parentName:"tr",align:null},"Postgres Database URL. The tool comes with automatic database migration, database url passed in must be exist and can be connected. ",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"postgresql://[username]:[password]@[pg_host]:[pg_port]/[db_name]"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY")),(0,r.kt)("td",{parentName:"tr",align:null},"Private key to the Graphcast ID wallet (Precendence over mnemonics).",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"GRAPH_NODE_STATUS_ENDPOINT")),(0,r.kt)("td",{parentName:"tr",align:null},"URL to a Graph Node Indexing Status endpoint.",(0,r.kt)("br",null),"Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"http://index-node:8030/graphql"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"REGISTRY_SUBGRAPH")),(0,r.kt)("td",{parentName:"tr",align:null},"URL to the Graphcast Registry subgraph for your network. Check ",(0,r.kt)("a",{parentName:"td",href:"../sdk/registry#subgraph-apis"},"APIs")," for your preferred network")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"NETWORK_SUBGRAPH")),(0,r.kt)("td",{parentName:"tr",align:null},"URL to the Graph Network subgraph. Check ",(0,r.kt)("a",{parentName:"td",href:"../sdk/registry#subgraph-apis"},"APIs")," for your preferred network")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"GRAPHCAST_NETWORK")),(0,r.kt)("td",{parentName:"tr",align:null},"The Graphcast Messaging fleet and pubsub namespace to use.",(0,r.kt)("br",null),"Mainnet: ",(0,r.kt)("inlineCode",{parentName:"td"},"mainnet"),(0,r.kt)("br",null),"Goerli: ",(0,r.kt)("inlineCode",{parentName:"td"},"testnet"))))),(0,r.kt)("h4",{id:"example-message-table"},"Example message table"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"id"),(0,r.kt)("th",{parentName:"tr",align:null},"message"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"1"),(0,r.kt)("td",{parentName:"tr",align:null},'{"nonce": 1686182179, "network": "mainnet", "payload": {"content": "0x3f...", "identifier": "QmVhiE4nax9i86UBnBmQCYDzvjWuwHShYh7aspGPQhU5Sj"}, "signature": "dff1...", "block_hash": "276e...", "identifier": "QmVhiE4nax9i86UBnBmQCYDzvjWuwHShYh7aspGPQhU5Sj", "block_number": 17431860}')),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"2"),(0,r.kt)("td",{parentName:"tr",align:null},'{"nonce": 1686182183, "network": "goerli", "payload": {"content": "0xc0...", "identifier": "QmacQnSgia4iDPWHpeY6aWxesRFdb8o5DKZUx96zZqEWrB"}, "signature": "dbd2...", "block_hash": "0198...", "identifier": "QmacQnSgia4iDPWHpeY6aWxesRFdb8o5DKZUx96zZqEWrB", "block_number": 9140860}')),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"..."),(0,r.kt)("td",{parentName:"tr",align:null},"...")))),(0,r.kt)("h2",{id:"advanced-configuration"},"Advanced Configuration"),(0,r.kt)("p",null,"In the configuration table below is the full list of environment variables you can set, along with example values."),(0,r.kt)("p",null,"See ",(0,r.kt)("a",{parentName:"p",href:"#basic-configuration"},"Basic Configuration")," above. The following environment variables are optional:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Name (Optional variables)"),(0,r.kt)("th",{parentName:"tr",align:null},"Description and examples"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"MNEMONIC")),(0,r.kt)("td",{parentName:"tr",align:null},"Mnemonic to the Graphcast ID wallet (first address of the wallet is used; Only one of ",(0,r.kt)("inlineCode",{parentName:"td"},"PRIVATE_KEY")," or ",(0,r.kt)("inlineCode",{parentName:"td"},"MNEMONIC")," is needed). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"claptrap armchair violin..."))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"COLLECT_MESSAGE_DURATION")),(0,r.kt)("td",{parentName:"tr",align:null},"Seconds that the Subgraph Radio will wait to collect remote POI attestations before making a comparison with the local POI. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"120")," for 2 minutes.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"COVERAGE")),(0,r.kt)("td",{parentName:"tr",align:null},'Toggle for topic coverage level. Possible values: "comprehensive", "on-chain", "minimal". Default is set to "on-chain" coverage.')),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"TOPICS")),(0,r.kt)("td",{parentName:"tr",align:null},"Comma separated static list of content topics (subgraphs) to subscribe to. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz,QmUwCFhXM3f6qH9Ls9Y6gDNURBH7mxsn6JcectgxAz6CwU,QmQ1Lyh3U6YgVP6YX1RgRz6c8GmKkEpokLwPvEtJx6cF1y"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_HOST")),(0,r.kt)("td",{parentName:"tr",align:null},"Interface onto which to bind the bundled Waku node. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"P2P port on which the bundled Waku node will operate. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"60000"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_NODE_KEY")),(0,r.kt)("td",{parentName:"tr",align:null},"Static Waku Node Key.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"BOOT_NODE_ADDRESSES")),(0,r.kt)("td",{parentName:"tr",align:null},"Peer addresses to use as Waku boot nodes. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},'"addr1, addr2, addr3"'))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SLACK_TOKEN")),(0,r.kt)("td",{parentName:"tr",align:null},"Slack Token to use for notifications. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"xoxp-0123456789-0123456789-0123456789-0123456789"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"TELEGRAM_TOKEN")),(0,r.kt)("td",{parentName:"tr",align:null},"Telegram Bot Token to use for notifications. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"TELEGRAM_CHAT_ID")),(0,r.kt)("td",{parentName:"tr",align:null},"The ID of the Telegram chat to send messages to. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"-1001234567890"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SLACK_CHANNEL")),(0,r.kt)("td",{parentName:"tr",align:null},"Name of Slack channel to send messages to (has to be a public channel). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"poir-notifications"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"WAKU_LOG_LEVEL")),(0,r.kt)("td",{parentName:"tr",align:null},"Waku node logging configuration. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"INFO")," (is also the default)")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"RUST_LOG")),(0,r.kt)("td",{parentName:"tr",align:null},"Rust tracing configuration. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"graphcast_sdk=debug,subgraph_radio=debug"),", defaults to ",(0,r.kt)("inlineCode",{parentName:"td"},"info")," for everything")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"DISCORD_WEBHOOK")),(0,r.kt)("td",{parentName:"tr",align:null},"Discord webhook URL for notifications. Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"https://discord.com/api/webhooks/123456789012345678/AbCDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"METRICS_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose Prometheus metrics on this (off by default). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"3001"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"METRICS_HOST")),(0,r.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose Prometheus metrics on this (off by default). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SERVER_HOST")),(0,r.kt)("td",{parentName:"tr",align:null},"If ",(0,r.kt)("inlineCode",{parentName:"td"},"SERVER_PORT")," is set, the Radio will expose an API service on the given host and port. Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"0.0.0.0"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"SERVER_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"If set, the Radio will expose an API service on the given port (off by default). Example: ",(0,r.kt)("inlineCode",{parentName:"td"},"8080"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"LOG_FORMAT")),(0,r.kt)("td",{parentName:"tr",align:null},"Options: ",(0,r.kt)("inlineCode",{parentName:"td"},"pretty")," - verbose and human readable; ",(0,r.kt)("inlineCode",{parentName:"td"},"json")," - not verbose and parsable; ",(0,r.kt)("inlineCode",{parentName:"td"},"compact")," - not verbose and not parsable; ",(0,r.kt)("inlineCode",{parentName:"td"},"full")," - verbose and not parsible. Default value: ",(0,r.kt)("inlineCode",{parentName:"td"},"pretty"),".")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"PERSISTENCE_FILE_PATH")),(0,r.kt)("td",{parentName:"tr",align:null},"Relative path. If set, the Radio will periodically store states of the program to the file in json format (off by default).")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"DISCV5_ENRS")),(0,r.kt)("td",{parentName:"tr",align:null},"Comma separated ENRs for Waku Discv5 bootstrapping. Defaults to empty list.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"DISCV5_PORT")),(0,r.kt)("td",{parentName:"tr",align:null},"Discoverable UDP port. Default: ",(0,r.kt)("inlineCode",{parentName:"td"},"9000"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"ID_VALIDATION")),(0,r.kt)("td",{parentName:"tr",align:null},"Defines the level of validation for message signers used during radio operation. Options include: ",(0,r.kt)("inlineCode",{parentName:"td"},"no-check"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"valid-address"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"graphcast-registered"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"graph-network-account"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"registered-indexer"),", ",(0,r.kt)("inlineCode",{parentName:"td"},"indexer"))))),(0,r.kt)("h3",{id:"configurations-explained"},"Configurations explained"),(0,r.kt)("h4",{id:"coverage-topic"},"COVERAGE (topic)"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"COVERAGE")," is used to specify the topic coverage level. It controls the range of topics (subgraph ipfs hashes) the Indexer subscribes to in order to process data and participate in the network."),(0,r.kt)("p",null,"There are three coverage levels available:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"comprehensive"),": Subscribe to on-chain topics, user-defined static topics, and subgraph deployments syncing on graph node. This level is useful for Indexers who want to compare public POIs for all deployments syncing on their graph node even if they don't have an active allocations open (their stake will not be taken into account in attestation)."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"on-chain"),": Subscribe to on-chain topics and user-defined static topics. This is the default coverage level and is suitable for indexers who only want to compare data for deployments with active allocations."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"minimal"),": Only subscribe to user-defined static topics. This level is for Indexers who want to limit their participation to specific topics of interest.")),(0,r.kt)("h4",{id:"identity-validaiton"},"Identity validaiton"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"ID_VALIDATION")," is used to define level of validation for message signers used during radio operation."),(0,r.kt)("p",null,"Available Options:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"no-check"),": does not perform check on the message signature and does not verify the signer."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"valid-address"),": checks the signer to be a valid Ethereum address."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"graphcast-registered"),": checks the signer to be registered on Graphcast Registry."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"graph-network-account"),": checks the signer to be a Graph account."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"registered-indexer"),": checks the signer to be registered on Graphcast Registry and corresponds to an Indexer that satisfies the minimum stake requirement."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"indexer"),": checks the signer to be either registered on Graphcast Registry or to be a Graph Account, and corresponds to an Indexer satisfying the minimum stake requirement.")),(0,r.kt)("h4",{id:"gossip-protocol"},"Gossip protocol"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"WAKU_HOST")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"WAKU_PORT")," specify where the bundled Waku node runs. If you want to run multiple Radios, or multiple instances of the same Radio, you should run them on different ports."),(0,r.kt)("p",null,"If you want to customize the log level, you can toggle ",(0,r.kt)("inlineCode",{parentName:"p"},"RUST_LOG")," environment variable. Here's an example configuration to get more verbose logging:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},'RUST_LOG="warn,hyper=warn,graphcast_sdk=debug,subgraph_radio=debug"\n')),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Discv5")," is an ambient node discovery network for establishing a decentralized network of interconnected Graphcast Radios. Discv5, when used in Graphcast Radios, serves as a dedicated peer-to-peer discovery protocol that empowers Radios to form an efficient, decentralized network. Without Discv5, the traffic within the Graphcast network would largely rely on centrally hosted boot nodes, leading to a less distributed architecture. However, with Discv5, Radios are capable of directly routing messages among themselves, significantly enhancing network decentralization and reducing reliance on the central nodes. If you want to learn more about Discv5, check out the ",(0,r.kt)("a",{parentName:"p",href:"https://rfc.vac.dev/spec/33/"},"official spec"),"."),(0,r.kt)("h2",{id:"monitoring-the-radio"},"Monitoring the Radio"),(0,r.kt)("h3",{id:"prometheus--grafana"},"Prometheus & Grafana"),(0,r.kt)("p",null,"The exposed metrics can be scraped by a Prometheus server and displayed in Grafana. In order to use them you have to have a local Prometheus server running and scraping metrics on the provided port. You can specify the metrics host and port by using the environment variables ",(0,r.kt)("inlineCode",{parentName:"p"},"METRICS_PORT")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"METRICS_HOST"),"."),(0,r.kt)("h2",{id:"http-server"},"HTTP Server"),(0,r.kt)("p",null,"The Radio spins up an HTTP server with a GraphQL API when ",(0,r.kt)("inlineCode",{parentName:"p"},"SERVER_HOST")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"SERVER_PORT")," environment variables are set. The supported routes are:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"/health")," for health status"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"/api/v1/graphql")," for GET and POST requests with GraphQL playground interface")),(0,r.kt)("p",null,"The GraphQL API now includes:"),(0,r.kt)("p",null,"Below are an example query:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-graphql"},"query {\n rows {\n id\n message {\n nonce\n network\n payload {\n content\n }\n }\n }\n\n messages {\n identifier\n nonce\n network\n blockNumber\n blockHash\n signature\n payload {\n identifier\n content\n }\n }\n}\n")),(0,r.kt)("p",null,"example mutation:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"mutation{\n deleteMessage(id:1)\n}\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ea715d66.9b8e85b6.js b/assets/js/ea715d66.9b8e85b6.js new file mode 100644 index 00000000..b5979fae --- /dev/null +++ b/assets/js/ea715d66.9b8e85b6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[5207],{3905:(e,t,a)=>{a.d(t,{Zo:()=>p,kt:()=>g});var n=a(7294);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function s(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var c=n.createContext({}),l=function(e){var t=n.useContext(c),a=t;return e&&(a="function"==typeof e?e(t):s(s({},t),e)),a},p=function(e){var t=l(e.components);return n.createElement(c.Provider,{value:t},e.children)},d="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},u=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,i=e.originalType,c=e.parentName,p=o(e,["components","mdxType","originalType","parentName"]),d=l(a),u=r,g=d["".concat(c,".").concat(u)]||d[u]||h[u]||i;return a?n.createElement(g,s(s({ref:t},p),{},{components:a})):n.createElement(g,s({ref:t},p))}));function g(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=a.length,s=new Array(i);s[0]=u;var o={};for(var c in t)hasOwnProperty.call(t,c)&&(o[c]=t[c]);o.originalType=e,o[d]="string"==typeof e?e:r,s[1]=o;for(var l=2;l{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>i,metadata:()=>o,toc:()=>l});var n=a(7462),r=(a(7294),a(3905));const i={sidebar_position:2},s="Design Principles",o={unversionedId:"graphcast/design-principles",id:"graphcast/design-principles",title:"Design Principles",description:"There are two main components of Graphcast",source:"@site/docs/graphcast/design-principles.md",sourceDirName:"graphcast",slug:"/graphcast/design-principles",permalink:"/graphcast/design-principles",draft:!1,editUrl:"https://github.com/graphops/docs/edit/main/docs/graphcast/design-principles.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2},sidebar:"gnSidebar",previous:{title:"Introduction",permalink:"/graphcast/intro"},next:{title:"Introduction",permalink:"/graphcast/sdk/intro"}},c={},l=[{value:"The Graphcast SDK",id:"the-graphcast-sdk",level:2},{value:"Radios",id:"radios",level:2},{value:"Features",id:"features",level:3},{value:"Proof of Indexing (POI) cross-checking",id:"proof-of-indexing-poi-cross-checking",level:4},{value:"Subgraph Upgrade Pre-sync",id:"subgraph-upgrade-pre-sync",level:4}],p={toc:l},d="wrapper";function h(e){let{components:t,...a}=e;return(0,r.kt)(d,(0,n.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"design-principles"},"Design Principles"),(0,r.kt)("p",null,"There are two main components of Graphcast"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"The Graphcast SDK: The base layer SDK which interfaces with The Graph stack and the Waku network. This includes interactions with an Ethereum client, a Graph node client, a client for the Indexer management server, the Network subgraph and the Registry subgraph)."),(0,r.kt)("li",{parentName:"ul"},"Radios: Highly customizable gossip applications, built with the help of the Graphcast SDK, which define the specific message formats and logic around constructing and handling the messages. They are the nodes communicating in the Graphcast Network.")),(0,r.kt)("h2",{id:"the-graphcast-sdk"},"The Graphcast SDK"),(0,r.kt)("p",null,"The SDK is the base layer which is used to abstract all the necessary components of each Radio away from the user. That includes:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Establishes a connection to Graphcast via a ",(0,r.kt)("a",{parentName:"li",href:"https://waku.org/"},"Waku")," Gossip node, providing an interface for subscribing to specific topics and broadcasting messages across the network."),(0,r.kt)("li",{parentName:"ul"},"Interactions with a Graph node and a client for the Indexer management server."),(0,r.kt)("li",{parentName:"ul"},"Queries to Network and Registry subgraphs."),(0,r.kt)("li",{parentName:"ul"},"Checks message validity for past message injections, nonexistent blocks and expired timestamps. It also guarantees that messages are signed by an authorised operator address of an active on-chain Indexer (this can be used as a basis for a reputation system)."),(0,r.kt)("li",{parentName:"ul"},"Supports a flexible and customizable configuration of the Graphcast gossip agent, enabling specification of network settings, peer discovery mechanisms, message encoding formats, and more. For detailed instructions on configuring Graphcast to suit your needs, refer to the configuration guide."),(0,r.kt)("li",{parentName:"ul"},"Topics in Graphcast represent different categories or subjects of information. Nodes can dynamically subscribe to specific topics to receive messages related to those topics. Topics enable efficient message routing and dissemination within the network."),(0,r.kt)("li",{parentName:"ul"},"Provides comprehensive message handling structure to ensure that messages are reliably transmitted, received, and processed within the network.")),(0,r.kt)("h2",{id:"radios"},"Radios"),(0,r.kt)("p",null,"General Radio components"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Supports Radio for specific use cases."),(0,r.kt)("li",{parentName:"ul"},"Controls topic subscriptions dynamically for interested topics."),(0,r.kt)("li",{parentName:"ul"},"Provides Radio type definition used to verify the integrity and authenticity of messages exchanged within the network."),(0,r.kt)("li",{parentName:"ul"},"Collects Radio-specific information and incorporates it into Graphcast messages along with other relevant metadata."),(0,r.kt)("li",{parentName:"ul"},"Observes and handles relevant messages received from peers."),(0,r.kt)("li",{parentName:"ul"},"Provides performance metrics, logs, and API services.")),(0,r.kt)("p",null,"The first Radio built on top of Graphcast is the Subgraph Radio. It's designed to facilitate real-time information exchange among participants in The Graph network and serves as a tool for Indexers and other network participants to share valuable Subgraph data."),(0,r.kt)("p",null,"With Subgraph Radio, Indexers can run a single Radio instance and track a wide variety of message types and data related to Subgraphs. Different use cases and message types form the different ",(0,r.kt)("em",{parentName:"p"},"features")," of the Radio."),(0,r.kt)("h3",{id:"features"},"Features"),(0,r.kt)("h4",{id:"proof-of-indexing-poi-cross-checking"},"Proof of Indexing (POI) cross-checking"),(0,r.kt)("p",null,"Indexers must generate valid POIs to earn indexing rewards. Indexers find it beneficial to alert each other on the health status of subgraphs in community discussions. To alleviate the manual workload, the POI cross-checking feature within Subgraph Radio:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Defines message types and topics"),(0,r.kt)("li",{parentName:"ul"},"Collects public POIs from the Graph node and sends them inside of Graphcast messages along with other useful metadata"),(0,r.kt)("li",{parentName:"ul"},"Observes relevant messages and aggregates public POIs sent from other Indexers, in order to compare ",(0,r.kt)("em",{parentName:"li"},"local")," POIs to ",(0,r.kt)("em",{parentName:"li"},"remote")," POIs"),(0,r.kt)("li",{parentName:"ul"},"Monitors the network for conflicts and takes certain actions if needed, for instance Indexers can configure an alert system to send messages to a custom channel in their Slack workspace, a Discord channel, or a Telegram chat.")),(0,r.kt)("h4",{id:"subgraph-upgrade-pre-sync"},"Subgraph Upgrade Pre-sync"),(0,r.kt)("p",null,"The subgraph upgrade pre-sync feature provides a way for Subgraph Developers to signal when they plan on releasing a new subgraph version, thereby allowing Indexers to start syncing the subgraph in advance. If the Radio operator has set up the notification system, they will get notified whenever a new subgraph upgrade intent message is received."),(0,r.kt)("p",null,"If the ",(0,r.kt)("inlineCode",{parentName:"p"},"INDEXER_MANAGEMEN_SERVER_ENDPOINT")," configuration variable has been set, the Radio will send a request to the Indexer Agent to start offchain syncing the new Subgraph deployment."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ea715d66.db2dfa0c.js b/assets/js/ea715d66.db2dfa0c.js deleted file mode 100644 index da38841d..00000000 --- a/assets/js/ea715d66.db2dfa0c.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[5207],{3905:(e,t,a)=>{a.d(t,{Zo:()=>p,kt:()=>g});var n=a(7294);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function s(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var c=n.createContext({}),l=function(e){var t=n.useContext(c),a=t;return e&&(a="function"==typeof e?e(t):s(s({},t),e)),a},p=function(e){var t=l(e.components);return n.createElement(c.Provider,{value:t},e.children)},d="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},u=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,i=e.originalType,c=e.parentName,p=o(e,["components","mdxType","originalType","parentName"]),d=l(a),u=r,g=d["".concat(c,".").concat(u)]||d[u]||h[u]||i;return a?n.createElement(g,s(s({ref:t},p),{},{components:a})):n.createElement(g,s({ref:t},p))}));function g(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=a.length,s=new Array(i);s[0]=u;var o={};for(var c in t)hasOwnProperty.call(t,c)&&(o[c]=t[c]);o.originalType=e,o[d]="string"==typeof e?e:r,s[1]=o;for(var l=2;l{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>i,metadata:()=>o,toc:()=>l});var n=a(7462),r=(a(7294),a(3905));const i={sidebar_position:2},s="Design Principles",o={unversionedId:"graphcast/design-principles",id:"graphcast/design-principles",title:"Design Principles",description:"There are two main components of Graphcast",source:"@site/docs/graphcast/design-principles.md",sourceDirName:"graphcast",slug:"/graphcast/design-principles",permalink:"/graphcast/design-principles",draft:!1,editUrl:"https://github.com/graphops/docs/edit/main/docs/graphcast/design-principles.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2},sidebar:"gnSidebar",previous:{title:"Introduction",permalink:"/graphcast/intro"},next:{title:"Introduction",permalink:"/graphcast/sdk/intro"}},c={},l=[{value:"The Graphcast SDK",id:"the-graphcast-sdk",level:2},{value:"Radios",id:"radios",level:2},{value:"Functionalities",id:"functionalities",level:3},{value:"Proof of Indexing (POI) cross-checking",id:"proof-of-indexing-poi-cross-checking",level:4},{value:"Subgraph Versioning",id:"subgraph-versioning",level:4}],p={toc:l},d="wrapper";function h(e){let{components:t,...a}=e;return(0,r.kt)(d,(0,n.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"design-principles"},"Design Principles"),(0,r.kt)("p",null,"There are two main components of Graphcast"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"The Graphcast SDK: The base layer SDK which interfaces with The Graph stack and the Waku network. This includes interactions with an Ethereum client, a Graph node client, a client for the Indexer management server, the Network subgraph and the Registry subgraph)."),(0,r.kt)("li",{parentName:"ul"},"Radios: Highly customizable gossip applications, built with the help of the Graphcast SDK, which define the specific message formats and logic around constructing and handling the messages. They are the nodes communicating in the Graphcast Network.")),(0,r.kt)("h2",{id:"the-graphcast-sdk"},"The Graphcast SDK"),(0,r.kt)("p",null,"The SDK is the base layer which is used to abstract all the necessary components of each Radio away from the user. That includes:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Establishes a connection to Graphcast via a ",(0,r.kt)("a",{parentName:"li",href:"https://waku.org/"},"Waku")," Gossip node, providing an interface for subscribing to specific topics and broadcasting messages across the network."),(0,r.kt)("li",{parentName:"ul"},"Interactions with an Ethereum node, a Graph node and a client for the Indexer management server."),(0,r.kt)("li",{parentName:"ul"},"Queries to Network and Registry subgraphs."),(0,r.kt)("li",{parentName:"ul"},"Checks message validity for past message injections, nonexistent blocks and expired timestamps. It also guarantees that messages are signed by an authorised operator address of an active on-chain Indexer (this can be used as a basis for a reputation system)."),(0,r.kt)("li",{parentName:"ul"},"Supports a flexible and customizable configuration of the Graphcast gossip agent, enabling specification of network settings, peer discovery mechanisms, message encoding formats, and more. For detailed instructions on configuring Graphcast to suit your needs, refer to the configuration guide."),(0,r.kt)("li",{parentName:"ul"},"Topics in Graphcast represent different categories or subjects of information. Nodes can dynamically subscribe to specific topics to receive messages related to those topics. Topics enable efficient message routing and dissemination within the network."),(0,r.kt)("li",{parentName:"ul"},"Provides comprehensive message handling structure to ensure that messages are reliably transmitted, received, and processed within the network.")),(0,r.kt)("h2",{id:"radios"},"Radios"),(0,r.kt)("p",null,"General Radio components"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Supports Radio for specific use cases."),(0,r.kt)("li",{parentName:"ul"},"Controls topic subscriptions dynamically for interested topics."),(0,r.kt)("li",{parentName:"ul"},"Provides Radio type definition used to verify the integrity and authenticity of messages exchanged within the network."),(0,r.kt)("li",{parentName:"ul"},"Collects Radio-specific information and incorporates it into Graphcast messages along with other relevant metadata."),(0,r.kt)("li",{parentName:"ul"},"Observes and handles relevant messages received from peers."),(0,r.kt)("li",{parentName:"ul"},"Provides performance metrics, logs, and API services.")),(0,r.kt)("p",null,"The first Radio built on top of Graphcast is the Subgraph Radio. It's designed to facilitate real-time information exchange among participants in The Graph network and serves as a tool for Indexers and other network participants to share valuable Subgraph data."),(0,r.kt)("p",null,"With Subgraph Radio, Indexers can run a single Radio instance and track a wide variety of message types and data related to Subgraphs. Different use cases and message types form the different ",(0,r.kt)("em",{parentName:"p"},"functionalities")," of the Radio."),(0,r.kt)("h3",{id:"functionalities"},"Functionalities"),(0,r.kt)("h4",{id:"proof-of-indexing-poi-cross-checking"},"Proof of Indexing (POI) cross-checking"),(0,r.kt)("p",null,"Indexers must generate valid POIs to earn indexing rewards. Indexers find it beneficial to alert each other on the health status of subgraphs in community discussions. To alleviate the manual workload, the POI functionality within Subgraph Radio:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Defines message types and topics"),(0,r.kt)("li",{parentName:"ul"},"Collects public POIs from the Graph node and sends them inside of Graphcast messages along with other useful metadata"),(0,r.kt)("li",{parentName:"ul"},"Observes relevant messages and aggregates public POIs sent from other Indexers, in order to compare ",(0,r.kt)("em",{parentName:"li"},"local")," POIs to ",(0,r.kt)("em",{parentName:"li"},"remote")," POIs"),(0,r.kt)("li",{parentName:"ul"},"Monitors the network for conflicts and takes certain actions if needed, for instance Indexers can configure an alert system to send messages to a custom channel in their Slack workspace, a Discord channel, or a Telegram chat.")),(0,r.kt)("h4",{id:"subgraph-versioning"},"Subgraph Versioning"),(0,r.kt)("p",null,"The subgraph versioning functionality provides a way for Subgraph Developers to signal when they plan on releasing a new subgraph version, thereby allowing Indexers to start syncing the subgraph in advance. If the Radio operator has set up the notification system, they will get notified whenever a new subgraph versioning message is received."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.ed93580e.js b/assets/js/runtime~main.41cb9f4f.js similarity index 94% rename from assets/js/runtime~main.ed93580e.js rename to assets/js/runtime~main.41cb9f4f.js index d0770e73..388d1b88 100644 --- a/assets/js/runtime~main.ed93580e.js +++ b/assets/js/runtime~main.41cb9f4f.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,f,c,t,r={},d={};function b(e){var a=d[e];if(void 0!==a)return a.exports;var f=d[e]={exports:{}};return r[e].call(f.exports,f,f.exports,b),f.exports}b.m=r,e=[],b.O=(a,f,c,t)=>{if(!f){var r=1/0;for(i=0;i=t)&&Object.keys(b.O).every((e=>b.O[e](f[o])))?f.splice(o--,1):(d=!1,t0&&e[i-1][2]>t;i--)e[i]=e[i-1];e[i]=[f,c,t]},b.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return b.d(a,{a:a}),a},f=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,b.t=function(e,c){if(1&c&&(e=this(e)),8&c)return e;if("object"==typeof e&&e){if(4&c&&e.__esModule)return e;if(16&c&&"function"==typeof e.then)return e}var t=Object.create(null);b.r(t);var r={};a=a||[null,f({}),f([]),f(f)];for(var d=2&c&&e;"object"==typeof d&&!~a.indexOf(d);d=f(d))Object.getOwnPropertyNames(d).forEach((a=>r[a]=()=>e[a]));return r.default=()=>e,b.d(t,r),t},b.d=(e,a)=>{for(var f in a)b.o(a,f)&&!b.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:a[f]})},b.f={},b.e=e=>Promise.all(Object.keys(b.f).reduce(((a,f)=>(b.f[f](e,a),a)),[])),b.u=e=>"assets/js/"+({53:"935f2afb",110:"66406991",453:"30a24c52",494:"8ba7f970",533:"b2b675dd",653:"4ac6162a",948:"8717b14a",1263:"d2eb8d4c",1477:"b2f554cd",1613:"a19c92ce",1633:"031793e1",1713:"a7023ddc",1723:"1026ed7f",1914:"d9f32620",2267:"59362658",2362:"e273c56f",2398:"55efb065",2535:"814f3328",3089:"a6aa9e1f",3205:"a80da1cf",3237:"1df93b7f",3514:"73664a40",3608:"9e4087bc",3936:"d5a57370",3997:"6ceb8cd0",4013:"01a85c17",4089:"bd9dd2f9",4439:"422a9904",5119:"931ae2d5",5207:"ea715d66",5252:"6fb70ffc",6103:"ccc49370",6241:"163dca7f",6245:"698cb0b5",6254:"2472ae08",6391:"14464846",6798:"94e1a954",6938:"608ae6a4",7058:"db074018",7178:"096bfee4",7633:"9b790421",7645:"a7434565",7918:"17896441",8271:"1c091541",8610:"6875c492",8613:"4515f2ba",8636:"f4f34a3a",9003:"925b3f96",9035:"4c9e35b1",9334:"247783bb",9423:"e904f572",9514:"1be78505",9635:"86e365b5",9642:"7661071f",9700:"e16015ca",9813:"2c9fa8e7"}[e]||e)+"."+{53:"017dd9f7",110:"479310be",453:"b07fef1c",494:"bcab85d0",533:"0531b767",653:"ecbe0742",948:"78963ab1",1263:"213e9832",1477:"6c07586f",1613:"f9f150df",1633:"68e1d3d8",1713:"23b2ba87",1723:"0794468a",1914:"5a4a9fed",2267:"6401aa21",2362:"cf7cab60",2398:"632c7c3d",2535:"9dbd0ebe",3089:"845cad8c",3205:"e7ede2a4",3237:"b3606ef8",3514:"2a3b45ce",3608:"a696b2b3",3936:"e5dfb980",3997:"bebce4bc",4013:"467cbdf3",4089:"139c5621",4439:"9ec98d23",4972:"73c7d017",5119:"4091f0a9",5207:"db2dfa0c",5252:"42aab89e",6048:"fb9b0605",6103:"d9c41d1e",6241:"400dc67f",6245:"0d885c52",6254:"557def3a",6316:"57f0e7b8",6391:"23f601d3",6798:"f9991880",6938:"a6023127",7058:"30e23342",7178:"f73c46b5",7633:"c35cee2c",7645:"8a3a8041",7724:"a1fda817",7918:"84e372fb",8271:"60b718c6",8610:"f37b7b5c",8613:"86ab55b5",8636:"94f36cf8",8954:"a8cafb86",9003:"35534246",9035:"2be4405b",9334:"dadfb5a6",9423:"9f073984",9487:"20f5186b",9514:"3a6a17fd",9635:"96579636",9642:"aa215b28",9700:"d1830de3",9813:"224a411d"}[e]+".js",b.miniCssF=e=>{},b.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),b.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),c={},t="docs:",b.l=(e,a,f,r)=>{if(c[e])c[e].push(a);else{var d,o;if(void 0!==f)for(var n=document.getElementsByTagName("script"),i=0;i{d.onerror=d.onload=null,clearTimeout(s);var t=c[e];if(delete c[e],d.parentNode&&d.parentNode.removeChild(d),t&&t.forEach((e=>e(f))),a)return a(f)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=l.bind(null,d.onerror),d.onload=l.bind(null,d.onload),o&&document.head.appendChild(d)}},b.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},b.p="/",b.gca=function(e){return e={14464846:"6391",17896441:"7918",59362658:"2267",66406991:"110","935f2afb":"53","30a24c52":"453","8ba7f970":"494",b2b675dd:"533","4ac6162a":"653","8717b14a":"948",d2eb8d4c:"1263",b2f554cd:"1477",a19c92ce:"1613","031793e1":"1633",a7023ddc:"1713","1026ed7f":"1723",d9f32620:"1914",e273c56f:"2362","55efb065":"2398","814f3328":"2535",a6aa9e1f:"3089",a80da1cf:"3205","1df93b7f":"3237","73664a40":"3514","9e4087bc":"3608",d5a57370:"3936","6ceb8cd0":"3997","01a85c17":"4013",bd9dd2f9:"4089","422a9904":"4439","931ae2d5":"5119",ea715d66:"5207","6fb70ffc":"5252",ccc49370:"6103","163dca7f":"6241","698cb0b5":"6245","2472ae08":"6254","94e1a954":"6798","608ae6a4":"6938",db074018:"7058","096bfee4":"7178","9b790421":"7633",a7434565:"7645","1c091541":"8271","6875c492":"8610","4515f2ba":"8613",f4f34a3a:"8636","925b3f96":"9003","4c9e35b1":"9035","247783bb":"9334",e904f572:"9423","1be78505":"9514","86e365b5":"9635","7661071f":"9642",e16015ca:"9700","2c9fa8e7":"9813"}[e]||e,b.p+b.u(e)},(()=>{var e={1303:0,532:0};b.f.j=(a,f)=>{var c=b.o(e,a)?e[a]:void 0;if(0!==c)if(c)f.push(c[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var t=new Promise(((f,t)=>c=e[a]=[f,t]));f.push(c[2]=t);var r=b.p+b.u(a),d=new Error;b.l(r,(f=>{if(b.o(e,a)&&(0!==(c=e[a])&&(e[a]=void 0),c)){var t=f&&("load"===f.type?"missing":f.type),r=f&&f.target&&f.target.src;d.message="Loading chunk "+a+" failed.\n("+t+": "+r+")",d.name="ChunkLoadError",d.type=t,d.request=r,c[1](d)}}),"chunk-"+a,a)}},b.O.j=a=>0===e[a];var a=(a,f)=>{var c,t,r=f[0],d=f[1],o=f[2],n=0;if(r.some((a=>0!==e[a]))){for(c in d)b.o(d,c)&&(b.m[c]=d[c]);if(o)var i=o(b)}for(a&&a(f);n{"use strict";var e,a,f,c,t,r={},d={};function b(e){var a=d[e];if(void 0!==a)return a.exports;var f=d[e]={exports:{}};return r[e].call(f.exports,f,f.exports,b),f.exports}b.m=r,e=[],b.O=(a,f,c,t)=>{if(!f){var r=1/0;for(i=0;i=t)&&Object.keys(b.O).every((e=>b.O[e](f[o])))?f.splice(o--,1):(d=!1,t0&&e[i-1][2]>t;i--)e[i]=e[i-1];e[i]=[f,c,t]},b.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return b.d(a,{a:a}),a},f=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,b.t=function(e,c){if(1&c&&(e=this(e)),8&c)return e;if("object"==typeof e&&e){if(4&c&&e.__esModule)return e;if(16&c&&"function"==typeof e.then)return e}var t=Object.create(null);b.r(t);var r={};a=a||[null,f({}),f([]),f(f)];for(var d=2&c&&e;"object"==typeof d&&!~a.indexOf(d);d=f(d))Object.getOwnPropertyNames(d).forEach((a=>r[a]=()=>e[a]));return r.default=()=>e,b.d(t,r),t},b.d=(e,a)=>{for(var f in a)b.o(a,f)&&!b.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:a[f]})},b.f={},b.e=e=>Promise.all(Object.keys(b.f).reduce(((a,f)=>(b.f[f](e,a),a)),[])),b.u=e=>"assets/js/"+({53:"935f2afb",110:"66406991",453:"30a24c52",494:"8ba7f970",533:"b2b675dd",653:"4ac6162a",948:"8717b14a",1263:"d2eb8d4c",1477:"b2f554cd",1613:"a19c92ce",1633:"031793e1",1713:"a7023ddc",1723:"1026ed7f",1914:"d9f32620",2267:"59362658",2362:"e273c56f",2398:"55efb065",2535:"814f3328",3089:"a6aa9e1f",3205:"a80da1cf",3237:"1df93b7f",3514:"73664a40",3608:"9e4087bc",3936:"d5a57370",3997:"6ceb8cd0",4013:"01a85c17",4089:"bd9dd2f9",4439:"422a9904",5119:"931ae2d5",5207:"ea715d66",5252:"6fb70ffc",6103:"ccc49370",6241:"163dca7f",6245:"698cb0b5",6254:"2472ae08",6391:"14464846",6798:"94e1a954",6938:"608ae6a4",7058:"db074018",7178:"096bfee4",7633:"9b790421",7645:"a7434565",7918:"17896441",8271:"1c091541",8610:"6875c492",8613:"4515f2ba",8636:"f4f34a3a",9003:"925b3f96",9035:"4c9e35b1",9334:"247783bb",9423:"e904f572",9514:"1be78505",9635:"86e365b5",9642:"7661071f",9700:"e16015ca",9813:"2c9fa8e7"}[e]||e)+"."+{53:"815abf75",110:"479310be",453:"b07fef1c",494:"bcab85d0",533:"0531b767",653:"ecbe0742",948:"78963ab1",1263:"213e9832",1477:"6c07586f",1613:"f9f150df",1633:"68e1d3d8",1713:"23b2ba87",1723:"0794468a",1914:"5a4a9fed",2267:"6401aa21",2362:"cf7cab60",2398:"632c7c3d",2535:"9dbd0ebe",3089:"845cad8c",3205:"e7ede2a4",3237:"b3606ef8",3514:"2a3b45ce",3608:"a696b2b3",3936:"e5dfb980",3997:"bebce4bc",4013:"467cbdf3",4089:"139c5621",4439:"9ec98d23",4972:"73c7d017",5119:"4091f0a9",5207:"9b8e85b6",5252:"42aab89e",6048:"fb9b0605",6103:"d9c41d1e",6241:"400dc67f",6245:"0cfa8968",6254:"557def3a",6316:"57f0e7b8",6391:"23f601d3",6798:"f9991880",6938:"a6023127",7058:"e53bd748",7178:"f73c46b5",7633:"c35cee2c",7645:"8a3a8041",7724:"a1fda817",7918:"84e372fb",8271:"60b718c6",8610:"f37b7b5c",8613:"86ab55b5",8636:"94f36cf8",8954:"a8cafb86",9003:"35534246",9035:"2be4405b",9334:"dadfb5a6",9423:"9f073984",9487:"20f5186b",9514:"3a6a17fd",9635:"eaa7086f",9642:"aa215b28",9700:"d1830de3",9813:"224a411d"}[e]+".js",b.miniCssF=e=>{},b.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),b.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),c={},t="docs:",b.l=(e,a,f,r)=>{if(c[e])c[e].push(a);else{var d,o;if(void 0!==f)for(var n=document.getElementsByTagName("script"),i=0;i{d.onerror=d.onload=null,clearTimeout(s);var t=c[e];if(delete c[e],d.parentNode&&d.parentNode.removeChild(d),t&&t.forEach((e=>e(f))),a)return a(f)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=l.bind(null,d.onerror),d.onload=l.bind(null,d.onload),o&&document.head.appendChild(d)}},b.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},b.p="/",b.gca=function(e){return e={14464846:"6391",17896441:"7918",59362658:"2267",66406991:"110","935f2afb":"53","30a24c52":"453","8ba7f970":"494",b2b675dd:"533","4ac6162a":"653","8717b14a":"948",d2eb8d4c:"1263",b2f554cd:"1477",a19c92ce:"1613","031793e1":"1633",a7023ddc:"1713","1026ed7f":"1723",d9f32620:"1914",e273c56f:"2362","55efb065":"2398","814f3328":"2535",a6aa9e1f:"3089",a80da1cf:"3205","1df93b7f":"3237","73664a40":"3514","9e4087bc":"3608",d5a57370:"3936","6ceb8cd0":"3997","01a85c17":"4013",bd9dd2f9:"4089","422a9904":"4439","931ae2d5":"5119",ea715d66:"5207","6fb70ffc":"5252",ccc49370:"6103","163dca7f":"6241","698cb0b5":"6245","2472ae08":"6254","94e1a954":"6798","608ae6a4":"6938",db074018:"7058","096bfee4":"7178","9b790421":"7633",a7434565:"7645","1c091541":"8271","6875c492":"8610","4515f2ba":"8613",f4f34a3a:"8636","925b3f96":"9003","4c9e35b1":"9035","247783bb":"9334",e904f572:"9423","1be78505":"9514","86e365b5":"9635","7661071f":"9642",e16015ca:"9700","2c9fa8e7":"9813"}[e]||e,b.p+b.u(e)},(()=>{var e={1303:0,532:0};b.f.j=(a,f)=>{var c=b.o(e,a)?e[a]:void 0;if(0!==c)if(c)f.push(c[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var t=new Promise(((f,t)=>c=e[a]=[f,t]));f.push(c[2]=t);var r=b.p+b.u(a),d=new Error;b.l(r,(f=>{if(b.o(e,a)&&(0!==(c=e[a])&&(e[a]=void 0),c)){var t=f&&("load"===f.type?"missing":f.type),r=f&&f.target&&f.target.src;d.message="Loading chunk "+a+" failed.\n("+t+": "+r+")",d.name="ChunkLoadError",d.type=t,d.request=r,c[1](d)}}),"chunk-"+a,a)}},b.O.j=a=>0===e[a];var a=(a,f)=>{var c,t,r=f[0],d=f[1],o=f[2],n=0;if(r.some((a=>0!==e[a]))){for(c in d)b.o(d,c)&&(b.m[c]=d[c]);if(o)var i=o(b)}for(a&&a(f);n Blog | GraphOps Docs - +

· One min read
Sébastien Lorber
Yangshun Tay

Docusaurus blogging features are powered by the blog plugin.

Simply add Markdown files (or folders) to the blog directory.

Regular blog authors can be added to authors.yml.

The blog post date can be extracted from filenames, such as:

  • 2019-05-30-welcome.md
  • 2019-05-30-welcome/index.md

A blog post folder can be convenient to co-locate blog post images:

Docusaurus Plushie

The blog supports tags as well!

And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.

· One min read
Gao Wei

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

- + \ No newline at end of file diff --git a/blog/archive.html b/blog/archive.html index 11c56bef..70a22fc8 100644 --- a/blog/archive.html +++ b/blog/archive.html @@ -5,13 +5,13 @@ Archive | GraphOps Docs - + - + \ No newline at end of file diff --git a/blog/first-blog-post.html b/blog/first-blog-post.html index 3b6758cd..03c4605c 100644 --- a/blog/first-blog-post.html +++ b/blog/first-blog-post.html @@ -5,13 +5,13 @@ First Blog Post | GraphOps Docs - +

First Blog Post

· One min read
Gao Wei

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

- + \ No newline at end of file diff --git a/blog/long-blog-post.html b/blog/long-blog-post.html index 84547103..6fd46e10 100644 --- a/blog/long-blog-post.html +++ b/blog/long-blog-post.html @@ -5,13 +5,13 @@ Long Blog Post | GraphOps Docs - +

Long Blog Post

· 3 min read
Endilie Yacop Sucipto

This is the summary of a very long blog post,

Use a <!-- truncate --> comment to limit blog post size in the list view.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

- + \ No newline at end of file diff --git a/blog/mdx-blog-post.html b/blog/mdx-blog-post.html index 72644965..4c7ac3dd 100644 --- a/blog/mdx-blog-post.html +++ b/blog/mdx-blog-post.html @@ -5,13 +5,13 @@ MDX Blog Post | GraphOps Docs - +
- + \ No newline at end of file diff --git a/blog/tags.html b/blog/tags.html index d03ce94c..c848df66 100644 --- a/blog/tags.html +++ b/blog/tags.html @@ -5,13 +5,13 @@ Tags | GraphOps Docs - + - + \ No newline at end of file diff --git a/blog/tags/docusaurus.html b/blog/tags/docusaurus.html index 4488d685..f9e103e4 100644 --- a/blog/tags/docusaurus.html +++ b/blog/tags/docusaurus.html @@ -5,13 +5,13 @@ 4 posts tagged with "docusaurus" | GraphOps Docs - +

4 posts tagged with "docusaurus"

View All Tags

· One min read
Sébastien Lorber
Yangshun Tay

Docusaurus blogging features are powered by the blog plugin.

Simply add Markdown files (or folders) to the blog directory.

Regular blog authors can be added to authors.yml.

The blog post date can be extracted from filenames, such as:

  • 2019-05-30-welcome.md
  • 2019-05-30-welcome/index.md

A blog post folder can be convenient to co-locate blog post images:

Docusaurus Plushie

The blog supports tags as well!

And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.

· One min read
Gao Wei

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

- + \ No newline at end of file diff --git a/blog/tags/facebook.html b/blog/tags/facebook.html index 3664807d..81164ee2 100644 --- a/blog/tags/facebook.html +++ b/blog/tags/facebook.html @@ -5,13 +5,13 @@ One post tagged with "facebook" | GraphOps Docs - +

One post tagged with "facebook"

View All Tags

· One min read
Sébastien Lorber
Yangshun Tay

Docusaurus blogging features are powered by the blog plugin.

Simply add Markdown files (or folders) to the blog directory.

Regular blog authors can be added to authors.yml.

The blog post date can be extracted from filenames, such as:

  • 2019-05-30-welcome.md
  • 2019-05-30-welcome/index.md

A blog post folder can be convenient to co-locate blog post images:

Docusaurus Plushie

The blog supports tags as well!

And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.

- + \ No newline at end of file diff --git a/blog/tags/hello.html b/blog/tags/hello.html index cc6fc34d..d0a10147 100644 --- a/blog/tags/hello.html +++ b/blog/tags/hello.html @@ -5,13 +5,13 @@ 2 posts tagged with "hello" | GraphOps Docs - +

2 posts tagged with "hello"

View All Tags

· One min read
Sébastien Lorber
Yangshun Tay

Docusaurus blogging features are powered by the blog plugin.

Simply add Markdown files (or folders) to the blog directory.

Regular blog authors can be added to authors.yml.

The blog post date can be extracted from filenames, such as:

  • 2019-05-30-welcome.md
  • 2019-05-30-welcome/index.md

A blog post folder can be convenient to co-locate blog post images:

Docusaurus Plushie

The blog supports tags as well!

And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.

- + \ No newline at end of file diff --git a/blog/tags/hola.html b/blog/tags/hola.html index 898c1b47..bad596b0 100644 --- a/blog/tags/hola.html +++ b/blog/tags/hola.html @@ -5,13 +5,13 @@ One post tagged with "hola" | GraphOps Docs - +

One post tagged with "hola"

View All Tags

· One min read
Gao Wei

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

- + \ No newline at end of file diff --git a/blog/welcome.html b/blog/welcome.html index e7aee2ee..9b649e4c 100644 --- a/blog/welcome.html +++ b/blog/welcome.html @@ -5,13 +5,13 @@ Welcome | GraphOps Docs - +

Welcome

· One min read
Sébastien Lorber
Yangshun Tay

Docusaurus blogging features are powered by the blog plugin.

Simply add Markdown files (or folders) to the blog directory.

Regular blog authors can be added to authors.yml.

The blog post date can be extracted from filenames, such as:

  • 2019-05-30-welcome.md
  • 2019-05-30-welcome/index.md

A blog post folder can be convenient to co-locate blog post images:

Docusaurus Plushie

The blog supports tags as well!

And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.

- + \ No newline at end of file diff --git a/graphcast/design-principles.html b/graphcast/design-principles.html index dae265c0..25dac742 100644 --- a/graphcast/design-principles.html +++ b/graphcast/design-principles.html @@ -5,13 +5,13 @@ Design Principles | GraphOps Docs - +
-

Design Principles

There are two main components of Graphcast

  • The Graphcast SDK: The base layer SDK which interfaces with The Graph stack and the Waku network. This includes interactions with an Ethereum client, a Graph node client, a client for the Indexer management server, the Network subgraph and the Registry subgraph).
  • Radios: Highly customizable gossip applications, built with the help of the Graphcast SDK, which define the specific message formats and logic around constructing and handling the messages. They are the nodes communicating in the Graphcast Network.

The Graphcast SDK

The SDK is the base layer which is used to abstract all the necessary components of each Radio away from the user. That includes:

  • Establishes a connection to Graphcast via a Waku Gossip node, providing an interface for subscribing to specific topics and broadcasting messages across the network.
  • Interactions with an Ethereum node, a Graph node and a client for the Indexer management server.
  • Queries to Network and Registry subgraphs.
  • Checks message validity for past message injections, nonexistent blocks and expired timestamps. It also guarantees that messages are signed by an authorised operator address of an active on-chain Indexer (this can be used as a basis for a reputation system).
  • Supports a flexible and customizable configuration of the Graphcast gossip agent, enabling specification of network settings, peer discovery mechanisms, message encoding formats, and more. For detailed instructions on configuring Graphcast to suit your needs, refer to the configuration guide.
  • Topics in Graphcast represent different categories or subjects of information. Nodes can dynamically subscribe to specific topics to receive messages related to those topics. Topics enable efficient message routing and dissemination within the network.
  • Provides comprehensive message handling structure to ensure that messages are reliably transmitted, received, and processed within the network.

Radios

General Radio components

  • Supports Radio for specific use cases.
  • Controls topic subscriptions dynamically for interested topics.
  • Provides Radio type definition used to verify the integrity and authenticity of messages exchanged within the network.
  • Collects Radio-specific information and incorporates it into Graphcast messages along with other relevant metadata.
  • Observes and handles relevant messages received from peers.
  • Provides performance metrics, logs, and API services.

The first Radio built on top of Graphcast is the Subgraph Radio. It's designed to facilitate real-time information exchange among participants in The Graph network and serves as a tool for Indexers and other network participants to share valuable Subgraph data.

With Subgraph Radio, Indexers can run a single Radio instance and track a wide variety of message types and data related to Subgraphs. Different use cases and message types form the different functionalities of the Radio.

Functionalities

Proof of Indexing (POI) cross-checking

Indexers must generate valid POIs to earn indexing rewards. Indexers find it beneficial to alert each other on the health status of subgraphs in community discussions. To alleviate the manual workload, the POI functionality within Subgraph Radio:

  • Defines message types and topics
  • Collects public POIs from the Graph node and sends them inside of Graphcast messages along with other useful metadata
  • Observes relevant messages and aggregates public POIs sent from other Indexers, in order to compare local POIs to remote POIs
  • Monitors the network for conflicts and takes certain actions if needed, for instance Indexers can configure an alert system to send messages to a custom channel in their Slack workspace, a Discord channel, or a Telegram chat.

Subgraph Versioning

The subgraph versioning functionality provides a way for Subgraph Developers to signal when they plan on releasing a new subgraph version, thereby allowing Indexers to start syncing the subgraph in advance. If the Radio operator has set up the notification system, they will get notified whenever a new subgraph versioning message is received.

- +

Design Principles

There are two main components of Graphcast

  • The Graphcast SDK: The base layer SDK which interfaces with The Graph stack and the Waku network. This includes interactions with an Ethereum client, a Graph node client, a client for the Indexer management server, the Network subgraph and the Registry subgraph).
  • Radios: Highly customizable gossip applications, built with the help of the Graphcast SDK, which define the specific message formats and logic around constructing and handling the messages. They are the nodes communicating in the Graphcast Network.

The Graphcast SDK

The SDK is the base layer which is used to abstract all the necessary components of each Radio away from the user. That includes:

  • Establishes a connection to Graphcast via a Waku Gossip node, providing an interface for subscribing to specific topics and broadcasting messages across the network.
  • Interactions with a Graph node and a client for the Indexer management server.
  • Queries to Network and Registry subgraphs.
  • Checks message validity for past message injections, nonexistent blocks and expired timestamps. It also guarantees that messages are signed by an authorised operator address of an active on-chain Indexer (this can be used as a basis for a reputation system).
  • Supports a flexible and customizable configuration of the Graphcast gossip agent, enabling specification of network settings, peer discovery mechanisms, message encoding formats, and more. For detailed instructions on configuring Graphcast to suit your needs, refer to the configuration guide.
  • Topics in Graphcast represent different categories or subjects of information. Nodes can dynamically subscribe to specific topics to receive messages related to those topics. Topics enable efficient message routing and dissemination within the network.
  • Provides comprehensive message handling structure to ensure that messages are reliably transmitted, received, and processed within the network.

Radios

General Radio components

  • Supports Radio for specific use cases.
  • Controls topic subscriptions dynamically for interested topics.
  • Provides Radio type definition used to verify the integrity and authenticity of messages exchanged within the network.
  • Collects Radio-specific information and incorporates it into Graphcast messages along with other relevant metadata.
  • Observes and handles relevant messages received from peers.
  • Provides performance metrics, logs, and API services.

The first Radio built on top of Graphcast is the Subgraph Radio. It's designed to facilitate real-time information exchange among participants in The Graph network and serves as a tool for Indexers and other network participants to share valuable Subgraph data.

With Subgraph Radio, Indexers can run a single Radio instance and track a wide variety of message types and data related to Subgraphs. Different use cases and message types form the different features of the Radio.

Features

Proof of Indexing (POI) cross-checking

Indexers must generate valid POIs to earn indexing rewards. Indexers find it beneficial to alert each other on the health status of subgraphs in community discussions. To alleviate the manual workload, the POI cross-checking feature within Subgraph Radio:

  • Defines message types and topics
  • Collects public POIs from the Graph node and sends them inside of Graphcast messages along with other useful metadata
  • Observes relevant messages and aggregates public POIs sent from other Indexers, in order to compare local POIs to remote POIs
  • Monitors the network for conflicts and takes certain actions if needed, for instance Indexers can configure an alert system to send messages to a custom channel in their Slack workspace, a Discord channel, or a Telegram chat.

Subgraph Upgrade Pre-sync

The subgraph upgrade pre-sync feature provides a way for Subgraph Developers to signal when they plan on releasing a new subgraph version, thereby allowing Indexers to start syncing the subgraph in advance. If the Radio operator has set up the notification system, they will get notified whenever a new subgraph upgrade intent message is received.

If the INDEXER_MANAGEMEN_SERVER_ENDPOINT configuration variable has been set, the Radio will send a request to the Indexer Agent to start offchain syncing the new Subgraph deployment.

+ \ No newline at end of file diff --git a/graphcast/intro.html b/graphcast/intro.html index fc622280..416e3e57 100644 --- a/graphcast/intro.html +++ b/graphcast/intro.html @@ -5,13 +5,13 @@ Introduction | GraphOps Docs - +

Introduction

Is there something you'd like to learn from or share with your fellow Indexers in an automated manner, but it's too much hassle or costs too much gas?

Currently, the cost to broadcast information to other network participants is determined by gas fees on the Ethereum blockchain. Graphcast solves this problem by acting as an optional decentralized, distributed peer-to-peer (P2P) communication tool that allows Indexers across the network to exchange information in real time. The cost of exchanging P2P messages is near zero, with the tradeoff of no data integrity guarantees. Nevertheless, Graphcast aims to provide message validity guarantees (i.e. that the message is valid and signed by a known protocol participant) with an open design space of reputation models.

The Graphcast SDK (Software Development Kit) allows developers to build Radios, which are gossip-powered applications that Indexers can run to serve a given purpose. We also intend to create a few Radios (or provide support to other developers/teams that wish to build Radios) for the following uses cases:

  • Real-time cross-checking of subgraph data integrity, with active bail-out in the case of diverging from stake-weighted POI consensus.
  • Conducting auctions and coordination for warp syncing subgraphs, substreams, and Firehose data from other Indexers.
  • Self-reporting on active query analytics, including subgraph request volumes, fee volumes, etc.
  • Self-reporting on indexing analytics, including subgraph indexing time, handler gas costs, indexing errors encountered, etc.
  • Self-reporting on stack information including graph-node version, Postgres version, Ethereum client version, etc.

Learn more

If you want to find out more about the initial idea behind Graphcast, as well as stay up to date with the newest developments, keep an eye on the GRC post on The Graph Forum, or join the Graphcast Discord channel.

Contributing

We welcome and appreciate your contributions! 🤝 ➡️ Graphcast SDK

- + \ No newline at end of file diff --git a/graphcast/radios/listener-radio.html b/graphcast/radios/listener-radio.html index 55e1707a..a6103772 100644 --- a/graphcast/radios/listener-radio.html +++ b/graphcast/radios/listener-radio.html @@ -5,13 +5,13 @@ Listener Radio | GraphOps Docs - +
-

Listener Radio

The source code for Listener Radio is available on GitHub and Docker builds are automatically published as GitHub Packages.

Introduction

This Radio shall monitor Graphcast network by the pubsub topic of graphcast-v[version]-[network]. The Radio will not send messages to the network, but instead will record the messages and generate basic metrics for network monitoring.

Graphcast network is a complex system with numerous nodes and connections, and monitoring it is crucial for maintaining its performance, identifying potential issues, and ensuring its robustness and reliability.

  • Performance Optimization: to identify bottlenecks and areas of inefficiency.
  • Troubleshooting: to quickly diagnose issues within the network, reducing downtime and improving reliability.
  • Security: to immediately detect any unusual activity that might indicate a security breach.
  • Planning and Forecasting: Record valuable data that can be used for planning and forecasting purposes, helping us to make informed decisions about the network's future.

Quick Start

  • Ensure a running Postgres instance
  • Set Postgres url to DATABASE_URL in .env
  • Set general GraphcastAgent environmental variables shown in the below table
  • cargo run from source code (later should use Github actions to build source and dockerize

Basic Configuration

You will need to prepare the following environment variables:

NameDescription and examples
DATABASE_URLPostgres Database URL. The tool comes with automatic database migration, database url passed in must be exist and can be connected.
Example: postgresql://[username]:[password]@[pg_host]:[pg_port]/[db_name]
PRIVATE_KEYPrivate key to the Graphcast ID wallet (Precendence over mnemonics).
Example: 0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
GRAPH_NODE_STATUS_ENDPOINTURL to a Graph Node Indexing Status endpoint.
Example: http://index-node:8030/graphql
REGISTRY_SUBGRAPHURL to the Graphcast Registry subgraph for your network. Check APIs for your preferred network
NETWORK_SUBGRAPHURL to the Graph Network subgraph. Check APIs for your preferred network
GRAPHCAST_NETWORKThe Graphcast Messaging fleet and pubsub namespace to use.
Mainnet: mainnet
Goerli: testnet

Example message table

idmessage
1{"nonce": 1686182179, "network": "mainnet", "payload": {"content": "0x3f...", "identifier": "QmVhiE4nax9i86UBnBmQCYDzvjWuwHShYh7aspGPQhU5Sj"}, "signature": "dff1...", "block_hash": "276e...", "identifier": "QmVhiE4nax9i86UBnBmQCYDzvjWuwHShYh7aspGPQhU5Sj", "block_number": 17431860}
2{"nonce": 1686182183, "network": "goerli", "payload": {"content": "0xc0...", "identifier": "QmacQnSgia4iDPWHpeY6aWxesRFdb8o5DKZUx96zZqEWrB"}, "signature": "dbd2...", "block_hash": "0198...", "identifier": "QmacQnSgia4iDPWHpeY6aWxesRFdb8o5DKZUx96zZqEWrB", "block_number": 9140860}
......

Advanced Configuration

In the configuration table below is the full list of environment variables you can set, along with example values.

See Basic Configuration above. The following environment variables are optional:

Name (Optional variables)Description and examples
MNEMONICMnemonic to the Graphcast ID wallet (first address of the wallet is used; Only one of PRIVATE_KEY or MNEMONIC is needed). Example: claptrap armchair violin...
COLLECT_MESSAGE_DURATIONSeconds that the Subgraph Radio will wait to collect remote POI attestations before making a comparison with the local POI. Example: 120 for 2 minutes.
COVERAGEToggle for topic coverage level. Possible values: "comprehensive", "on-chain", "minimal". Default is set to "on-chain" coverage.
TOPICSComma separated static list of content topics (subgraphs) to subscribe to. Example: QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz,QmUwCFhXM3f6qH9Ls9Y6gDNURBH7mxsn6JcectgxAz6CwU,QmQ1Lyh3U6YgVP6YX1RgRz6c8GmKkEpokLwPvEtJx6cF1y
WAKU_HOSTInterface onto which to bind the bundled Waku node. Example: 0.0.0.0
WAKU_PORTP2P port on which the bundled Waku node will operate. Example: 60000
WAKU_NODE_KEYStatic Waku Node Key.
BOOT_NODE_ADDRESSESPeer addresses to use as Waku boot nodes. Example: "addr1, addr2, addr3"
SLACK_TOKENSlack Token to use for notifications. Example: xoxp-0123456789-0123456789-0123456789-0123456789
TELEGRAM_TOKENTelegram Bot Token to use for notifications. Example: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
TELEGRAM_CHAT_IDThe ID of the Telegram chat to send messages to. Example: -1001234567890
SLACK_CHANNELName of Slack channel to send messages to (has to be a public channel). Example: poir-notifications
WAKU_LOG_LEVELWaku node logging configuration. Example: INFO (is also the default)
RUST_LOGRust tracing configuration. Example: graphcast_sdk=debug,subgraph_radio=debug, defaults to info for everything
DISCORD_WEBHOOKDiscord webhook URL for notifications. Example: https://discord.com/api/webhooks/123456789012345678/AbCDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN
METRICS_PORTIf set, the Radio will expose Prometheus metrics on this (off by default). Example: 3001
METRICS_HOSTIf set, the Radio will expose Prometheus metrics on this (off by default). Example: 0.0.0.0
SERVER_HOSTIf SERVER_PORT is set, the Radio will expose an API service on the given host and port. Default: 0.0.0.0
SERVER_PORTIf set, the Radio will expose an API service on the given port (off by default). Example: 8080
LOG_FORMATOptions: pretty - verbose and human readable; json - not verbose and parsable; compact - not verbose and not parsable; full - verbose and not parsible. Default value: pretty.
PERSISTENCE_FILE_PATHRelative path. If set, the Radio will periodically store states of the program to the file in json format (off by default).
DISCV5_ENRSComma separated ENRs for Waku Discv5 bootstrapping. Defaults to empty list.
DISCV5_PORTDiscoverable UDP port. Default: 9000
ID_VALIDATIONDefines the level of validation for message signers used during radio operation. Options include: no-check, valid-address, graphcast-registered, graph-network-account, registered-indexer, indexer

Configurations explained

COVERAGE (topic)

COVERAGE is used to specify the topic coverage level. It controls the range of topics (subgraph ipfs hashes) the Indexer subscribes to in order to process data and participate in the network.

There are three coverage levels available:

  • comprehensive: Subscribe to on-chain topics, user-defined static topics, and subgraph deployments syncing on graph node. This level is useful for Indexers who want to compare public POIs for all deployments syncing on their graph node even if they don't have an active allocations open (their stake will not be taken into account in attestation).
  • on-chain: Subscribe to on-chain topics and user-defined static topics. This is the default coverage level and is suitable for indexers who only want to compare data for deployments with active allocations.
  • minimal: Only subscribe to user-defined static topics. This level is for Indexers who want to limit their participation to specific topics of interest.

Identity validaiton

ID_VALIDATION is used to define level of validation for message signers used during radio operation.

Available Options:

  • no-check: does not perform check on the message signature and does not verify the signer.
  • valid-address: checks the signer to be a valid Ethereum address.
  • graphcast-registered: checks the signer to be registered on Graphcast Registry.
  • graph-network-account: checks the signer to be a Graph account.
  • registered-indexer: checks the signer to be registered on Graphcast Registry and corresponds to an Indexer that satisfies the minimum stake requirement.
  • indexer: checks the signer to be either registered on Graphcast Registry or to be a Graph Account, and corresponds to an Indexer satisfying the minimum stake requirement.

Gossip protocol

WAKU_HOST and WAKU_PORT specify where the bundled Waku node runs. If you want to run multiple Radios, or multiple instances of the same Radio, you should run them on different ports.

If you want to customize the log level, you can toggle RUST_LOG environment variable. Here's an example configuration to get more verbose logging:

RUST_LOG="warn,hyper=warn,graphcast_sdk=debug,subgraph_radio=debug"

Discv5 is an ambient node discovery network for establishing a decentralized network of interconnected Graphcast Radios. Discv5, when used in Graphcast Radios, serves as a dedicated peer-to-peer discovery protocol that empowers Radios to form an efficient, decentralized network. Without Discv5, the traffic within the Graphcast network would largely rely on centrally hosted boot nodes, leading to a less distributed architecture. However, with Discv5, Radios are capable of directly routing messages among themselves, significantly enhancing network decentralization and reducing reliance on the central nodes. If you want to learn more about Discv5, check out the official spec.

Monitoring the Radio

Prometheus & Grafana

The exposed metrics can be scraped by a Prometheus server and displayed in Grafana. In order to use them you have to have a local Prometheus server running and scraping metrics on the provided port. You can specify the metrics host and port by using the environment variables METRICS_PORT and METRICS_HOST.

HTTP Server

The Radio spins up an HTTP server with a GraphQL API when SERVER_HOST and SERVER_PORT environment variables are set. The supported routes are:

  • /health for health status
  • /api/v1/graphql for GET and POST requests with GraphQL playground interface

The GraphQL API now includes:

Below are an example query:

Query {
rows{
id
message{
nonce
network
payload{
content
}
}
}

messages{
identifier
nonce
network
blockNumber
blockHash
signature
payload{
identifier
content
}
}
}

example mutation:

mutation{
deleteMessage(id:1)
}
- +

Listener Radio

The source code for Listener Radio is available on GitHub and Docker builds are automatically published as GitHub Packages.

Introduction

This Radio shall monitor Graphcast network by the pubsub topic of graphcast-v[version]-[network]. The Radio will not send messages to the network, but instead will record the messages and generate basic metrics for network monitoring.

Graphcast network is a complex system with numerous nodes and connections, and monitoring it is crucial for maintaining its performance, identifying potential issues, and ensuring its robustness and reliability.

  • Performance Optimization: to identify bottlenecks and areas of inefficiency.
  • Troubleshooting: to quickly diagnose issues within the network, reducing downtime and improving reliability.
  • Security: to immediately detect any unusual activity that might indicate a security breach.
  • Planning and Forecasting: Record valuable data that can be used for planning and forecasting purposes, helping us to make informed decisions about the network's future.

Quick Start

  • Ensure a running Postgres instance
  • Set Postgres url to DATABASE_URL in .env
  • Set general GraphcastAgent environmental variables shown in the below table
  • cargo run from source code (later should use Github actions to build source and dockerize

Basic Configuration

You will need to prepare the following environment variables:

NameDescription and examples
DATABASE_URLPostgres Database URL. The tool comes with automatic database migration, database url passed in must be exist and can be connected.
Example: postgresql://[username]:[password]@[pg_host]:[pg_port]/[db_name]
PRIVATE_KEYPrivate key to the Graphcast ID wallet (Precendence over mnemonics).
Example: 0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
GRAPH_NODE_STATUS_ENDPOINTURL to a Graph Node Indexing Status endpoint.
Example: http://index-node:8030/graphql
REGISTRY_SUBGRAPHURL to the Graphcast Registry subgraph for your network. Check APIs for your preferred network
NETWORK_SUBGRAPHURL to the Graph Network subgraph. Check APIs for your preferred network
GRAPHCAST_NETWORKThe Graphcast Messaging fleet and pubsub namespace to use.
Mainnet: mainnet
Goerli: testnet

Example message table

idmessage
1{"nonce": 1686182179, "network": "mainnet", "payload": {"content": "0x3f...", "identifier": "QmVhiE4nax9i86UBnBmQCYDzvjWuwHShYh7aspGPQhU5Sj"}, "signature": "dff1...", "block_hash": "276e...", "identifier": "QmVhiE4nax9i86UBnBmQCYDzvjWuwHShYh7aspGPQhU5Sj", "block_number": 17431860}
2{"nonce": 1686182183, "network": "goerli", "payload": {"content": "0xc0...", "identifier": "QmacQnSgia4iDPWHpeY6aWxesRFdb8o5DKZUx96zZqEWrB"}, "signature": "dbd2...", "block_hash": "0198...", "identifier": "QmacQnSgia4iDPWHpeY6aWxesRFdb8o5DKZUx96zZqEWrB", "block_number": 9140860}
......

Advanced Configuration

In the configuration table below is the full list of environment variables you can set, along with example values.

See Basic Configuration above. The following environment variables are optional:

Name (Optional variables)Description and examples
MNEMONICMnemonic to the Graphcast ID wallet (first address of the wallet is used; Only one of PRIVATE_KEY or MNEMONIC is needed). Example: claptrap armchair violin...
COLLECT_MESSAGE_DURATIONSeconds that the Subgraph Radio will wait to collect remote POI attestations before making a comparison with the local POI. Example: 120 for 2 minutes.
COVERAGEToggle for topic coverage level. Possible values: "comprehensive", "on-chain", "minimal". Default is set to "on-chain" coverage.
TOPICSComma separated static list of content topics (subgraphs) to subscribe to. Example: QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz,QmUwCFhXM3f6qH9Ls9Y6gDNURBH7mxsn6JcectgxAz6CwU,QmQ1Lyh3U6YgVP6YX1RgRz6c8GmKkEpokLwPvEtJx6cF1y
WAKU_HOSTInterface onto which to bind the bundled Waku node. Example: 0.0.0.0
WAKU_PORTP2P port on which the bundled Waku node will operate. Example: 60000
WAKU_NODE_KEYStatic Waku Node Key.
BOOT_NODE_ADDRESSESPeer addresses to use as Waku boot nodes. Example: "addr1, addr2, addr3"
SLACK_TOKENSlack Token to use for notifications. Example: xoxp-0123456789-0123456789-0123456789-0123456789
TELEGRAM_TOKENTelegram Bot Token to use for notifications. Example: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
TELEGRAM_CHAT_IDThe ID of the Telegram chat to send messages to. Example: -1001234567890
SLACK_CHANNELName of Slack channel to send messages to (has to be a public channel). Example: poir-notifications
WAKU_LOG_LEVELWaku node logging configuration. Example: INFO (is also the default)
RUST_LOGRust tracing configuration. Example: graphcast_sdk=debug,subgraph_radio=debug, defaults to info for everything
DISCORD_WEBHOOKDiscord webhook URL for notifications. Example: https://discord.com/api/webhooks/123456789012345678/AbCDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN
METRICS_PORTIf set, the Radio will expose Prometheus metrics on this (off by default). Example: 3001
METRICS_HOSTIf set, the Radio will expose Prometheus metrics on this (off by default). Example: 0.0.0.0
SERVER_HOSTIf SERVER_PORT is set, the Radio will expose an API service on the given host and port. Default: 0.0.0.0
SERVER_PORTIf set, the Radio will expose an API service on the given port (off by default). Example: 8080
LOG_FORMATOptions: pretty - verbose and human readable; json - not verbose and parsable; compact - not verbose and not parsable; full - verbose and not parsible. Default value: pretty.
PERSISTENCE_FILE_PATHRelative path. If set, the Radio will periodically store states of the program to the file in json format (off by default).
DISCV5_ENRSComma separated ENRs for Waku Discv5 bootstrapping. Defaults to empty list.
DISCV5_PORTDiscoverable UDP port. Default: 9000
ID_VALIDATIONDefines the level of validation for message signers used during radio operation. Options include: no-check, valid-address, graphcast-registered, graph-network-account, registered-indexer, indexer

Configurations explained

COVERAGE (topic)

COVERAGE is used to specify the topic coverage level. It controls the range of topics (subgraph ipfs hashes) the Indexer subscribes to in order to process data and participate in the network.

There are three coverage levels available:

  • comprehensive: Subscribe to on-chain topics, user-defined static topics, and subgraph deployments syncing on graph node. This level is useful for Indexers who want to compare public POIs for all deployments syncing on their graph node even if they don't have an active allocations open (their stake will not be taken into account in attestation).
  • on-chain: Subscribe to on-chain topics and user-defined static topics. This is the default coverage level and is suitable for indexers who only want to compare data for deployments with active allocations.
  • minimal: Only subscribe to user-defined static topics. This level is for Indexers who want to limit their participation to specific topics of interest.

Identity validaiton

ID_VALIDATION is used to define level of validation for message signers used during radio operation.

Available Options:

  • no-check: does not perform check on the message signature and does not verify the signer.
  • valid-address: checks the signer to be a valid Ethereum address.
  • graphcast-registered: checks the signer to be registered on Graphcast Registry.
  • graph-network-account: checks the signer to be a Graph account.
  • registered-indexer: checks the signer to be registered on Graphcast Registry and corresponds to an Indexer that satisfies the minimum stake requirement.
  • indexer: checks the signer to be either registered on Graphcast Registry or to be a Graph Account, and corresponds to an Indexer satisfying the minimum stake requirement.

Gossip protocol

WAKU_HOST and WAKU_PORT specify where the bundled Waku node runs. If you want to run multiple Radios, or multiple instances of the same Radio, you should run them on different ports.

If you want to customize the log level, you can toggle RUST_LOG environment variable. Here's an example configuration to get more verbose logging:

RUST_LOG="warn,hyper=warn,graphcast_sdk=debug,subgraph_radio=debug"

Discv5 is an ambient node discovery network for establishing a decentralized network of interconnected Graphcast Radios. Discv5, when used in Graphcast Radios, serves as a dedicated peer-to-peer discovery protocol that empowers Radios to form an efficient, decentralized network. Without Discv5, the traffic within the Graphcast network would largely rely on centrally hosted boot nodes, leading to a less distributed architecture. However, with Discv5, Radios are capable of directly routing messages among themselves, significantly enhancing network decentralization and reducing reliance on the central nodes. If you want to learn more about Discv5, check out the official spec.

Monitoring the Radio

Prometheus & Grafana

The exposed metrics can be scraped by a Prometheus server and displayed in Grafana. In order to use them you have to have a local Prometheus server running and scraping metrics on the provided port. You can specify the metrics host and port by using the environment variables METRICS_PORT and METRICS_HOST.

HTTP Server

The Radio spins up an HTTP server with a GraphQL API when SERVER_HOST and SERVER_PORT environment variables are set. The supported routes are:

  • /health for health status
  • /api/v1/graphql for GET and POST requests with GraphQL playground interface

The GraphQL API now includes:

Below are an example query:

query {
rows {
id
message {
nonce
network
payload {
content
}
}
}

messages {
identifier
nonce
network
blockNumber
blockHash
signature
payload {
identifier
content
}
}
}

example mutation:

mutation{
deleteMessage(id:1)
}
+ \ No newline at end of file diff --git a/graphcast/radios/one-shot.html b/graphcast/radios/one-shot.html index 4e4c26cf..51248254 100644 --- a/graphcast/radios/one-shot.html +++ b/graphcast/radios/one-shot.html @@ -3,15 +3,15 @@ -One-shot CLI | GraphOps Docs +One-shot CLI | GraphOps Docs - +
-

One-shot CLI

The source code for the one-shot CLI is available on GitHub as a member of the Subgraph Radio workspace.

Introduction

The one-shot CLI enables sending one-off messages. Currently, its default behaviour is to construct and send a message of type VersionUpgradeMessage, which is used for the Subgraph Versioning functionality of Subgraph Radio.

The one-shot CLI is configured using config variables. You will need to prepare the following config variables (either as env variables or passing CLI args when running the CLI):

NameDescription and Examples
PRIVATE_KEYPrivate key to the Graphcast ID wallet (precendence over mnemonics).
Example: PRIVATE_KEY=YOUR_PRIVATE_KEY
MNEMONICMnemonic to the Graphcast ID wallet (first address of the wallet is used; Only one of private key or mnemonic is needed).
Example: MNEMONIC=YOUR_MNEMONIC
GRAPH_ACCOUNTGraph account corresponding to Graphcast operator.
Example: GRAPH_ACCOUNT=YOUR_GRAPH_ACCOUNT
REGISTRY_SUBGRAPHSubgraph endpoint to the Graphcast Registry.
Default: https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-goerli
NETWORK_SUBGRAPHSubgraph endpoint to The Graph network subgraph.
Default: https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli
GRAPHCAST_NETWORKSupported Graphcast networks: mainnet, testnet.
Default: testnet
IDENTIFIERSubgraph deployment hash is used to be the message content identifier.
Example: IDENTIFIER=YOUR_IDENTIFIER
NEW_HASHSubgraph deployment hash for the upgrade version of the subgraph.
Example: NEW_HASH=YOUR_NEW_HASH
SUBGRAPH_IDSubgraph id shared by the old and new deployment.
Example: SUBGRAPH_ID=YOUR_SUBGRAPH_ID
INDEX_NETWORKSubgraph id shared by the old and new deployment.
Example: INDEX_NETWORK=YOUR_INDEX_NETWORK
MIGRATION_TIMEUNIX timestamp that the developer plan on migrating the usage.
Example: MIGRATION_TIME=YOUR_MIGRATION_TIME
ID_VALIDATIONIdentity validation mechanism for message signers (options: no-check, valid-address, graphcast-registered, graph-network-account, registered-indexer, indexer).
Example: ID_VALIDATION=valid-address
LOG_LEVELLogging configuration to set as RUST_LOG.
Default: info
LOG_FORMATSupport logging formats: pretty, json, full, compact.
Default: pretty

The one-shot CLI code is very extensible and could be altered to send any kind of Graphcast-compatible message to the network.

- +

One-shot CLI

The source code for the one-shot CLI is available on GitHub.

Introduction

The one-shot CLI enables sending one-off messages. Currently, its default behaviour is to construct and send a message of type VersionUpgradeMessage, which is used for the Subgraph Upgrade Pre-sync feature of Subgraph Radio.

The one-shot CLI is configured using config variables. You will need to prepare the following config variables (either as env variables or passing CLI args when running the CLI):

NameDescription and Examples
PRIVATE_KEYPrivate key to the Graphcast ID wallet (precendence over mnemonics).
Example: PRIVATE_KEY=YOUR_PRIVATE_KEY
MNEMONICMnemonic to the Graphcast ID wallet (first address of the wallet is used; Only one of private key or mnemonic is needed).
Example: MNEMONIC=YOUR_MNEMONIC
GRAPH_ACCOUNTGraph account corresponding to Graphcast operator.
Example: GRAPH_ACCOUNT=YOUR_GRAPH_ACCOUNT
REGISTRY_SUBGRAPHSubgraph endpoint to the Graphcast Registry.
Default: https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-goerli
NETWORK_SUBGRAPHSubgraph endpoint to The Graph network subgraph.
Default: https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli
GRAPHCAST_NETWORKSupported Graphcast networks: mainnet, testnet.
Default: testnet
IDENTIFIERSubgraph deployment hash is used to be the message content identifier.
Example: IDENTIFIER=YOUR_IDENTIFIER
NEW_HASHSubgraph deployment hash for the upgrade version of the subgraph.
Example: NEW_HASH=YOUR_NEW_HASH
SUBGRAPH_IDSubgraph id shared by the old and new deployment.
Example: SUBGRAPH_ID=YOUR_SUBGRAPH_ID
INDEX_NETWORKSubgraph id shared by the old and new deployment.
Example: INDEX_NETWORK=YOUR_INDEX_NETWORK
MIGRATION_TIMEUNIX timestamp that the developer plan on migrating the usage.
Example: MIGRATION_TIME=YOUR_MIGRATION_TIME
ID_VALIDATIONIdentity validation mechanism for message signers (options: no-check, valid-address, graphcast-registered, graph-network-account, registered-indexer, indexer).
Example: ID_VALIDATION=valid-address
LOG_LEVELLogging configuration to set as RUST_LOG.
Default: info
LOG_FORMATSupport logging formats: pretty, json, full, compact.
Default: pretty

The one-shot CLI code is very extensible and could be altered to send any kind of Graphcast-compatible message to the network.

Run with Docker

  1. Pull the one-shot CLI image
docker pull ghcr.io/graphops/one-shot-cli:latest
  1. Run the image, providing the required configuration variables. Here's a sample configuration:
docker run ghcr.io/graphops/one-shot-cli \
--private-key "1b098c0f518c046dc01bc9cf785cb670e6e059d99a294319598583f546f420c8" \
--graph-account "0xedfecf44f942d53b0b8c9559560bc6cb3d6d2c1d" \
--identifier "QmXoFPYJmtSraJDTEGDXLAP52FjVQmLBJqrj6DUevk47o" \
--new-hash "Qmf7mgYnJktiYTvXGwBhbR52lXpjCZgFLA5Y9kugHtZmWU" \
--subgraph-id "J5s8MTaDrNwxHVeB2okoVnZ2eULeYc47L6LKef6BLWwX" \
--index-network "goerli" \
--migration-time 1692114708

(or) Run using a pre-built binary

We also provide pre-built binaries for Ubuntu and MacOS, which you can find in the Assets section on each release in the releases page on Github. Simply download the binary, make it executable (chmod a+x ./one-shot-cli-{TAG}-{SYSTEM}) and then run it (using ./one-shot-cli-{TAG}-{SYSTEM}), like this:

./one-shot-cli-0.0.1-macos \
--private-key "1b098c0f518c046dc01bc9cf785cb670e6e059d99a294319598583f546f420c8" \
--graph-account "0xedfecf44f942d53b0b8c9559560bc6cb3d6d2c1d" \
--identifier "QmXoFPYJmtSraJDTEGDXLAP52FjVQmLBJqrj6DUevk47o" \
--new-hash "Qmf7mgYnJktiYTvXGwBhbR52lXpjCZgFLA5Y9kugHtZmWU" \
--subgraph-id "J5s8MTaDrNwxHVeB2okoVnZ2eULeYc47L6LKef6BLWwX" \
--index-network "goerli" \
--migration-time 1692114708

(or) Run using a pre-built binary

  1. Clone the repo
git clone https://github.com/graphops/one-shot-cli.git
  1. Navigate to the project directory
cd one-shot-cli
  1. Run the CLI
cargo run --release -- --private-key "1b098c0f518c046dc01bc9cf785cb670e6e059d99a294319598583f546f420c8" \
--graph-account "0xedfecf44f942d53b0b8c9559560bc6cb3d6d2c1d" \
--identifier "QmXoFPYJmtSraJDTEGDXLAP52FjVQmLBJqrj6DUevk47o" \
--new-hash "Qmf7mgYnJktiYTvXGwBhbR52lXpjCZgFLA5Y9kugHtZmWU" \
--subgraph-id "J5s8MTaDrNwxHVeB2okoVnZ2eULeYc47L6LKef6BLWwX" \
--index-network "goerli" \
--migration-time 1692114708
+ \ No newline at end of file diff --git a/graphcast/radios/subgraph-radio.html b/graphcast/radios/subgraph-radio.html index 740bc9c2..520146b7 100644 --- a/graphcast/radios/subgraph-radio.html +++ b/graphcast/radios/subgraph-radio.html @@ -5,14 +5,13 @@ Subgraph Radio | GraphOps Docs - +
-

Subgraph Radio

The source code for the Subgraph Radio is available on GitHub and Docker builds are automatically published as GitHub Packages. Subgraph Radio is also published as a crate on crates.io.

Introduction

Subgraph Radio is an optional component of the Graph Protocol Indexer Stack. It uses the Graphcast Network to facilitate the exchange of Subgraph data and information among Indexers and other participants in the network.

An essential aspect of earning indexing rewards as an Indexer is the generation of valid Proof of Indexing hashes (POIs). These POIs provide evidence of the Indexer's possession of correct data. Submitting invalid POIs could lead to a Dispute and possible slashing by the protocol. With Subgraph Radio's POI functionality, Indexers gain confidence knowing that their POIs are continually cross-verified against those of other participating Indexers. Should there be a discrepancy in POIs, Subgraph Radio functions as an early warning system, alerting the Indexer within minutes.

All POIs generated through Subgraph Radio are public (normalized), meaning they are hashed with a 0x0 Indexer Address and can be compared between Indexers. However, these public POIs are not valid for on-chain reward submission. Subgraph Radio groups and weighs public POIs according to the aggregate stake in GRT attesting to each. The normalized POI with the most substantial aggregate attesting stake is deemed canonical and used for comparisons with your local Indexer POIs.

For enhanced security, we recommend running Subgraph Radio with an independent Graphcast ID linked to your Indexer account. This Graphcast ID is an Ethereum account authorized to sign POI attestations on behalf of your Indexer. By default, Subgraph Radio validates messages received from any signer, that can be resolved to an Indexer address, regardless of whether or not they are registered on the Graphcast registry (though this behavior can be altered by setting the ID_VALIDATION config variable). Learn how to register a Graphcast ID here.

Basic Configuration

The Subgraph Radio is configured using environment variables. You will need to prepare the following environment variables:

NameDescription and examples
PRIVATE_KEYPrivate key of the Graphcast ID wallet or the Indexer Operator wallet (precendence over MNEMONICS).
Example: 0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
INDEXER_ADDRESSIndexer address for Graphcast message verification, in all lowercase.
Example: 0xabcdcabdabcdabcdcabdabcdabcdcabdabcdabcd
GRAPH_NODE_STATUS_ENDPOINTURL to a Graph Node Indexing Status endpoint.
Example: http://index-node:8030/graphql
REGISTRY_SUBGRAPHURL to the Graphcast Registry subgraph for your network. Check APIs for your preferred network
NETWORK_SUBGRAPHURL to the Graph Network subgraph. Check APIs for your preferred network
GRAPHCAST_NETWORKThe Graphcast Messaging fleet and pubsub namespace to use.
Mainnet: mainnet
Goerli: testnet

Run with Docker

  1. Pull the Subgraph Radio image
docker pull ghcr.io/graphops/subgraph-radio:latest
  1. Run the image, providing the required environment variables. Here's a sample mainnet configuration:
docker run \
-e GRAPHCAST_NETWORK="mainnet" \
-e REGISTRY_SUBGRAPH="https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-mainnet" \
-e NETWORK_SUBGRAPH="https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet" \
-e PRIVATE_KEY="PRIVATE_KEY" \
-e GRAPH_NODE_STATUS_ENDPOINT="http://graph-node:8030/graphql" \
-e RUST_LOG="warn,hyper=warn,graphcast_sdk=info,subgraph_radio=info" \
-e INDEXER_ADDRESS="INDEXER_ADDRESS" \
ghcr.io/graphops/subgraph-radio:latest

(or) Run with docker-compose

You can append this service definition to your docker-compose manifest and customise the definitions:

services:
# ... your other service definitions
subgraph-radio:
image: ghcr.io/graphops/subgraph-radio:latest
container_name: subgraph-radio
restart: unless-stopped
environment:
GRAPHCAST_NETWORK: "mainnet"
REGISTRY_SUBGRAPH: "https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-mainnet"
NETWORK_SUBGRAPH: "https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet"
PRIVATE_KEY: "PRIVATE_KEY"
GRAPH_NODE_STATUS_ENDPOINT: "http://graph-node:8030/graphql"
RUST_LOG: "warn,hyper=warn,graphcast_sdk=info,subgraph_radio=info"
INDEXER_ADDRESS: "INDEXER_ADDRESS"
logging:
driver: local

(or) Run as part of StakeSquid's docker-compose setup

Subgraph Radio is included as an optional component in both the mainnet and testnet versions of StakeSquid's guide.

(or) Run using a pre-built binary

We also provide pre-built binaries for Ubuntu and MacOS, which you can find in the Assets section on each release in the releases page on Github. Simply download the binary, make it executable (chmod a+x ./subgraph-radio-{TAG}-{SYSTEM}) and then run it (using ./subgraph-radio-{TAG}-{SYSTEM}).

Advanced Configuration

In the configuration table below is the full list of environment variables you can set, along with example values.

See Basic Configuration above. The following environment variables are optional:

Name (Optional variables)Description and examples
MNEMONICMnemonic to the Graphcast ID wallet or the Indexer Operator wallet (first address of the wallet is used; Only one of PRIVATE_KEY or MNEMONIC is needed). Example: claptrap armchair violin...
COLLECT_MESSAGE_DURATIONSeconds that the Subgraph Radio will wait to collect remote POI attestations before making a comparison with the local POI. Example: 120 for 2 minutes.
COVERAGEToggle for topic coverage level. Possible values: "comprehensive", "on-chain", "minimal". Default is set to "comprehensive" coverage.
TOPICSComma separated static list of content topics (subgraphs) to subscribe to. Example: QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz,QmUwCFhXM3f6qH9Ls9Y6gDNURBH7mxsn6JcectgxAz6CwU,QmQ1Lyh3U6YgVP6YX1RgRz6c8GmKkEpokLwPvEtJx6cF1y
WAKU_HOSTInterface onto which to bind the bundled Waku node. Example: 0.0.0.0
WAKU_PORTP2P port on which the bundled Waku node will operate. Example: 60000
WAKU_NODE_KEYStatic Waku Node Key.
BOOT_NODE_ADDRESSESPeer addresses to use as Waku boot nodes. Example: "addr1, addr2, addr3"
SLACK_TOKENSlack Token to use for notifications. Example: xoxp-0123456789-0123456789-0123456789-0123456789
TELEGRAM_TOKENTelegram Bot Token to use for notifications. Example: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
TELEGRAM_CHAT_IDThe ID of the Telegram chat to send messages to. Example: -1001234567890
SLACK_CHANNELName of Slack channel to send messages to (has to be a public channel). Example: poir-notifications
WAKU_LOG_LEVELWaku node logging configuration. Example: INFO (is also the default)
RUST_LOGRust tracing configuration. Example: graphcast_sdk=debug,subgraph_radio=debug, defaults to info for everything
DISCORD_WEBHOOKDiscord webhook URL for notifications. Example: https://discord.com/api/webhooks/123456789012345678/AbCDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN
METRICS_PORTIf set, the Radio will expose Prometheus metrics on this (off by default). Example: 3001
METRICS_HOSTIf set, the Radio will expose Prometheus metrics on this (off by default). Example: 0.0.0.0
SERVER_HOSTIf SERVER_PORT is set, the Radio will expose an API service on the given host and port. Default: 0.0.0.0
SERVER_PORTIf set, the Radio will expose an API service on the given port (off by default). Example: 8080
LOG_FORMATOptions: pretty - verbose and human readable; json - not verbose and parsable; compact - not verbose and not parsable; full - verbose and not parsible. Default value: pretty.
PERSISTENCE_FILE_PATHRelative path. If set, the Radio will periodically store states of the program to the file in json format (off by default).
DISCV5_ENRSComma separated ENRs for Waku Discv5 bootstrapping. Defaults to empty list.
DISCV5_PORTDiscoverable UDP port. Default: 9000
ID_VALIDATIONDefines the level of validation for message signers used during radio operation. Options include: no-check, valid-address, graphcast-registered, graph-network-account, registered-indexer, indexer. Default: indexer

Configurations explained

COVERAGE (topic)

COVERAGE is used to specify the topic coverage level. It controls the range of topics (subgraph ipfs hashes) the Indexer subscribes to in order to process data and participate in the network.

There are three coverage levels available:

  • comprehensive: Subscribe to on-chain topics, user-defined static topics, and subgraph deployments syncing on graph node. This level is useful for Indexers who want to compare public POIs for all deployments syncing on their graph node even if they don't have an active allocations open (their stake will not be taken into account in attestation).
  • on-chain: Subscribe to on-chain topics and user-defined static topics. This is the default coverage level and is suitable for indexers who only want to compare data for deployments with active allocations.
  • minimal: Only subscribe to user-defined static topics. This level is for Indexers who want to limit their participation to specific topics of interest.

Identity validaiton

ID_VALIDATION is used to define level of validation for message signers used during radio operation. We recommend registered-indexer for most strict identity validation, while indexer is a viable option for those who want to use the network before considering Grapchast ID registration. You can choose a sender identity validation mechanism for your radio, based on your use case and security preferences.

Available Options:

  • no-check: Does not perform check on the message signature and does not verify the signer. All messages should pass the sender check.
  • valid-address: Requires the signer to be a valid Ethereum address. Messages should be traceable to an Ethers wallet.
  • graphcast-registered: Requires the signer to be registered on the Graphcast Registry.
  • graph-network-account: signer must be a Graph account.
  • registered-indexer: signer must be registered at Graphcast Registry and correspond to an Indexer satisfying the indexer minimum stake requirement.
  • indexer: signer must be registered at Graphcast Registry or is a Graph Account, and correspond to an Indexer satisfying the indexer minimum stake requirement.

Gossip protocol

WAKU_HOST and WAKU_PORT specify where the bundled Waku node runs. If you want to run multiple Radios, or multiple instances of the same Radio, you should run them on different ports.

If you want to customize the log level, you can toggle RUST_LOG environment variable. Here's an example configuration to get more verbose logging:

RUST_LOG="warn,hyper=warn,graphcast_sdk=debug,subgraph_radio=debug"

Discv5 is an ambient node discovery network for establishing a decentralized network of interconnected Graphcast Radios. Discv5, when used in Graphcast Radios, serves as a dedicated peer-to-peer discovery protocol that empowers Radios to form an efficient, decentralized network. Without Discv5, the traffic within the Graphcast network would largely rely on centrally hosted boot nodes, leading to a less distributed architecture. However, with Discv5, Radios are capable of directly routing messages among themselves, significantly enhancing network decentralization and reducing reliance on the central nodes. If you want to learn more about Discv5, check out the official spec.

State management

PERSISTENCE_FILE_PATH configuration variable allows the Radio to maintain operational continuity across sessions. When the file path is set, it triggers the Radio to periodically store its state, including local attestations, remote messages and POI comparison results in a JSON-formatted file at the specified path. This facilitates seamless session transitions and minimizes data loss. In the event of a system disruption, the state can be reloaded from this file, ensuring the Radio can resume operation effectively.

Monitoring the Radio

Notifications

If the Radio operator has set up a Slack, Discord and/or Telegram bot integration and the Radio finds a POI mismatch, it sends alerts to the designated channels. The operator can also inspect the logs to see if the Radio is functioning properly, if it's sending and receiving messages, if it's comparing normalised POIs, if there is a found POI mismatch, etc.

Prometheus & Grafana

The Subgraph Radio exposes metrics that can then be scraped by a Prometheus server and displayed in Grafana. In order to use them you have to have a local Prometheus server running and scraping metrics on the provided port. You can specify the metrics host and port by using the environment variables METRICS_PORT and METRICS_HOST. We also provide a Grafana dashboard config JSON file which you can use to visualise the metrics in Grafana.

HTTP Server

The Radio spins up an HTTP server with a GraphQL API when SERVER_HOST and SERVER_PORT environment variables are set. The supported routes are:

  • /health for health status
  • /api/v1/graphql for GET and POST requests with GraphQL playground interface

The GraphQL API now includes several advanced queries:

  • radioPayloadMessages
  • localAttestations
  • comparisonResults
  • stakeRatio
  • senderRatio

Below are some example queries:

Query {
radioPayloadMessages{
identifier
nonce
blockNumber
network
signature
}
localAttestations{
deployment
blockNumber
attestation{
ppoi
}
}
comparisonResults(identifier:"Qm...."){
deployment
blockNumber
resultType
localAttestation{
ppoi
}
attestations{
ppoi
}
}
stakeRatio(filter: {deployment: "__", blockNumber: "___"}){
deployment
blockNumber
compareRatio
}
senderRatio{
deployment
blockNumber
compareRatio
}
}

You can customize the returned data from the stakeRatio and senderRatio queries by providing optional filters as arguments:

  • deployment - If provided, only attestations for the specified deployment will be included in the comparison.
  • block - If provided, only attestations for the specified block number will be included in the comparison.
  • filter - A more complex filter that can include deployment, block_number, and result_type fields. This filter is used to further refine the set of attestations included in the comparison. -Here's an example of a query with filters:
Query {
stakeRatio(filter: {deployment: "Qm....", blockNumber: 12345, result_type: "Type_Name"}){
deployment
blockNumber
compareRatio
}
}

In this example, the stakeRatio query will return the stake ratios only for attestations from deployment "Qm...." and block number 12345, and only for the specified result type.

Note: The result_type field of the filter corresponds to the resultType field in the comparisonResults query. This field represents the type of comparison result.

The API also includes the senderRatio and stakeRatio endpoints, which return more detailed insights into the state of the Radio.

senderRatio provides an overview of the consensus status of the attestations from remote messages. It gives a ratio string that signifies the number of indexers with the same public POI as the local Radio. The results are presented as x/y!/z where:

  • x, y, and z are sorted by descending stake weights
  • ! indicates the entry that corresponds to the local result.

For example, 2/0! means there are two indexers attesting with a higher sum of stake weight and no other indexer shares the same public POIs as the local Radio. 8! means there are eight other indexers agreeing with the local Radio.

stakeRatio offers similar functionality to senderRatio but the results are based on the stake weight. It orders the attestations by stake weight, then computes the ratio of unique senders.

Query {
senderRatio{
deployment
blockNumber
compareRatio
}
stakeRatio{
deployment
blockNumber
compareRatio
}
}

These queries provide a clear aggregation of the attestations from remote messages, giving a concise understanding of the Radio's state. The optional filters - deployment, block, and filter - can be used to refine the results.

How it works

Fetching active allocations

The Subgraph Radio is responsible for reading active allocations of the Radio operator's corresponding Indexer. It periodically polls the Graph Node for new blocks on all relevant networks and constructs Graphcast topics on each allocation identified by subgraph deployment IPFS hash.

tip

The relevant networks are those corresponding to the subgraphs that have active allocations.

The Radio fetches new active allocations at a regular interval to ensure that it is processing the latest information. Chainheads for these networks are updated with data from the Graph Node, and the Radio ensures that it is always using the latest chainhead when processing messages.

Gathering and comparing normalised POIs

At a given interval, the Radio fetches the normalised POI for each deployment. This interval is defined in blocks different for each network. It then saves those public POIs, and as other Indexers running the Radio start doing the same, messages start propagating through the network. The Radio saves each message and processes them on a given interval.

The messages include a nonce (UNIX timestamp), block number, signature (used to derive the sender's on-chain Indexer address) and network. Before saving an entry to the map, the Radio operator verifies through the Graph network subgraph for the sender's on-chain identity and amount of tokens staked, which is used during comparisons later on.

At another interval, the Radio compares the local public POIs with the collected remote ones. The remote POIs are sorted so that for each subgraph (on each block), the POI that is backed by the most on-chain stake is selected. This means that the combined stake of all Indexers that attested to it is considered, not just the highest staking Indexer. The top POI is then compared with the local POIs for that subgraph at that block to determine consensus.

If there is a mismatch and if the Radio operator has set up a Slack, Discord and/or Telegram bot integration, the Radio will send alerts to the designated channels.

After a successful comparison, the attestations that have been checked are removed from the store.

Developing the Subgraph Radio

Building the image using the Dockerfile locally

If you want to make any changes to the Subgraph Radio codebase, you can use this option.

Prerequisites
  1. Clone this repo and cd into it
  2. Create a .env file that includes at least the required environment variables. To see the full list of environment variables you can provide, check out the Configuration section.
Running the Subgraph Radio inside a Docker container
docker-compose up -d

Building Subgraph Radio locally

To have full control over the Subgraph Radio code and run it directly on your machine (without Docker) you can use this option.

Prerequisites

  1. Clone this repo and cd into it
  2. Make sure you have the following installed:
  • Rust
  • Go
  • Build tools (e.g. the build-essentials package for Debian-based Linux distributions or Xcode Command Line Tools for MacOS)
  • C compiler (e.g. the clang package for Debian-based Linux distribution or Xcode Command Line Tools for MacOS)
  • OpenSSL (e.g. the libssl-dev package for Debian-based Linux distribution or openssl for MacOS)
  • PostreSQL libraries and headers (e.g. the libpq-dev package for Debian-based Linux distribution or postgresql for MacOS)
  1. You have Graph Node syncing your indexer's on-chain allocations.
  2. You have created a .env file that includes at least the required environment variables. To see the full list of environment variables you can provide, check out the Configuration section.

Running the Subgraph Radio natively

cargo run --release
- +

Subgraph Radio

The source code for the Subgraph Radio is available on GitHub and Docker builds are automatically published as GitHub Packages. Subgraph Radio is also published as a crate on crates.io.

Introduction

Subgraph Radio is an optional component of the Graph Protocol Indexer Stack. It uses the Graphcast Network to facilitate the exchange of Subgraph data and information among Indexers and other participants in the network.

An essential aspect of earning indexing rewards as an Indexer is the generation of valid Proof of Indexing hashes (POIs). These POIs provide evidence of the Indexer's possession of correct data. Submitting invalid POIs could lead to a Dispute and possible slashing by the protocol. With Subgraph Radio's POI feature, Indexers gain confidence knowing that their POIs are continually cross-verified against those of other participating Indexers. Should there be a discrepancy in POIs, Subgraph Radio functions as an early warning system, alerting the Indexer within minutes.

All POIs generated through Subgraph Radio are public (normalized), meaning they are hashed with a 0x0 Indexer Address and can be compared between Indexers. However, these public POIs are not valid for on-chain reward submission. Subgraph Radio groups and weighs public POIs according to the aggregate stake in GRT attesting to each. The normalized POI with the most substantial aggregate attesting stake is deemed canonical and used for comparisons with your local Indexer POIs.

For enhanced security, we recommend running Subgraph Radio with an independent Graphcast ID linked to your Indexer account. This Graphcast ID is an Ethereum account authorized to sign POI attestations on behalf of your Indexer. By default, Subgraph Radio validates messages received from any signer, that can be resolved to an Indexer address, regardless of whether or not they are registered on the Graphcast registry (though this behavior can be altered by setting the ID_VALIDATION config variable). Learn how to register a Graphcast ID here.

Basic Configuration

The Subgraph Radio can be configured using environment variables, CLI arguments, as well as a TOML or YAML configuration file. Take a look at the configuration options to learn more. In all cases, users will need to prepare the following configuration variables:

NameDescription and examples
PRIVATE_KEYPrivate key of the Graphcast ID wallet or the Indexer Operator wallet (precendence over MNEMONICS).
Example: 0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
INDEXER_ADDRESSIndexer address for Graphcast message verification, in all lowercase.
Example: 0xabcdcabdabcdabcdcabdabcdabcdcabdabcdabcd
GRAPH_NODE_STATUS_ENDPOINTURL to a Graph Node Indexing Status endpoint.
Example: http://index-node:8030/graphql
REGISTRY_SUBGRAPHURL to the Graphcast Registry subgraph for your network. Check APIs for your preferred network
NETWORK_SUBGRAPHURL to the Graph Network subgraph. Check APIs for your preferred network
GRAPHCAST_NETWORKThe Graphcast Messaging fleet and pubsub namespace to use.
Mainnet: mainnet
Goerli: testnet

Run with Docker

  1. Pull the Subgraph Radio image
docker pull ghcr.io/graphops/subgraph-radio:latest
  1. Run the image, providing the required environment variables. Here's a sample mainnet configuration:
docker run \
-e GRAPHCAST_NETWORK="mainnet" \
-e REGISTRY_SUBGRAPH="https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-mainnet" \
-e NETWORK_SUBGRAPH="https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet" \
-e PRIVATE_KEY="PRIVATE_KEY" \
-e GRAPH_NODE_STATUS_ENDPOINT="http://graph-node:8030/graphql" \
-e RUST_LOG="warn,hyper=warn,graphcast_sdk=info,subgraph_radio=info" \
-e INDEXER_ADDRESS="INDEXER_ADDRESS" \
ghcr.io/graphops/subgraph-radio:latest

(or) Run with docker-compose

You can append this service definition to your docker-compose manifest and customise the definitions:

services:
# ... your other service definitions
subgraph-radio:
image: ghcr.io/graphops/subgraph-radio:latest
container_name: subgraph-radio
restart: unless-stopped
environment:
GRAPHCAST_NETWORK: "mainnet"
REGISTRY_SUBGRAPH: "https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-mainnet"
NETWORK_SUBGRAPH: "https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet"
PRIVATE_KEY: "PRIVATE_KEY"
GRAPH_NODE_STATUS_ENDPOINT: "http://graph-node:8030/graphql"
RUST_LOG: "warn,hyper=warn,graphcast_sdk=info,subgraph_radio=info"
INDEXER_ADDRESS: "INDEXER_ADDRESS"
logging:
driver: local

(or) Run as part of StakeSquid's docker-compose setup

Subgraph Radio is included as an optional component in both the mainnet and testnet versions of StakeSquid's guide.

(or) Run using a pre-built binary

We also provide pre-built binaries for Ubuntu and MacOS, which you can find in the Assets section on each release in the releases page on Github. Simply download the binary, make it executable (chmod a+x ./subgraph-radio-{TAG}-{SYSTEM}) and then run it (using ./subgraph-radio-{TAG}-{SYSTEM}).

Advanced Configuration

In the configuration table below is the full list of environment variables you can set, along with example values.

See Basic Configuration above. The following environment variables are optional:

Name (Optional variables)Description and examples
MNEMONICMnemonic to the Graphcast ID wallet or the Indexer Operator wallet (first address of the wallet is used; Only one of PRIVATE_KEY or MNEMONIC is needed). Example: claptrap armchair violin...
COLLECT_MESSAGE_DURATIONSeconds that the Subgraph Radio will wait to collect remote POI attestations before making a comparison with the local POI. Example: 120 for 2 minutes.
COVERAGEToggle for topic coverage level. Possible values: "comprehensive", "on-chain", "minimal", "none". Default is set to "comprehensive" coverage.
TOPICSComma separated static list of content topics (subgraphs) to subscribe to. Example: QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz,QmUwCFhXM3f6qH9Ls9Y6gDNURBH7mxsn6JcectgxAz6CwU,QmQ1Lyh3U6YgVP6YX1RgRz6c8GmKkEpokLwPvEtJx6cF1y
WAKU_HOSTInterface onto which to bind the bundled Waku node. Example: 0.0.0.0
WAKU_PORTP2P port on which the bundled Waku node will operate. Example: 60000
WAKU_NODE_KEYStatic Waku Node Key.
BOOT_NODE_ADDRESSESPeer addresses to use as Waku boot nodes. Example: "addr1, addr2, addr3"
SLACK_TOKENSlack Token to use for notifications. Example: xoxp-0123456789-0123456789-0123456789-0123456789
TELEGRAM_TOKENTelegram Bot Token to use for notifications. Example: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
TELEGRAM_CHAT_IDThe ID of the Telegram chat to send messages to. Example: -1001234567890
SLACK_CHANNELName of Slack channel to send messages to (has to be a public channel). Example: poir-notifications
WAKU_LOG_LEVELWaku node logging configuration. Example: INFO (is also the default)
RUST_LOGRust tracing configuration. Example: graphcast_sdk=debug,subgraph_radio=debug, defaults to info for everything
DISCORD_WEBHOOKDiscord webhook URL for notifications. Example: https://discord.com/api/webhooks/123456789012345678/AbCDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN
METRICS_PORTIf set, the Radio will expose Prometheus metrics on this (off by default). Example: 3001
METRICS_HOSTIf set, the Radio will expose Prometheus metrics on this (off by default). Example: 0.0.0.0
SERVER_HOSTIf SERVER_PORT is set, the Radio will expose an API service on the given host and port. Default: 0.0.0.0
SERVER_PORTIf set, the Radio will expose an API service on the given port (off by default). Example: 8080
LOG_FORMATOptions: pretty - verbose and human readable; json - not verbose and parsable; compact - not verbose and not parsable; full - verbose and not parsible. Default value: pretty.
PERSISTENCE_FILE_PATHRelative path. If set, the Radio will periodically store states of the program to the file in json format (off by default).
DISCV5_ENRSComma separated ENRs for Waku Discv5 bootstrapping. Defaults to empty list.
DISCV5_PORTDiscoverable UDP port. Default: 9000
ID_VALIDATIONDefines the level of validation for message signers used during radio operation. Options include: no-check, valid-address, graphcast-registered, graph-network-account, registered-indexer, indexer. Default: indexer
INDEXER_MANAGEMENT_SERVER_ENDPOINTURL to the Indexer management server of Indexer Agent. Example: http://localhost:18000
AUTO_UPGRADEToggle for the types of subgraphs for which the Radio will send offchain syncing commands to the indexer management server. Default to upgrade all syncing deployments. Possible values: "comprehensive", "on-chain", "minimal", "none". Default is set to "comprehensive" coverage.

Configurations explained

COVERAGE (topic)

COVERAGE is used to specify the topic coverage level. It controls the range of topics (subgraph ipfs hashes) the Indexer subscribes to in order to process data and participate in the network.

There are three coverage levels available:

  • comprehensive: Subscribe to on-chain topics, user-defined static topics, and subgraph deployments syncing on graph node. This level is useful for Indexers who want to compare public POIs for all deployments syncing on their graph node even if they don't have an active allocations open (their stake will not be taken into account in attestation).
  • on-chain: Subscribe to on-chain topics and user-defined static topics. This is the default coverage level and is suitable for indexers who only want to compare data for deployments with active allocations.
  • minimal: Only subscribe to user-defined static topics. This level is for Indexers who want to limit their participation to specific topics of interest.

Identity validaiton

ID_VALIDATION is used to define level of validation for message signers used during radio operation. We recommend registered-indexer for most strict identity validation, while indexer is a viable option for those who want to use the network before considering Grapchast ID registration. You can choose a sender identity validation mechanism for your radio, based on your use case and security preferences.

Available Options:

  • no-check: Does not perform check on the message signature and does not verify the signer. All messages should pass the sender check.
  • valid-address: Requires the signer to be a valid Ethereum address. Messages should be traceable to an Ethers wallet.
  • graphcast-registered: Requires the signer to be registered on the Graphcast Registry.
  • graph-network-account: signer must be a Graph account.
  • registered-indexer: signer must be registered at Graphcast Registry and correspond to an Indexer satisfying the indexer minimum stake requirement.
  • indexer: signer must be registered at Graphcast Registry or is a Graph Account, and correspond to an Indexer satisfying the indexer minimum stake requirement.

Gossip protocol

WAKU_HOST and WAKU_PORT specify where the bundled Waku node runs. If you want to run multiple Radios, or multiple instances of the same Radio, you should run them on different ports.

If you want to customize the log level, you can toggle RUST_LOG environment variable. Here's an example configuration to get more verbose logging:

RUST_LOG="warn,hyper=warn,graphcast_sdk=debug,subgraph_radio=debug"

Discv5 is an ambient node discovery network for establishing a decentralized network of interconnected Graphcast Radios. Discv5, when used in Graphcast Radios, serves as a dedicated peer-to-peer discovery protocol that empowers Radios to form an efficient, decentralized network. Without Discv5, the traffic within the Graphcast network would largely rely on centrally hosted boot nodes, leading to a less distributed architecture. However, with Discv5, Radios are capable of directly routing messages among themselves, significantly enhancing network decentralization and reducing reliance on the central nodes. If you want to learn more about Discv5, check out the official spec.

State management

PERSISTENCE_FILE_PATH configuration variable allows the Radio to maintain operational continuity across sessions. When the file path is set, it triggers the Radio to periodically store its state, including local attestations, remote messages and POI comparison results in a JSON-formatted file at the specified path. This facilitates seamless session transitions and minimizes data loss. In the event of a system disruption, the state can be reloaded from this file, ensuring the Radio can resume operation effectively.

Subgraph Upgrade Pre-sync feature configuration variables

The subgraph upgrade pre-sync feature provides a way for Subgraph Developers to signal when they plan on releasing a new subgraph version, thereby allowing Indexers to start syncing the subgraph in advance. If the Radio operator has set up the notification system, they will get notified whenever a new subgraph upgrade intent message is received.

If the INDEXER_MANAGEMEN_SERVER_ENDPOINT configuration variable has been set, the Radio will send a request to the Indexer Agent to start offchain syncing the new Subgraph deployment.

The AUTO_UPGRADE variable can be toggled to change the coverage level of subgraphs for which the Radio will send offchain syncing commands to the indexer management server.

Configuration options

To configure Subgraph Radio, you can use the following methods:

Using Environment Variables

Example .env file:

PRIVATE_KEY="a2b3c1d4e5f6890e7f6g5h4i3j2k1l0m"
GRAPH_NODE_STATUS_ENDPOINT="http://127.0.0.42:8030/graphql"
REGISTRY_SUBGRAPH="https://api.thegraph.com/subgraphs/name/randomuser/graphcast-registry-mainnet"
NETWORK_SUBGRAPH="https://api.thegraph.com/subgraphs/name/graphprotocol/graph-mainnet"
GRAPHCAST_NETWORK=mainnet
INDEXER_ADDRESS="0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"

Using CLI arguments

Pass the configuration options directly as command-line arguments.

docker run ghcr.io/graphops/subgraph-radio \
--private-key "a2b3c1d4e5f6890e7f6g5h4i3j2k1l0m" \
--graph-node-status-endpoint "http://127.0.0.42:8030/graphql" \
--registry-subgraph "https://api.thegraph.com/subgraphs/name/randomuser/graphcast-registry-mainnet" \
--network-subgraph "https://api.thegraph.com/subgraphs/name/graphprotocol/graph-mainnet" \
--graphcast-network mainnet \
--indexer-address "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"

Using a TOML/YAML file

Example TOML configuration file (config.toml):

[graph_stack]
graph_node_status_endpoint = 'http://127.0.0.42:8030/graphql'
indexer_address = '0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6'
registry_subgraph = 'https://api.thegraph.com/subgraphs/name/randomuser/graphcast-registry-mainnet'
network_subgraph = 'https://api.thegraph.com/subgraphs/name/graphprotocol/graph-mainnet'
private_key = 'a2b3c1d4e5f6890e7f6g5h4i3j2k1l0m'

Then you just need to have the CONFIG_FILE set, either as an env variable - CONFIG_FILE=path/to/config.toml or passed as a CLI arg - --config-file path/to/config.toml.

Example YAML configuration file (config.yaml):

graph_stack:
graph_node_status_endpoint: "http://127.0.0.42:8030/graphql"
indexer_address: "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
registry_subgraph: "https://api.thegraph.com/subgraphs/name/randomuser/graphcast-registry-mainnet"
network_subgraph: "https://api.thegraph.com/subgraphs/name/graphprotocol/graph-mainnet"
private_key: "a2b3c1d4e5f6890e7f6g5h4i3j2k1l0m"

Then you just need to have the CONFIG_FILE set, either as an env variable - CONFIG_FILE=path/to/config.yaml or passed as a CLI arg - --config-file path/to/config.yaml.

We also have an extensive configuration file template in the repo.

Monitoring the Radio

Notifications

If the Radio operator has set up a Slack, Discord and/or Telegram bot integration and the Radio finds a POI mismatch, it sends alerts to the designated channels. The operator can also inspect the logs to see if the Radio is functioning properly, if it's sending and receiving messages, if it's comparing normalised POIs, if there is a found POI mismatch, etc.

Prometheus & Grafana

The Subgraph Radio exposes metrics that can then be scraped by a Prometheus server and displayed in Grafana. In order to use them you have to have a local Prometheus server running and scraping metrics on the provided port. You can specify the metrics host and port by using the environment variables METRICS_PORT and METRICS_HOST. We also provide a Grafana dashboard config JSON file which you can use to visualise the metrics in Grafana.

HTTP Server

The Radio spins up an HTTP server with a GraphQL API when SERVER_HOST and SERVER_PORT environment variables are set. The supported routes are:

  • /health for health status
  • /api/v1/graphql for GET and POST requests with GraphQL playground interface

The GraphQL API now includes several advanced queries:

  • radioPayloadMessages
  • localAttestations
  • comparisonResults
  • comparisonRatio

Below are some example queries:

query {
radioPayloadMessages {
identifier
nonce
signature
graphAccount
payload {
identifier
content
}
}
localAttestations {
deployment
blockNumber
attestation {
ppoi
}
}
comparisonResults(identifier: "Qm...") {
deployment
blockNumber
resultType
localAttestation {
ppoi
}
attestations {
senders
stakeWeight
ppoi
}
}
comparisonRatio {
deployment
blockNumber
stakeRatio
}
}

You can customize the returned data from the comparisonRatio query by providing optional arguments - deployment, block and resultType.

query {
comparisonRatio(deployment: "Qm...", block: 17887350, resultType: MATCH) {
deployment
blockNumber
stakeRatio
}
}

In this example, the stakeRatio query will return the stake ratios only for attestations from deployment "Qm..." and block number 17887350, and only for the specified result type.

Note: The result_type field of the filter corresponds to the resultType field in the comparisonResults query. This field represents the type of comparison result.

comparisonRatio provides an overview of the consensus status of the attestations from remote messages. It gives a ratio string that signifies the number of indexers with the same public POI as the local Radio. The results are presented as x/y!/z where:

  • x, y, and z are sorted by descending stake weights
  • ! indicates the entry that corresponds to the local result.

For example, 2/0! means there are two indexers attesting with a higher sum of stake weight and no other indexer shares the same public POIs as the local Radio. 8! means there are eight other indexers agreeing with the local Radio.

stakeRatio orders the attestations by stake weight, then computes the ratio of unique senders.

These queries provide a clear aggregation of the attestations from remote messages, giving a concise understanding of the Radio's state. The optional filters - deployment, block, and filter - can be used to refine the results.

How it works

Fetching active allocations

The Subgraph Radio is responsible for reading active allocations of the Radio operator's corresponding Indexer. It periodically polls the Graph Node for new blocks on all relevant networks and constructs Graphcast topics on each allocation identified by subgraph deployment IPFS hash.

tip

The relevant networks are those corresponding to the subgraphs that have active allocations.

The Radio fetches new active allocations at a regular interval to ensure that it is processing the latest information. Chainheads for these networks are updated with data from the Graph Node, and the Radio ensures that it is always using the latest chainhead when processing messages.

Gathering and comparing normalised POIs

At a given interval, the Radio fetches the normalised POI for each deployment. This interval is defined in blocks different for each network. It then saves those public POIs, and as other Indexers running the Radio start doing the same, messages start propagating through the network. The Radio saves each message and processes them on a given interval.

The messages include a nonce (UNIX timestamp), block number, signature (used to derive the sender's on-chain Indexer address) and network. Before saving an entry to the map, the Radio operator verifies through the Graph network subgraph for the sender's on-chain identity and amount of tokens staked, which is used during comparisons later on.

At another interval, the Radio compares the local public POIs with the collected remote ones. The remote POIs are sorted so that for each subgraph (on each block), the POI that is backed by the most on-chain stake is selected. This means that the combined stake of all Indexers that attested to it is considered, not just the highest staking Indexer. The top POI is then compared with the local POIs for that subgraph at that block to determine consensus.

If there is a mismatch and if the Radio operator has set up a Slack, Discord and/or Telegram bot integration, the Radio will send alerts to the designated channels.

After a successful comparison, the attestations that have been checked are removed from the store.

Developing the Subgraph Radio

Building the image using the Dockerfile locally

If you want to make any changes to the Subgraph Radio codebase, you can use this option.

Prerequisites
  1. Clone this repo and cd into it
  2. Create a .env file that includes at least the required environment variables. To see the full list of environment variables you can provide, check out the Configuration section.
Running the Subgraph Radio inside a Docker container
docker-compose up -d

Building Subgraph Radio locally

To have full control over the Subgraph Radio code and run it directly on your machine (without Docker) you can use this option.

Prerequisites

  1. Clone this repo and cd into it
  2. Make sure you have the following installed:
  • Rust
  • Go
  • Build tools (e.g. the build-essentials package for Debian-based Linux distributions or Xcode Command Line Tools for MacOS)
  • C compiler (e.g. the clang package for Debian-based Linux distribution or Xcode Command Line Tools for MacOS)
  • OpenSSL (e.g. the libssl-dev package for Debian-based Linux distribution or openssl for MacOS)
  • PostreSQL libraries and headers (e.g. the libpq-dev package for Debian-based Linux distribution or postgresql for MacOS)
  1. You have Graph Node syncing your indexer's on-chain allocations.
  2. You have created a .env file that includes at least the required environment variables. To see the full list of environment variables you can provide, check out the Configuration section.

Running the Subgraph Radio natively

cargo run --release
+ \ No newline at end of file diff --git a/graphcast/sdk/intro.html b/graphcast/sdk/intro.html index 29b2892e..4ea7c087 100644 --- a/graphcast/sdk/intro.html +++ b/graphcast/sdk/intro.html @@ -5,13 +5,13 @@ Introduction | GraphOps Docs - +

Introduction

Graphcast SDK is a decentralized, distributed peer-to-peer (P2P) communication tool that enables users across the network to exchange information in real-time. It is designed to overcome the high cost of signaling or coordination between blockchain participants by enabling off-chain communication (gossip/cheap talk). This is particularly useful for applications where real-time communication is essential but the cost of on-chain transactions is prohibitive.

How it Works

The SDK serves as a base layer for Radio developers, providing essential components to build their applications without starting from scratch. These components include:

  1. Connection to the Graphcast network: Forms a communication network and provides an interface to subscribe to receive messages on specific topics and to broadcast messages onto the network. Allows for real-time communication between different nodes in the network.

  2. Interactions with Graph entities: This allows for necessary interactions with Graph node, Graph network subgraph, Graphcast registry.

An example of a ping-pong Radio is provided in the examples folder, which leverages the base layer and defines the specific logic around constructing and sending messages, as well as receiving and handling them. This example can serve as a starting point for developers looking to build their own Radios.

Network Configurations

A Graphcast radio can interact with many parts of The Graph network modularly. The network configurations actively supported by the team include mainnet (Ethereum mainnet and Arbitrum One) and testnet (Goerli and Arbitrum Goerli). You are free to define and use your own Graphcast Network and Graphcast Registry. This flexibility allows for a wide range of applications and use cases.

Contributing

Contributions are welcome and appreciated! Please refer to the Contributor Guide, Code Of Conduct, and Security Notes for this repository. These documents provide guidelines for how to contribute to the project in a way that is beneficial to all parties involved.

Upgrading and Testing

Updates to the SDK will be merged into the main branch once their release PR has been approved. For testing, it is recommended to use nextest as your test runner. You can run the suite using the command cargo nextest run. Regular testing is crucial to ensure the stability and reliability of the software.

Resources

- + \ No newline at end of file diff --git a/graphcast/sdk/radio-dev.html b/graphcast/sdk/radio-dev.html index f49d0ed1..157ff0db 100644 --- a/graphcast/sdk/radio-dev.html +++ b/graphcast/sdk/radio-dev.html @@ -5,13 +5,13 @@ Radio Development | GraphOps Docs - +

Radio Development

Do you want to build robust, peer-to-peer messaging apps that automatically exchange valuable data with other Indexers in real time? Do you have an idea for what data could be useful to share that could lead to greater communication efficiency in The Graph network as a whole? Then you want to build a Radio on top of the Graphcast network.

For a more complex and full example of the Graphcast SDK being used to create a Subgraph Radio, take a look at this repo.

A simple ping pong example

Let's take a look at the simplest possible example of a Radio, built on top of Graphcast - a ping pong app. When one participant sends Ping, all the others in the network are listening on the ping pong topic will send Pong back. Pretty straightforward.

Register a Graphcast ID

We recommend that you register a Graphcast ID for your on-chain Indexer address. You can learn what a Graphcast ID is and how to register one here.

Once you complete those steps you will have a Graphcast ID that is authorized to sign messages on behalf of your Indexer.

Populate your .env file

You now need to export a few environment variables:

NameDescription and examples
PRIVATE_KEYPrivate key to the Graphcast ID wallet or Indexer Operator wallet (Precendence over MNEMONICS).
Example: 0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
REGISTRY_SUBGRAPHURL to the Graphcast Registry subgraph for your network. Check APIs for your preferred network
NETWORK_SUBGRAPHURL to the Graph Network subgraph. Check APIs for your preferred network
GRAPHCAST_NETWORKThe Graphcast Messaging fleet and pubsub namespace to use. For this example you should use testnet

A few dependencies

Make sure you have the following installed:

  • Rust
  • Go
  • Build tools (e.g. the build-essentials package for Debian-based Linux distributions or Xcode Command Line Tools for MacOS)
  • C compiler (e.g. the clang package for Debian-based Linux distribution or Xcode Command Line Tools for MacOS)
  • OpenSSL (e.g. the libssl-dev package for Debian-based Linux distribution or openssl for MacOS)
  • PostreSQL libraries and headers (e.g. the libpq-dev package for Debian-based Linux distribution or postgresql for MacOS)

Start off with a new Rust project (cargo new ping-pong). Then add the following dependencies to you Cargo.toml file:

[dependencies]
graphcast-sdk = "0.4.0"
once_cell = "1.15"
tokio = { version = "1.1.1", features = ["full"] }
anyhow = "1.0.39"
ethers = "1.0.0"
dotenv = "0.15.0"
tracing = "0.1"
ethers-contract = "1.0.0"
ethers-core = "1.0.0"
ethers-derive-eip712 = "1.0.0"
prost = "0.11"
serde = "1.0.147"
serde_derive = "1.0.114"

The imports

Open your main.rs file and add the following imports:

// For date and time utils
use chrono::Utc;

// Load environment variables from .env file
use dotenv::dotenv;

// Import Arc and Mutex for thread-safe sharing of data across threads
use std::sync::{Arc, Mutex};

// Import Graphcast SDK types and functions for agent configuration, message handling, and more
use graphcast_sdk::graphcast_agent::{GraphcastAgent, GraphcastAgentConfig};

// Import sleep and Duration for handling time intervals and thread delays
use std::{thread::sleep, time::Duration};

// Import AsyncMutex for asynchronous mutual exclusion of shared resources
use tokio::sync::Mutex as AsyncMutex;

// Import tracing macros for logging and diagnostic purposes
use tracing::{debug, error, info, trace};

// Import SimpleMessage from the crate's types module
use types::SimpleMessage;

// Import Config from the crate's config module
use config::Config;

use crate::types::{GRAPHCAST_AGENT, MESSAGES};

// Include the local config and types modules
mod config;
mod types;

Structure

Everything we need will be inside the main() function. And since we'll be using async code we have to annotate it with #[tokio::main], we can start off with something as simple as:

#[tokio::main]
async fn main() {
// TODO: Radio logic
}

Before diving into the contents of the main function, let's quickly populate the other two files we need - config.rs and types.rs.

Let's take a look at types.rs first:

use async_graphql::SimpleObject;
use ethers_contract::EthAbiType;
use ethers_core::types::transaction::eip712::Eip712;
use ethers_derive_eip712::*;
use graphcast_sdk::graphcast_agent::GraphcastAgent;
use prost::Message;
use serde::{Deserialize, Serialize};

// Import the OnceCell container for lazy initialization of global/static data
use once_cell::sync::OnceCell;
use std::sync::{Arc, Mutex};

/// A global static (singleton) instance of A GraphcastMessage vector.
/// It is used to save incoming messages after they've been validated, in order
/// defer their processing for later, because async code is required for the processing but
/// it is not allowed in the handler itself.
pub static MESSAGES: OnceCell<Arc<Mutex<Vec<SimpleMessage>>>> = OnceCell::new();

/// The Graphcast Agent instance must be a global static variable (for the time being).
/// This is because the Radio handler requires a static immutable context and
/// the handler itself is being passed into the Graphcast Agent, so it needs to be static as well.
pub static GRAPHCAST_AGENT: OnceCell<GraphcastAgent> = OnceCell::new();

/// Make a test radio type
#[derive(Eip712, EthAbiType, Clone, Message, Serialize, Deserialize, SimpleObject)]
#[eip712(
name = "Graphcast Ping-Pong Radio",
version = "0",
chain_id = 1,
verifying_contract = "0xc944e90c64b2c07662a292be6244bdf05cda44a7"
)]
pub struct SimpleMessage {
#[prost(string, tag = "1")]
pub identifier: String,
#[prost(string, tag = "2")]
pub content: String,
}

impl SimpleMessage {
pub fn new(identifier: String, content: String) -> Self {
SimpleMessage {
identifier,
content,
}
}

pub fn radio_handler(&self) {
MESSAGES
.get()
.expect("Could not retrieve messages")
.lock()
.expect("Could not get lock on messages")
.push(self.clone());
}
}

SimpleMessage defines the structure that all messages for this Radio must follow.

RadioPayloadMessage is decorated with several macros - #[derive(Eip712, EthAbiType, Clone, Message, Serialize, Deserialize)], which automatically implement certain traits that are required by the SDK.

The #[eip712] macro is used to define information that is used in EIP-712, a standard for structuring typed data in Ethereum transactions.

Now let's see the config.rs file:

use clap::Parser;
use ethers::signers::WalletError;
use graphcast_sdk::build_wallet;
use graphcast_sdk::graphcast_agent::message_typing::IdentityValidation;
use graphcast_sdk::init_tracing;
use graphcast_sdk::wallet_address;
use serde::{Deserialize, Serialize};
use tracing::info;

#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
#[clap(
name = "ping-pong-radio",
about = "A simple example for using the Graphcast SDK to build Radios",
author = "GraphOps"
)]
pub struct Config {
#[clap(
long,
value_name = "ENDPOINT",
env = "GRAPH_NODE_STATUS_ENDPOINT",
help = "API endpoint to the Graph Node Status Endpoint"
)]
pub graph_node_endpoint: Option<String>,
#[clap(
long,
value_name = "KEY",
value_parser = Config::parse_key,
env = "PRIVATE_KEY",
hide_env_values = true,
help = "Private key to the Graphcast ID wallet (Precendence over mnemonics)",
)]
pub private_key: Option<String>,
#[clap(
long,
value_name = "KEY",
value_parser = Config::parse_key,
env = "MNEMONIC",
hide_env_values = true,
help = "Mnemonic to the Graphcast ID wallet (first address of the wallet is used; Only one of private key or mnemonic is needed)",
)]
pub mnemonic: Option<String>,
#[clap(
long,
value_name = "SUBGRAPH",
env = "REGISTRY_SUBGRAPH",
help = "Subgraph endpoint to the Graphcast Registry",
default_value = "https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-goerli"
)]
pub registry_subgraph: String,
#[clap(
long,
value_name = "INDEXER_ADDRESS",
env = "INDEXER_ADDRESS",
help = "Graph account corresponding to Graphcast operator"
)]
pub indexer_address: String,
#[clap(
long,
value_name = "SUBGRAPH",
env = "NETWORK_SUBGRAPH",
help = "Subgraph endpoint to The Graph network subgraph",
default_value = "https://gateway.testnet.thegraph.com/network"
)]
pub network_subgraph: String,
#[clap(
long,
value_name = "LOG_FORMAT",
env = "LOG_FORMAT",
help = "Support logging formats: pretty, json, full, compact",
long_help = "pretty: verbose and human readable; json: not verbose and parsable; compact: not verbose and not parsable; full: verbose and not parsible",
possible_values = ["pretty", "json", "full", "compact"],
default_value = "full"
)]
pub log_format: String,
#[clap(
long,
value_name = "ID_VALIDATION",
value_enum,
env = "ID_VALIDATION",
default_value = "valid-address",
help = "Identity validation mechanism for senders (message signers)",
long_help = "Identity validation mechanism for senders (message signers)\n
no-check: all messages signer is valid, \n
valid-address: signer needs to be an valid Eth address, \n
graphcast-registered: must be registered at Graphcast Registry, \n
graph-network-account: must be a Graph account, \n
registered-indexer: must be registered at Graphcast Registry, correspond to and Indexer statisfying indexer minimum stake requirement, \n
indexer: must be registered at Graphcast Registry or is a Graph Account, correspond to and Indexer statisfying indexer minimum stake requirement"
)]
pub id_validation: IdentityValidation,
}

impl Config {
/// Parse config arguments
pub fn args() -> Self {
// TODO: load config file before parse (maybe add new level of subcommands)
let config = Config::parse();
init_tracing(config.log_format.clone()).expect("Could not set up global default subscriber for logger, check environmental variable `RUST_LOG` or the CLI input `log-level`");
config
}

/// Validate that private key as an Eth wallet
fn parse_key(value: &str) -> Result<String, WalletError> {
// The wallet can be stored instead of the original private key
let wallet = build_wallet(value)?;
let addr = wallet_address(&wallet);
info!(address = addr, "Resolved Graphcast id");
Ok(String::from(value))
}
}

This file defines the Config struct and its associated methods for handling configuration options of our Radio. This outlines the basic configuration that all Radios have to define.

The configuration options can be provided through command-line arguments, environment variables, or a combination of both. The Config struct parses and validates these options, it also initializes the tracing system for logging purposes.

Methods

  • args(): Parses and returns the configuration options from command-line arguments and environment variables.
  • parse_key(value: &str): Validates a given private key by attempting to create an Ethereum wallet with it. Returns the private key as a string if successful.

Instantiate the essentials

From here on, all following code will be in the main function. To start off, we define a name for our Radio, read the provided environment variables and instantiate our configuration struct.

// This can be any string
let radio_name = "ping-pong".to_string();
// Loads the environment variables from .env
dotenv().ok();

// Instantiates the configuration struct based on provided environment variables or CLI args
let config = Config::args();
let _parent_span = tracing::info_span!("main").entered();

Now let's instantiate a few variables that will do all the heavy lifting for us.

// Subtopics are optionally provided and used as the content topic identifier of the message subject,
// if not provided then they are usually generated based on indexer allocations
let subtopics: Vec<String> = vec!["ping-pong-content-topic".to_string()];

// GraphcastAgentConfig defines the configuration that the SDK expects from all Radios, regardless of their specific functionality
let graphcast_agent_config = GraphcastAgentConfig::new(
config.private_key.expect("No private key provided"),
config.indexer_address,
radio_name,
config.registry_subgraph,
config.network_subgraph,
config.id_validation.clone(),
config.graph_node_endpoint,
None,
Some("testnet".to_string()),
Some(subtopics),
None,
None,
None,
None,
Some(true),
// Example ENR address
Some(vec![String::from("enr:-JK4QBcfVXu2YDeSKdjF2xE5EDM5f5E_1Akpkv_yw_byn1adESxDXVLVjapjDvS_ujx6MgWDu9hqO_Az_CbKLJ8azbMBgmlkgnY0gmlwhAVOUWOJc2VjcDI1NmsxoQOUZIqKLk5xkiH0RAFaMGrziGeGxypJ03kOod1-7Pum3oN0Y3CCfJyDdWRwgiMohXdha3UyDQ")]),
None,
)
.await
.unwrap_or_else(|e| panic!("Could not create GraphcastAgentConfig: {e}"));

GraphcastAgentConfig takes in an optional vector for content topics. Here we explicitly provide a singleton vector of "ping-pong-content-topic", but you can define topics based on the radio's use case needs. If you leave the field as None, then the agent will automatically fetch your indexer's active allocations and create a list of topics in the format of radio application name + the allocated subgraph deployments' IPFS hash.

Next, we will instantiate a GraphcastAgent:

debug!("Initializing the Graphcast Agent");
let (graphcast_agent, waku_msg_receiver) = GraphcastAgent::new(graphcast_agent_config)
.await
.expect("Could not create Graphcast agent");

GraphcastAgent is the main struct through which the Radios communicate with the SDK.

And lastly for the setup part, we need to run two one-off setters for GraphcastAgent and for the incoming messages store:

// A one-off setter to load the Graphcast Agent into the global static variable
_ = GRAPHCAST_AGENT.set(graphcast_agent);

// A one-off setter to instantiate an empty vec before populating it with incoming messages
_ = MESSAGES.set(Arc::new(Mutex::new(vec![])));

Awesome, we're all set to start with the actual Radio logic now!

Sending messages

We'll define a helper function that holds the logic of sending messages to the Graphcast network:

// Helper function to reuse message sending code
async fn send_message(payload: SimpleMessage) {
if let Err(e) = GRAPHCAST_AGENT
.get()
.expect("Could not retrieve Graphcast agent")
.send_message(
// The identifier can be any string that suits your Radio logic
// If it doesn't matter for your Radio logic (like in this case), you can just use a UUID or a hardcoded string
"ping-pong-content-topic",
payload,
Utc::now().timestamp(),
)
.await
{
error!(error = tracing::field::debug(&e), "Failed to send message");
};
}

Again, the identifier that we define as ping-pong-content-topic can be any string that suits your Radio logic, if it doesn't really matter for your use case (like in the ping-pong Radio case) you can just use a UUID or a hardcoded string.

Receiving and handling messages

We now know how to send message, but how do we receive and handle message from other network participants?

After GossipAgent validates the incoming messages, we provide a custom callback handler that specifies what to do with the message. In this handler we cache the message for later aggregation and processing, but depending on your Radio use case you are free any data storage option - a database, a custom data structure or a simple vector.

Here is a simple handler that does just that:

// The handler specifies what to do with incoming messages.
// This is where you can define multiple message types and how they gets handled by the radio
// by chaining radio payload typed decode and handler functions
tokio::spawn(async move {
for msg in waku_msg_receiver {
trace!(
"Radio operator received a Waku message from Graphcast agent, now try to fit it to Graphcast Message with Radio specified payload"
);
let _ = GRAPHCAST_AGENT
.get()
.expect("Could not retrieve Graphcast agent")
.decoder::<SimpleMessage>(msg.payload())
.await
.map(|msg| {
msg.payload.radio_handler();
})
.map_err(|err| {
error!(
error = tracing::field::debug(&err),
"Failed to handle Waku signal"
);
err
});
}
});

GRAPHCAST_AGENT
.get()
.expect("Could not retrieve Graphcast agent")
.register_handler()
.expect("Could not register handler");

The main loop

Great, we're almost there! We have a way to pass messages back and forth 🏓. But sending a one-off message is no fun, we want to create some sort of scheduled and continuous logic of message exchange, and perhaps the easiest way to do that is to use a block number as cue.

We'll start listening to Ethereum blocks coming from the Graph Node and on each block we'll do a simple check - if the block number is even we'll send a "Ping" message, and if it's odd we'll process the messages we've received. After processing the messages we'll clear our store.

let mut block_number = 0;

loop {
block_number += 1;
info!(block = block_number, "🔗 Block number");
if block_number & 2 == 0 {
// If block number is even, send ping message
let msg = SimpleMessage::new(
"table".to_string(),
std::env::args().nth(1).unwrap_or("Ping".to_string()),
);
send_message(msg).await;
} else {
// If block number is odd, process received messages
let messages = AsyncMutex::new(
MESSAGES
.get()
.expect("Could not retrieve messages")
.lock()
.expect("Could not get lock on messages"),
);
for msg in messages.lock().await.iter() {
if msg.content == *"Ping" {
let replay_msg = SimpleMessage::new("table".to_string(), "Pong".to_string());
send_message(replay_msg).await;
};
}

// Clear message store after processing
messages.lock().await.clear();
}

// Wait before next block check
sleep(Duration::from_secs(5));
}

The finished Radio

Congratulations, you've now written you first full Graphcast Radio! The finished code is also available in this repo, the only important difference is in the dependencies.

That's awesome. But how do we run it?

You can start up the ping-pong Radio using cargo run.

You can spawn more instances of the ping-pong Radio and examine how they interact with each other in the terminal logs.

Now there's just one more thing to do - have fun examining the logs & be proud of yourself - you made it! 🥂 From here on out, the only limit to the Radios you can build is your own imagination.

- + \ No newline at end of file diff --git a/graphcast/sdk/registry.html b/graphcast/sdk/registry.html index 167878f7..6ad831d0 100644 --- a/graphcast/sdk/registry.html +++ b/graphcast/sdk/registry.html @@ -5,13 +5,13 @@ Registry | GraphOps Docs - +

Registry

The Graphcast Registry contracts allow an address to set a GraphcastID by calling setGraphcastID(indexer_address, graphcastID_address) as either an Indexer or an Indexer operator, or calling setGraphcastID(graphcastID_address) as the Indexer address. The relationship between an Indexer address to its GraphcastID is limited to 1:1, and cannot be set to itself. This restriction provides consistency and security for the Indexer identity to operate on Graphcast as one GraphcastID operating across Radio applications. To learn more about the registry, you can check out the Github repository.

There are also subgraphs for these registry contracts. They provide information on both the Indexer registration status and the GraphcastID registration status, specifically mapping the indexer registered on The Graph service registry contract to GraphcastID registered on the Graphcast registry contract.

Register a Graphcast ID

The Graphcast Registry contract maps Graphcast IDs to Indexers in the Graph Protocol. With a unique Graphcast ID, an Indexer can sign messages for the Radio, eliminating the need to expose their private Indexer (or Indexer Operator) key or mnemonic. This provides an added layer of security, protecting Indexers' sensitive information while enabling participation in the Graphcast Network.

Here is a brief overview of the accounts you'll be interacting with:

Account NameDescription
Indexer AccountThe existing account associated with your Graph Protocol Indexer. This may be a Token Lock Contract address, or a multisig or EOA address.
Indexer Operator AccountAn account you have registered as an Operator for your Indexer. You can use the Operator account that you pass to indexer-agent.
Graphcast ID AccountA new account that you will create that is used by Graphcast Radio instances to sign messages on behalf of your Indexer.

You'll need to use a registered Indexer Operator account for your Indexer to register a Graphcast ID.

tip

You can register multiple Operators for your Indexer in parallel. If you would prefer not to import the Operator account that you use with indexer-agent into your wallet in order to register your Graphcast ID, you can generate and register a dedicated operator account for this purpose. After you have registered your Graphcast ID, you can deregister the dedicated operator if you desire.

  1. Generate a new Ethereum account to act as your Graphcast ID, keeping the details safe. Be sure to select the Ethereum network, and save the mnemonic, as well as the address and private key for the first account. This is your Graphcast ID.
  2. Import your Indexer Operator private key into your wallet (e.g. MetaMask or Frame) in order to send a transaction to register your Graphcast ID.
  3. Navigate to the Graphcast registry contract for your preferred network and register your Graphcast ID.
  4. Call setGraphcastIDFor(indexer_address, graphcast_id), passing in your Indexer Address and Graphcast ID. Neither address should be your Indexer Operator address that is being used to sign the transaction.
  5. Submit your transaction and wait for it to be included in a block.

Registry endpoints

NetworkRegistry ContractSubgraph API
Ethereum-mainnet0x89f97698d6006f25570cd2e31737d3d22aedcbcfhttps://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-mainnet
Ethereum-goerli0x26ebbA649FAa7b56FDB8DE9Ea17aF3504B76BFA0https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-goerli
Arbitrum-one0xfae79e8cb8fbac2408e5baf89262bd92b6ca464ahttps://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-arb-one
Arbitrum-goerli0x50c2d70a41ecefe4cc54a331457ea204ecf97292https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-arbitrum-go
info

Each Graphcast ID can be associated with a single Indexer. To revoke a Graphcast ID for your Indexer, call setGraphcastIDFor(indexer_address, graphcast_id) with a Graphcast ID of 0x0 using a registered Indexer Operator Account.

Subgraph APIs

Here we list out the APIs the team supports actively. For network subgraph endpoint, We recommend you to expose your indexer-service's endpoint at /network queries with authentication. You can also index and serve registry subgraph but they are not currently deployed on the decentralized network.

Here are the endpoints available on the hosted service.

Protocol NetworkGraphcast NetworkRegistry Subgraph EndpointNetwork Subgraph Endpoint
Ethereum Mainnetmainnethttps://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-mainnethttps://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet
Goerlitestnethttps://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-goerlihttps://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli/network
Arbitrum-Onemainnethttps://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-arb-onehttps://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-arbitrum
Arbitrum-Goerlitestnethttps://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-arbitrum-gohttps://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-arbitrum-goerli
- + \ No newline at end of file diff --git a/index.html b/index.html index ea554cf5..cd73da41 100644 --- a/index.html +++ b/index.html @@ -5,13 +5,13 @@ GraphOps Docs | GraphOps Docs - +
Image copyright Eko Purnomo, courtesy of the Noun Project

Deploy, monitor and scale your Indexer on Kubernetes using Launchpad

Launchpad provides a toolbox for smoothly operating your Graph Protocol Indexer on Kubernetes

Image copyright Eko Purnomo, courtesy of the Noun Project

Join Graphcast to coordinate with other Indexers using Radios

Run Radios (P2P apps) in your stack to coordinate with other Indexers via the Graphcast Network

- + \ No newline at end of file diff --git a/launchpad/advanced/advanced-kubernetes.html b/launchpad/advanced/advanced-kubernetes.html index 4dd8b749..ed6ad4a3 100644 --- a/launchpad/advanced/advanced-kubernetes.html +++ b/launchpad/advanced/advanced-kubernetes.html @@ -5,13 +5,13 @@ Considerations for Kubernetes installation using FCOS | GraphOps Docs - +

Considerations for Kubernetes installation using FCOS

This guide provides a general walkthrough for installing Kubernetes using Fedora CoreOS (FCOS) as the base operating system.

Prerequisites

Before proceeding with this guide, ensure you have a solid understanding of how FCOS works and the steps required to install and enable FCOS as detailed in Install FCOS Guide.

Additionally, a clear grasp of the fundamental Kubernetes architecture will greatly aid in navigating the guidance outlined ahead.

Key components for Kubernetes Installation

To set up Kubernetes on any node, you will require the kubeadm tool and a compatible container runtime.

Key features of kubeadm include:

  • Cluster Initialization: kubeadm helps you initialize the control plane node of a Kubernetes cluster. It handles tasks like generating TLS certificates, creating the Kubernetes configuration files, and setting up the initial control plane components.

  • Node Joining: You can use kubeadm to add worker nodes (also known as worker or minion nodes) to the cluster. This involves generating the necessary credentials and configurations for nodes to communicate with the control plane.

  • Upgrades: kubeadm assists in upgrading a Kubernetes cluster to a newer version by providing commands to perform version-specific upgrade tasks.

  • Configurations: The tool helps generate the necessary Kubernetes configuration files (e.g., kubeconfig) that enable communication between different components of the cluster.

  • Networking: While kubeadm itself does not handle networking directly, it can help you integrate with various networking solutions, such as Calico, Flannel, or others.

  • Token Management: kubeadm uses tokens for securely joining nodes to the cluster. It manages the generation and distribution of these tokens.

  • Certificate Management: kubeadm manages TLS certificates required for secure communication between cluster components.

  • Configuration Validation: kubeadm performs preflight checks to validate whether the host system is ready for cluster creation or joining.

note

If you opt for a multi-node Kubernetes cluster, your Butane configurations will differ based on the specific role each node plays, whether it's a control plane or a worker node.

Butane config for Kubernetes control-planes

Running kubeadm init is the first step in setting up the Kubernetes control plane, but there are several additional tasks you need to perform to ensure that the control plane is fully functional and secure:

  1. Install kubectl: After running kubeadm init, you'll receive instructions on how to set up the kubectl command-line tool will be used to interact with the Kubernetes cluster.

  2. Set Up Network Plugin: Kubernetes requires a network plugin to enable communication between pods and nodes. Choose a network plugin that suits your needs (e.g., Calico, Flannel, Cilium) and install it on the cluster.

  3. Secure the Control Plane: Apply security best practices to the control plane components. For example, you can restrict access to the API server, enable RBAC (Role-Based Access Control), and set up authentication and authorization mechanisms.

  4. Back Up Certificates: Back up the Kubernetes certificates generated during the kubeadm init process. These certificates are critical for secure communication within the cluster.

  5. Configure Load Balancing: If you're setting up a high-availability control plane, you might need to configure load balancing for the API server to distribute traffic among multiple control plane nodes.

Remember that this list provides a general overview of the tasks you need to complete after running kubeadm init. The specific steps may vary depending on your cluster's requirements and the components you choose to install.

Butane config for Kubernetes worker nodes

On a worker node you need to perform the following steps for installing Kubernetes:

  1. Install the Container Runtime of your choice. This runtime is responsible for managing and running containers.

  2. Install the kubelet: The kubelet is the primary node agent responsible for managing containers on the node and ensuring they match the desired state described in the Kubernetes manifest files.

  3. Run kubeadm join: Once the container runtime and kubelet are installed and properly configured on the worker node, you can run the kubeadm join command to connect the node to the cluster's control plane.

  4. Network Configuration: After the node is joined to the cluster, you might need to configure network plugins (e.g., Calico, Flannel) to enable communication between nodes and pods.

- + \ No newline at end of file diff --git a/launchpad/client-side-tooling.html b/launchpad/client-side-tooling.html index acf91f9c..b203906e 100644 --- a/launchpad/client-side-tooling.html +++ b/launchpad/client-side-tooling.html @@ -5,13 +5,13 @@ Client Side Tooling | GraphOps Docs - +

Client Side Tooling

Launchpad comes with an opinionated set of tools on your local machine, layered over one another to provide a declarative workflow to manage your cluster software stack.

Client Side Stack

These tools do not run on your servers, but on your local machine. They form the command & control center that you use to send instructions to your cluster.

Installing on your local machine

Launchpad comes with a task to install local dependencies on your machine. See the Quick Start Guide for more information.

Understanding the tools in the client-side stack

Taskfile

Taskfile is a simple task runner for automation and devops tasks. It allows you to define tasks in a single file, Taskfile.yml, and run them in a consistent, cross-platform way. It can be used to automate anything from building and deploying applications to running tests and linting code. Taskfile is written in Go and is easy to install and use.

Launchpad uses task as the primary command line interface. You can also define your own tasks!

Helm

Helm is a package manager for Kubernetes that helps you manage and automate the deployment of your applications. It allows you to define, install, and upgrade Kubernetes resources in a consistent, versioned way. Helm uses a simple templating syntax to allow you to parameterize your deployments and create reusable chart templates. Helm also provides a variety of pre-built charts for popular software.

Launchpad uses Helm to deploy packages (Helm Charts) into your cluster.

Helmfile

Helmfile is a tool for managing multiple Helm charts together using a single file. It allows you to define a set of Helm releases together in a file, and then use a single command to install, upgrade, or delete all of the releases at once. This makes it easy to manage complex, multi-chart applications. Helmfile is written in Go and is easy to install and use.

Launchpad uses Helmfile to declare and manage sets of related Helm releases.

Kustomize

Kustomize lets you customize raw, template-free YAML files for multiple purposes, leaving the original YAML untouched and usable as is. It is used by helmfile for some of its features.

Kubectl

Kubectl is the command-line interface for Kubernetes that allows you to deploy, scale, and manage applications on a Kubernetes cluster. It provides a simple, easy-to-use command-line interface for performing common Kubernetes tasks such as creating and managing pods, services, and deployments.

Launchpad uses Kubectl to interact with your Kubernetes cluster.

- + \ No newline at end of file diff --git a/launchpad/design-principles.html b/launchpad/design-principles.html index a33752d9..97737984 100644 --- a/launchpad/design-principles.html +++ b/launchpad/design-principles.html @@ -5,13 +5,13 @@ Design Principles | GraphOps Docs - + - + \ No newline at end of file diff --git a/launchpad/faq.html b/launchpad/faq.html index 23063d24..f532d33f 100644 --- a/launchpad/faq.html +++ b/launchpad/faq.html @@ -5,13 +5,13 @@ Frequently Asked Questions | GraphOps Docs - + - + \ No newline at end of file diff --git a/launchpad/guides/arbitrum-archive-kubernetes-guide.html b/launchpad/guides/arbitrum-archive-kubernetes-guide.html index 71a659a1..b01f9272 100644 --- a/launchpad/guides/arbitrum-archive-kubernetes-guide.html +++ b/launchpad/guides/arbitrum-archive-kubernetes-guide.html @@ -5,14 +5,14 @@ Arbitrum Archive Mainnet Node Guide | GraphOps Docs - +

Arbitrum Archive Mainnet Node Guide

danger

This Quick Start guide has not yet been updated for Launchpad V2.

This guide is intended to be an end to end walk-through of running an Arbitrum Archive Mainnet Node in an existing Kubernetes cluster. Sync times are reported to be in the range of 1 week on dedicated hardware. The node consists of 2 parts, the classic part and the nitro hardfork. The classic part is only required to request archive data for blocks before the hardfork and takes the aforementioned 1 weeks to sync from scratch. The nitro history is shorter and can be quickly synced within 3 days.

Arbitrum Nitro has a built-in proxy to redirect queries with block numbers below it’s genesis block (they’re sent to the Arbitrum Classic node)

Prerequisites

All the Launchpad Prerequisites apply if running a Kubernetes cluster using Launchpad, so be sure to read them first. This guide can be used with existing Kubernetes clusters as well.

You will need:

  • an ethereum-mainnet RPC endpoint
  • CPU: 4 Cores / 8 Threads
  • RAM: 16 GiB
  • Storage: 3 TiB NVMe SSD

If running a Kubernetes cluster using Launchpad

  1. Check that the cluster is running and healthy - review Quick Start guide for more info.
  2. In your private infra repo pull in latest launchpad-starter changes
task launchpad:pull-upstream-starter
  1. Pull in latest-core changes
task launchpad:update-core
  1. blockchain node data snapshot Arbitrum Classic provides functionality to download data from a snapshot. Review all files in [<your-private-copy-of-launchpad-starter>/helmfiles/release-names/arbitrum-mainnet/](https://github.com/graphops/launchpad-starter/blob/main/helmfiles/release-values/arbitrum-mainnet/) before deploying the chart
arbitrum:
restoreSnapshot:
enable: true
snapshotUrl: https://a-link-to-your-snapshot-archive.tar.gz
mode: streaming # or multipart depending on chain
  1. connect to eth-mainnet-rpc-node Both Arbitrum Classic and Arbitrum Nitro connect to l1 via the following commands:
arbitrum:
extraArgs:
- --node.chain-id=42161 # determines Arbitrum network - 42161 mainnet
- --l1.url=http://a-link-to-your-eth-mainnet-url:8545
nitro:
extraArgs:
- --http.api=net,web3,eth,debug
- --l2.chain-id=42161 # determines Arbitrum network - 42161 mainnet
- --l1.url=http://a-link-to-your-eth-mainnet-url:8545
- --node.rpc.classic-redirect=http://arbitrum-classic-archive-trace-mainnet-0:8547/
- --init.url=https://snapshot.arbitrum.io/mainnet/nitro.tar

Deploying with helm in a Kubernetes cluster outside Launchpad

You can find blockchain related helm packages 'here'

Given that Arbitrum needs both Nitro and classic to run use the following commands:

Deploy Arbitrum Classic

We'll first deploy Arbitrum Classic as Arbitrum Nitro needs to connect to the Classic endpoint.

Create a values arbitrum-classic.yaml file with the following contents

arbitrum:
extraArgs:
- --node.chain-id=42161 # determines Arbitrum network - 42161 mainnet
- --l1.url=http://a-link-to-your-eth-mainnet-url:8545
restoreSnapshot:
enable: true
snapshotUrl: https://a-link-to-your-snapshot-archive.tar.gz
mode: streaming # or multipart depending on chain

Deploy helm-chart:

helm repo add graphops http://graphops.github.io/launchpad-charts
helm install --dry-run arbitrum-classic graphops/arbitrum-classic:latest --namespace arbitrum-mainnet --value arbitrum-classic.yaml

Deploy Arbitrum Nitro

Create a values arbitrum-nitro.yaml file with the following contents

nitro:
extraArgs:
- --http.api=net,web3,eth,debug
- --l2.chain-id=42161 # determines Arbitrum network - 42161 mainnet
- --l1.url=http://a-link-to-your-eth-mainnet-url:8545
- --node.rpc.classic-redirect=http://arbitrum-classic:8547/ # replace `arbitrum-classic` with the name of your arbitrum-classic release deployed at the previous step
- --init.url=https://snapshot.arbitrum.io/mainnet/nitro.tar

Deploy helm-chart:

helm install --dry-run arbitrum-nitro graphops/arbitrum-classic:latest --namespace arbitrum-mainnet --value arbitrum-nitro.yaml
- + \ No newline at end of file diff --git a/launchpad/guides/avalanche-archive-kubernetes.html b/launchpad/guides/avalanche-archive-kubernetes.html index 3a036104..5565a91b 100644 --- a/launchpad/guides/avalanche-archive-kubernetes.html +++ b/launchpad/guides/avalanche-archive-kubernetes.html @@ -5,14 +5,14 @@ Avalanche Archive Mainnet Node Guide | GraphOps Docs - +

Avalanche Archive Mainnet Node Guide

danger

This Quick Start guide has not yet been updated for Launchpad V2.

This guide is intended to be an end to end walk-through of running an Avalanche Archive Mainnet Node in an existing Kubernetes cluster. Sync times are reported to be in the range of 3 weeks on dedicated hardware.

Prerequisites

All the Launchpad Prerequisites apply if running a Kubernetes cluster using Launchpad, so be sure to read them first. This guide can be used with existing Kubernetes clusters as well.

For avalanche workload you will need:

  • CPU: 4 Cores / 8 Threads
  • RAM: 16 GiB
  • Storage: 3 TiB NVMe SSD

If running a Kubernetes cluster using Launchpad

  1. Check that the cluster is running and healthy - review Quick Start guide for more info.
  2. In your private infra repo pull in latest launchpad-starter changes
task launchpad:pull-upstream-starter
  1. Pull in latest-core changes
task launchpad:update-core
  1. Check default values- double-check values and update as needed in <your-private-copy-of-launchpad-starter>/helmfiles/release-names/arbitrum-mainnet/avalanche-archive-trace-mainnet-0.yaml

  2. Deploy avalanche-mainnet namespace

task releases:apply avalanche-mainnet

Deploying with helm in a Kubernetes cluster outside Launchpad

You can find blockchain related helm packages here

By default avalanche is told what type of node to run by the following default toml config:

configTemplate: |
# Store configuration in toml format
snowman-api-enabled = "false"
eth-apis = [
"eth",
"eth-filter",
"net",
"web3",
"internal-eth",
"internal-blockchain",
"internal-transaction",
"internal-tx-pool",
"internal-account"
]
metrics-enabled = "true"
pruning-enabled = "false"
state-sync-enabled = "false"

Override the above config by providing a new one in a values file and deploy:

helm repo add graphops http://graphops.github.io/launchpad-charts
helm install --dry-run avalanche graphops/avalanche:latest --namespace avalanche-mainnet --values avalanche-mainnet.yaml
- + \ No newline at end of file diff --git a/launchpad/guides/celo-archive-kubernetes-guide.html b/launchpad/guides/celo-archive-kubernetes-guide.html index ae58e344..6e0ff8eb 100644 --- a/launchpad/guides/celo-archive-kubernetes-guide.html +++ b/launchpad/guides/celo-archive-kubernetes-guide.html @@ -5,14 +5,14 @@ Celo Archive Mainnet Node Guide | GraphOps Docs - +

Celo Archive Mainnet Node Guide

danger

This Quick Start guide has not yet been updated for Launchpad V2.

This guide is intended to be an end to end walk-through of running an Celo Archive Mainnet Node in an existing Kubernetes cluster. Sync times are reported to be in the range of 4 days on dedicated hardware.

Prerequisites

All the Launchpad Prerequisites apply if running a Kubernetes cluster using Launchpad, so be sure to read them first. This guide can be used with existing Kubernetes clusters as well.

For Celo workload you will need:

  • CPU: 4 Cores / 8 Threads
  • RAM: 16 GiB
  • Storage: 3 TiB NVMe SSD

If running a Kubernetes cluster using Launchpad

  1. Check that the cluster is running and healthy - review Quick Start guide for more info.
  2. In your private infra repo pull in latest launchpad-starter changes
task launchpad:pull-upstream-starter
  1. Pull in latest-core changes
task launchpad:update-core
  1. Check default values- double-check values and update as needed in <your-private-copy-of-launchpad-starter>/helmfiles/release-names/arbitrum-mainnet/celo-archive-trace-mainnet-0.yaml

  2. Deploy celo-mainnet namespace

task releases:apply celo-mainnet

Deploying with helm in a Kubernetes cluster outside Launchpad

You can find blockchain related helm packages here

Create a values celo-mainnet.yaml file with the following contents or similar:

celo:
extraArgs:
- --verbosity 3
- --syncmode full
- --gcmode archive
- --txlookuplimit=0
- --cache.preimages
- --http.corsdomain=*
- --ws # enable ws
- --http.api=eth,net,web3,debug,admin,personal

Deploy helm-chart:

helm repo add graphops http://graphops.github.io/launchpad-charts
helm install --dry-run celo graphops/celo:latest --namespace celo-mainnet --values celo-mainnet.yaml
- + \ No newline at end of file diff --git a/launchpad/guides/goerli-indexer-guide.html b/launchpad/guides/goerli-indexer-guide.html index c9fd1141..057e59e5 100644 --- a/launchpad/guides/goerli-indexer-guide.html +++ b/launchpad/guides/goerli-indexer-guide.html @@ -5,13 +5,13 @@ Goerli Indexer Guide | GraphOps Docs - +

Goerli Indexer Guide

This guide is intended to be an end to end walk-through of setting up an Indexer running on the Graph Protocol Testnet on the Ethereum Goerli network.

Prerequisites

All the Launchpad Prerequisites apply, so be sure to read them first.

You will need:

  • At least one server running Ubuntu 22.04 with keypair authenticated SSH access
  • A web browser with MetaMask installed

What we're going to do

  1. Create all the relevant Ethereum accounts, fund them, register them on-chain with the protocol
  2. Follow the Quick Start guide to set up our local machine, and then set up our cluster of servers, and finally deploy the Graph Stack
  3. Configure DNS, verify ingress and TLS is working
  4. Allocate to subgraph deployments
  5. Verify that we are serving queries

Create the Indexer and Operator Ethereum accounts

We will need to set up two new Ethereum accounts:

  1. The Indexer account: this account is your Indexer's identity, and is used to stake GRT into the protocol. This account owns your in-protocol GRT. This key should be kept very safe!
  2. The Operator account: this account is authorised to perform some operational actions (like managing allocations) on behalf of your Indexer. The key for this account will live on your server(s). The Indexer Software uses this account to automate interactions with the protocol. This account does not own your GRT, but can take actions that put your GRT at risk (e.g. submitting a bad POI could make you liable to slashing). You can replace the Operator account with a new one at any time.

Generating mnemonics for your new accounts

Ian Coleman's BIP39 generator is great for quickly generating new mnemonics and their derived accounts.

  1. Set the "Coin" to "ETH - Ethereum".
  2. Click Generate
  3. Take note of: the mnemonic and (optionally, if you follow this guide) all details for the first derived address

Generate two new mnemonics and save their details.

tip

When setting up your Indexer account for mainnet, use a more secure method, like a hardware wallet, for generating your Indexer account.

Funding our new accounts

Both our new accounts will need ETH in order to pay for transaction costs. The Operator account will be used for all automated protocol interactions, so it will need a lot more ETH than the Indexer account. The Indexer account will need to pay for gas to stake GRT into the protocol, and set various metadata and parameters.

For the Goeli testnet, there are a number of ETH faucets that can be used to fund your new accounts. You can find various options here: https://faucetlink.to/goerli

Our Indexer account will also need at least 100,000 Goerli GRT in order to stake in the protocol. If you are a MIPs participant, you should follow the relevant instructions to get that GRT. Otherwise there is a Goerli GRT faucet available in the Graph Protocol Discord.

Registering our Indexer and Operator accounts

We will use The Graph's Testnet Network app to register our new accounts with the protocol on-chain.

  1. Use MetaMask to import the private key for your Indexer mnemonic's first derived account. This will allow us to transact as our Indexer.
  2. Navigate to "Indexing" under your profile dropdown menu
  3. Click the Stake button and Stake at least 100k GRT (first allowing GRT token access with an approval transaction, followed by the stake transaction)
  4. Navigate to "Settings" under your profile dropdown menu, and then to "Operators" in the left hand menu
  5. Click the plus symbol, paste in your Operator mnemonic's first derived address, click Add and submit the transaction
  6. (optionally) Navigate to other settings to configure profile details

Launching off the pad!

Now that our accounts are ready, let's follow the Quick Start guide to:

  1. Create a new repository for our infrastructure
  2. Configure our local machine to command our cluster
  3. Configure our servers into a Kubernetes cluster
  4. Deploy core cluster services and the Graph Indexing stack into our cluster
- + \ No newline at end of file diff --git a/launchpad/guides/install-fcos.html b/launchpad/guides/install-fcos.html index c3d2641a..1fd74bee 100644 --- a/launchpad/guides/install-fcos.html +++ b/launchpad/guides/install-fcos.html @@ -5,14 +5,14 @@ FCOS Installation | GraphOps Docs - +

FCOS Installation

Fedora CoreOS (FCOS) is an open-source container-focused operating system that is:

  • minimal
  • automatically updated
  • designed for clusters but can also be used standalone.

It is optimized for Kubernetes and includes technology from CoreOS Container Linux and Fedora Atomic Host, providing a secure and scalable container host for workloads.

Here are key differences between FCOS and traditional operating systems:

  • Package management: FCOS uses rpm-ostree for atomic updates, while traditional OSes use package managers like apt or yum.
  • Security: FCOS includes SELinux for enhanced security, while traditional OSes may require additional security configurations.
  • Containerization: FCOS is designed for container workloads, while traditional OSes may need extra setup for containers.
  • Automatic updates: FCOS provides automatic updates, while traditional OSes may require manual updates.
  • Minimal footprint: FCOS is optimized for running containers at scale, while traditional OSes have a broader range of software and features.

This guide takes you through the different considerations required to install and configure FCOS. NOTE the following instructions are for guidance only and do not represent step by step instructions.

Picking the right installation method

To install and configure FCOS, you need to use the coreos-installer tool. The following options for booting the OS are available:

  • Installing on bare metal:

    • Booting from live ISO using a KVM
    • Booting from PXE or iPXE
    • Booting from a container
    • Installing coreos-installer using cargo (not officially documented) but a good option for anyone running Hetzner servers or any other provider that doesn't offer PXE/iPXE boot and is not officially supporting FCOS images. The officially supported alternative for this option would be booting from live ISO. Example of coreos-installer install using cargo:
      # install packages necessary for coreos-installer
      apt update && apt upgrade
      apt install pkg-config libssl-dev libzstd-dev
      # install cargo
      curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
      source "$HOME/.cargo/env"
      cargo install coreos-installer
      # install butane
      wget https://github.com/coreos/butane/releases/download/$YOUR_BUTANE_VERSION/butane-x86_64-unknown-linux-gnu
      chmod +x butane-x86_64-unknown-linux-gnu
  • Installing on cloud servers/VMs:

    • official FCOS images can be used to provision new servers for AWS and GCP - can be found under Cloud Launchable section on downloads page

Once an installation image is picked, time to customize the system.

Create a configuration

FedoraCoreOS follows the principles of immutable infrastructure, where the operating system and application components are treated as immutable, meaning they are not modified after deployment. Updates are delivered through "automatic updates" managed by the OS , following a rolling update strategy. New instances with updated images are provisioned, and old instances are replaced.

Treating the operating system as immutable:

  • reduces configuration drift
  • enhances system reliability
  • stateful components or data can still exist outside the operating system and have their own mechanisms for persistence and updates

To customize a Fedora CoreOS (FCOS) system, a configuration file needs to be provided which will be used by Ignition to provision the system.

This file will be used to customize various aspects of the system, such as creating a user, adding a trusted SSH key, enabling systemd services, and more.

To create an ignition file:

  • define a butane config in YAML format using the specification. Your butane file should contain the following minimum sections:
    • Ignition Version: Specify the version of the Ignition configuration format to use
    • Storage Configuration: Define the disk layout and filesystems for your FCOS installation. This includes partitioning, formatting, and mounting options.
    • Passwd Users: Set up user accounts for logging into the FCOS instance.
    • Systemd Units: Configure systemd units to manage services and perform system-level tasks.
    • Networkd Units: Configure network settings, including network interfaces, IP addressing, and DNS as required.
  • These are just the basic building blocks for a Butane configuration file. Depending on your specific requirements, you may need to include additional configuration options such as users, SSH keys, systemd units, networking, etc. You can refer to the Butane documentation and the FCOS documentation for more details and advanced configuration options.
  • An example of a butane file you can get started with containing the minimum requirement:
    variant: fcos
    version: 1.4.0
    storage:
    disks:
    - device: /dev/sda
    partitions:
    - number: 1
    size: 512MiB
    label: root
    filesystem: ext4
    should_exist: true
    filesystems:
    - name: root
    mount:
    path: /
    device: /dev/disk/by-partlabel/root
    format: true
    passwd:
    users:
    - name: myuser
    ssh_authorized_keys:
    - ssh-rsa AAAA...
    systemd:
    units:
    - name: my-service.service
    enable: true
    contents: |
    [Unit]
    Description=My Service

    [Service]
    ExecStart=/usr/bin/my-service
    networkd:
    units:
    - name: 00-eth0.network
    contents: |
    [Match]
    Name=eth0

    [Network]
    DHCP=ipv4
  • use the butane cli (formally Fedora CoreOS Config Transpiler (fcct)) to convert the YAML config into a valid ignition configuration (JSON format).
    butane --pretty --strict < /tmp/config.bu > /tmp/config.ign
    # or if using podman
    sudo podman run --interactive --rm [quay.io/coreos/butane:release](http://quay.io/coreos/butane:release) --pretty --strict < /tmp/config.bu > /tmp/config.ign

Install new OS with coreos-installer

Next pass the config.ign file to coreos-installer.

coreos-installer install /dev/sda -i config.ign /tmp/config.ign

If you've run the above command the folowing will happen on the host

  1. The CoreOS Installer will install the Fedora CoreOS operating system onto the specified device (in this case, /dev/sda) using the provided Ignition configuration file (/tmp/config.ign).
  2. The installation process will partition and format the device, copy necessary files, and configure the bootloader.
  3. At this point user should reboot the system.
  4. Upon reboot, the system will start up with the newly installed Fedora CoreOS.
  5. After the initial boot, Fedora CoreOS will automatically manage updates using the rpm-ostree tool. It will fetch and apply updates in an atomic manner, ensuring a consistent and reliable system.
  6. You can log in to the system and start using Fedora CoreOS. As an immutable operating system, any modifications to the system outside of automatic updates are typically done by updating the Ignition configuration file and performing a reboot to apply the changes.

Next steps

The outlined steps mark the initial phase of grasping the workings of FCOS. For the different components that you'd need to include in your butane config to install Kuberneter follow Advanced Kubernetes.

- + \ No newline at end of file diff --git a/launchpad/intro.html b/launchpad/intro.html index 54c91ed0..974f5c6d 100644 --- a/launchpad/intro.html +++ b/launchpad/intro.html @@ -5,13 +5,13 @@ Introduction | GraphOps Docs - +

Introduction

Launchpad is a toolkit for running a Graph Protocol Indexer on Kubernetes. It aims to provide the fastest path to production multi-chain indexing, with sane security and performance defaults. It should work well whether you have a single node cluster or twenty. It is comprised of an opinionated set of tools on your local machine, layered over one another to provide a declarative workflow to manage your deployments stack.

There are three major components to be aware of:

  1. Launchpad Starter (graphops/launchpad-starter): A starting point for every new Launchpad deployment
  2. Launchpad Charts (graphops/launchpad-charts): A collection of Helm Charts for blockchains and web3 apps
  3. Launchpad Namespaces (graphops/launchpad-namespaces): A collection of preconfigured Kubernetes Namespaces using Helmfile

Launchpad components

Features

  • Actively maintained by GraphOps and contributors
  • An opinionated starter (launchpad-starter) to define and manage your stack in a declarative, version controlled manner
  • A collection of Helm Charts for deploying and monitoring blockchain nodes and Graph Protocol Indexers in Kubernetes, with P2P NodePort support
  • Preconfigured namespaces for core cluster functions (logging, monitoring, etc) and major blockchains
  • An automated dependency update pipeline for graphops/launchpad-charts and graphops/launchpad-namespaces

Next Steps

  • Read the Prerequisites section to understand what you need to bring
  • Read the Quick Start guide to get up and running
  • Look at the repositories above on GitHub to understand how they work
  • Review Advanced Topics to understand more advanced behavior
- + \ No newline at end of file diff --git a/launchpad/other-resources.html b/launchpad/other-resources.html index 5b463a08..ddd1e1c8 100644 --- a/launchpad/other-resources.html +++ b/launchpad/other-resources.html @@ -5,13 +5,13 @@ Other Resources | GraphOps Docs - + - + \ No newline at end of file diff --git a/launchpad/prerequisites.html b/launchpad/prerequisites.html index 242968f3..7ae2f669 100644 --- a/launchpad/prerequisites.html +++ b/launchpad/prerequisites.html @@ -5,13 +5,13 @@ Prerequisites | GraphOps Docs - +

Prerequisites

You will need some things to use Launchpad for your infrastructure:

A basic understanding of infrastructure

We expect that you are familiar with infrastructure basics, including:

  • Linux
  • Networking, DNS
  • SSH and authentication
  • Storage fundamentals
  • Basic system administration

A basic, functional knowledge of git

The Launchpad stack advocates for declarative, version controlled infrastructure. This means the declarative state of your infrastructure will be committed into a private git repo as it evolves over time. You will need to be able to perform basic git workflows like:

  • Staging files (e.g. git add .)
  • Committing changes and pushing code (e.g. git push origin main)
  • Viewing the repo history (e.g. git show, git log, or using GitHub)

More advanced users will benefit from understanding how to pull and rebase, but this is not a requirement.

A basic understanding of operating a Graph Protocol Indexer

We will assume a basic understanding of the Graph Protocol Indexing stack, as well as some of the operational requirements of Indexing.

See Other Resources for links to helpful resources.

A client machine

Launchpad comes with a series of tools that should run on a client device. This is most likely your local machine. These tools should not run on your servers. Instead, they help you instruct your cluster of servers to do what you want.

Currently, Launchpad comes with support for Linux and MacOS clients. Windows is currently not supported, though you may be able to use Launchpad using the Windows Subsystem for Linux.

A Kubernetes cluster

You will need to provision a Kubernetes cluster. This can be a self-managed cluster (see our guide using Fedora CoreOS), or a managed cluster from a major Cloud Provider like AWS or GCP.

Willingness to learn and contribute

Launchpad is a collaborative effort to create the best UX for Graph Protocol Indexers on Kubernetes. The Launchpad stack provides an opinionated set of defaults and recipes for success, but to be an advanced operator you will need to learn Kubernetes and many of the other tools in the stack. With Launchpad, you have guard rails to guide you in your journey towards mastering operating your Indexer on Kubernetes.

Please contribute back when you are able!

- + \ No newline at end of file diff --git a/launchpad/quick-start.html b/launchpad/quick-start.html index 86f31472..0c5b20e5 100644 --- a/launchpad/quick-start.html +++ b/launchpad/quick-start.html @@ -5,7 +5,7 @@ Quick Start | GraphOps Docs - + @@ -16,7 +16,7 @@ The path for this Namespace, under helmfiles, would then look like:

- path: git::https://github.com/graphops/launchpad-namespaces.git@storage/helmfile.yaml?ref=storage-v1.2

pinning to an exact version:

Your ?ref= would look like this, for the storage namespace: ?ref=storage-v1.2.2. The path for this Namespace, under helmfiles, would then look like:

- path: git::https://github.com/graphops/launchpad-namespaces.git@storage/helmfile.yaml?ref=storage-v1.2.2

following the latest canary:

Your ?ref= would look like this, for the storage namespace: ?ref=storage-canary/latest. The path for this Namespace, under helmfiles, would then look like:

- path: git::https://github.com/graphops/launchpad-namespaces.git@storage/helmfile.yaml?ref=storage-canary/latest

We would recommend that you either follow the latest stable releases, or pin to a specific version.

note

For full implementation details and other comprehensive notes about launchpad-namespaces please visit the github repo.

Pulling in starter changes

From time to time, you may want to update your infra repo with the latest changes from our starter.

Launchpad comes with a built in task to do this:

task launchpad:pull-upstream-starter
- + \ No newline at end of file diff --git a/launchpad/server-side-stack.html b/launchpad/server-side-stack.html index 22ed4451..48be58a6 100644 --- a/launchpad/server-side-stack.html +++ b/launchpad/server-side-stack.html @@ -5,13 +5,13 @@ Server Side Stack | GraphOps Docs - +

Server Side Stack

Server Side Stack

Your Kubernetes cluster

note

Launchpad v1 used k0s as the Kubernetes distribution for managing Kubernetes. K0s was picked for this project as it was viewed to be one of the top open-source lightweight certified Kubernetes distributions targeted at public & private clouds, on-premises, edge & hybrid.

Launchpad V2 represents a departure from the previous model of orchestrating the configuration of host machines to form a Kubernetes cluster. Instead, users are now encouraged to bring their own Kubernetes clusters to be used in conjunction with Launchpad. This approach ensures that users are not tied to a specific Kubernetes distribution or mode of installation.

Alternatively, for the indexers seeking more detailed guidance with regards to a Kubernetes setup, we have created guides that outline the steps one should consider when installing and deploying Fedora CoreOS (FCOS) - an auto-updating, minimal, container-focused OS, designed for clusters or standalone use and optimized for Kubernetes. These guides are designed to assist you in getting started with the process of setting up and guide you through the management of your Kubernetes cluster.

If you are bringing your own Kubernetes cluster, feel free to skip to Quick Start.

For guidance on Fedora CoreOS setup and considerations, skip to Install FCOS Guide.

- + \ No newline at end of file diff --git a/mips-resources/intro.html b/mips-resources/intro.html index 8dc81d8e..b2e44603 100644 --- a/mips-resources/intro.html +++ b/mips-resources/intro.html @@ -5,13 +5,13 @@ Introduction | GraphOps Docs - +

Introduction

It's an exciting time to be participating in The Graph ecosystem! During Graph Day 2022 Yaniv Tal announced the sunsetting of the hosted service, a moment The Graph ecosystem has been working towards for many years.

To support the sunsetting of the hosted service and the migration of all of it's activity to the decentralized network, The Graph Foundation has announced the Migration Infrastructure Providers (MIPs) program.

The MIPs program is an incentivization program for Indexers to support them with resources to index chains beyond Ethereum mainnet and help The Graph protocol expand the decentralized network into a multi-chain infrastructure layer.

The MIPs program has allocated 0.75% of the GRT supply (75M GRT), with 0.5% to reward Indexers who contribute to bootstrapping the network and 0.25% allocated to migration grants for subgraph developers using multi-chain subgraphs.

Useful Resources

- + \ No newline at end of file diff --git a/mips-resources/mips-faq.html b/mips-resources/mips-faq.html index c705f99d..e41ae515 100644 --- a/mips-resources/mips-faq.html +++ b/mips-resources/mips-faq.html @@ -5,13 +5,13 @@ MIPs FAQs | GraphOps Docs - +

MIPs FAQs

1. Is it possible to generate a valid proof of indexing (POI) even if a subgraph has failed?

Yes, it is indeed.

For context, the arbitration charter, learn more about the charter here, specifies the methodology for generating a POI for a failed subgraph.

A community member, SunTzu, has created a script to automate this process in compliance with the arbitration charter's methodology. Check out the repo here.

2. Which chain will the MIPs program incentivise first?

The first chain that will be supported on the decentralized network is Gnosis Chain! Formerly known as xDAI, Gnosis Chain is an EVM-based chain. Gnosis Chain was selected as the first given its user-friendliness of running nodes, Indexer readiness, alignment with The Graph and adoption within web3.

3. How will new chains be added to the MIPs program?

New chains will be announced throughout the MIPs program, based on Indexer readiness, demand, and community sentiment. Chains will firstly be supported on the testnet and, subsequently, a GIP will be passed to support that chain on mainnet. Indexers participating in the MIPs program will choose which chains they are interested in supporting and will earn rewards per chain, in addition to earning query fees and indexing rewards on the network for serving subgraphs. MIPs participants will be scored based on their performance, ability to serve network needs, and community support.

4. How will we know when the network is ready for a new chain?

The Graph Foundation will be monitoring QoS performance metrics, network performance and community channels to best assess readiness. The priority is ensuring the network meets performance needs for those multi-chain dapps to be able to migrate their subgraphs.

5. How are rewards divided per chain?

Given that chains vary in their requirements for syncing nodes, and they differ in query volume and adoption, rewards per chain will be decided at the end of that chain's cycle to ensure that all feedback and learnings are captured. However, at all times Indexers will also be able to earn query fees and indexing rewards once the chain is supported on the network.

6. Do we need to index all the chains in the MIPs program or can we pick just one chain and index that?

You are welcome to index whichever chain you'd like! The goal of the MIPs program is to equip Indexers with the tools & knowledge to index the chains they desire and support the web3 ecosystems they are interested in. However, for every chain, there are phases from testnet to mainnet. Make sure to complete all the phases for the chains you are indexing. See The MIPs notion page to learn more about the phases.

7. When will rewards be distributed?

MIPs rewards will be distributed per chain once performance metrics are met and migrated subgraphs are supported by those Indexers. Look out for info about the total rewards per chain mid-way through that chain's cycle.

8. How does scoring work?

Indexers will compete for rewards based on scoring throughout the program on the leaderboard. Program scoring will be based on:

Subgraph Coverage

  • Are you providing maximal support for subgraphs per chain?

  • During MIPs, large Indexers are expected to stake 50%+ of subgraphs per chain they support.

Quality Of Service

  • Is the Indexer serving the chain with good Quality of Service (latency, fresh data, uptime, etc.)?

  • Is the Indexer supporting dapp developers being reactive to their needs?

Is Indexer allocating efficiently, contributing to the overall health of the network?

Community Support

  • Is Indexer collaborating with fellow Indexers to help them get set up for multi-chain?

  • Is Indexer providing feedback to core devs throughout the program or sharing information with Indexers in the Forum?

9. How will the Discord role be assigned?

Moderators will assign the roles in the next few days.

10. Is it okay to start the program on a testnet and then switch to Mainnet? Will you be able to identify my node and take it into account while distributing rewards?

Yes, it is actually expected of you to do so. Several phases are on Görli and one is on the mainnet.

11. At what point do you expect participants to add a mainnet deployment?

There will be a requirement to have a mainnet indexer during phase 3. More infomation on this will be shared in this notion page soon.

12. Will rewards be subject to vesting?

The percentage to be distributed at the end of the program will be subject to vesting. More on this will be shared in the Indexer Agreement.

13. For teams with more than one member, will all the team members be given a MIPs Discord role?

Yes

14. Is it possible to use the locked tokens from the graph curator program to participate in the MIPs testnet?

Yes

15. During the MIPs program, will there be a period to dispute invalid POI?

To be decided. Please return to this page periodically for more details on this or if your request is urgent, please email info@thegraph.foundation

17. Can we combine two vesting contracts?

No. The options are: you can delegate one to the other one or run two separate indexers.

18. KYC Questions?

Please email info@thegraph.foundation

19. I am not ready to index Gnosis chain, can I jump in and start indexing from another chain when I am ready?

Yes

We do not give recommendations on regions. When picking locations you might want to think about where the major markets are for cryptocurrencies.

21. What is “handler gas cost”?

It is the deterministic measure of the cost of executing a handler. Contrary to what the name might suggest, it is not related to the gas cost on blockchains.

- + \ No newline at end of file