Gora enables blockchain programs (smart contracts) to interact with the outside world. Getting financial information from high-quality providers, extracting arbitrary data from public pages, calling online APIs or running Web Assembly code off-chain - is all made possible by Gora. To maintain security and trust, Gora relies on decentralization. A network of independent Gora nodes executes requested operations in parallel and certifies the outcome via reliable consensus procedure.
Customers interact with Gora in one of three roles: investor, node operator or smart contract developer. An investor purchases Gora tokens and delegates them to a node operator, receiving a share of rewards accrued from processing Gora requests. A node operator stakes Gora tokens (their own or investor's) and runs Gora Node Runner software to process Gora requests and receive rewards in Gora tokens. A smart contract developer writes applications that make use of Gora smart contract API's.
This document is aimed mainly at developers or node operators. Software engineers working with Gora-enabled blockchains, as well as companies interested in adding an oracle to blockchains they manage, will find technical information here. For Gora node operators, instructions and background information are also provided. Most recent and relevant products are described first, followed by info on legacy offerings.
App-specific oracles (ASO's) are Gora's response to recent market demands. These are oracles designed to serve a certain web3 application or application type. While a general-purpose oracle strives for maximum flexibility to support all kinds or applications, it may lack specialized data processing or access control features needed for more niche use cases. For example, a sports oracle may want to provide team statistics which requires getting data from several resources and performing floating-point maths on it. A private oracle may want to only serve specific smart contracts or authenticate itself to data sources in a bespoke way.
Feature | General-purpose oracle | App-specific oracle |
---|---|---|
Ease of use | Moderate | High |
Customizability | Low | High |
Restrictions on data sources | Not supported | Up to ASO owner |
Restrictions on users | Not supported | Up to ASO owner |
Third-party monetization | Not supported | Via ASO owner's fee |
Failover capability | None | Via multiple executors |
Gora provides accesible and flexible tools to create your own ASO's and deploy them to EVM-compatible blockchains of your choice: either public, like Base or Polygon, or private, which organizations can run internally. Gora does this by combining its tried and true general-purpose oracle architecture with a powerful off-chain computation engine. Rather than simply fetching data online and passing it on for verification, Gora ASO requests execute oracle programs: tiny pieces of software that implement customizations and extensions to oracle functionality. Oracle programs can be easily created by customers deploying ASO's using programming languages of their choice.
Gora's app-specific oracles rely on two key mechanisms: an ASO smart contract and an executor oracle. ASO smart contract contains oracle program and custom configuration required by customer for their specific use case. An executor oracle is a generic and complete oracle engine that implements fundamental oracle functionality using a distributed node network and consensus verification. They work together to serve web3 application requests as follows:
Gora provides common shared executors on a number of popular public blockchain networks. ASO customers just starting out are advised to use these. When data privacy, extra computing power or control over staking tokenomics is desired, customers are welcome to setup their own executors using Gora software. ASO smart contract can switch executors at any time.
Gora application-specific oracles are normally created and updated using Gora's web-based ASO control panel. It provides a user-friendly way to handle all aspects of ASO's, including compiling oracle programs and testing them.
To start using Gora ASO control panel, go to https://aso.gora.io/ and connect your Web3 wallet by clicking "Connect Wallet". If you already created ASO's using the account selected in your wallet, you will be able to choose one from the drop-down list. You will also see a "Create new" button clicking which will make a new ASO for you.
Once you create a new ASO by clicking "Create new" button or select an existing one in the dropdown list, you will be presented with the ASO control panel. It contains properties of the currenty selected ASO for you to edit.
Control panel entry fields and their meanings are as follows:
0.0012
.gzip
compression.An ASO oracle program is a compact piece of software that queries online data sources and produces an oracle value. Any ASO must have an oracle program to function, and usually it is written specifically for this ASO. While Gora ASO programs can be written in any language that compiles to Web Assembly, the ASO control panel and documentation examples use C language. It is simple, ubiquitous and can create very compact executables suitable for storage on the blockchain. To learn about oracle program API and control flow, please see Gora off-chain computation API.
No software installation is required to work with oracle programs. They can be written, compiled, tested and deployed inside ASO web control panel. To get started, click "Insert example" button in the control panel for a newly created ASO. The field (which must be empty) will be filled with a basic C program that returns string "Hello Gora!"
as the oracle value. Clicking "Compile" button will compile this program and populate the compiled binary field.
ASO contol panel allows to test oracle programs before they are deployed to the blockchain. Pressing "Test locally" button will trigger compilation (when source code is present) and execution of the current oracle program. Click it to run the test and check out the result placed in the "Log messages" box. For programs that take arguments, they can be provided "Program arguments (JSON)" field as a JSON-formatted array. In a production environment, these arguments would come from args
parameter of the request()
method call to ASO smart contract.
Gora app-specific oracles work using a simple callback pattern. To make an oracle request, customer smart contract calls ASO smart contract's request
method. If parameters need to be passed to the oracle program, they are supplied as the method argument (array of byte strings). Unique request ID is returned by ASO for future reference. On successful request completion, customer smart contract gets a response call to its special __goraAsoResponse
method from the same ASO smart contract. The call has two arguments: request ID to match the response to the initiated request, and the actual value returned by the oracle.
To get a feel of it, consider the following contrived Solidity fragment that might occur in a smart contract tracking Bitcoin price and DowJones Industrial Average index:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// ASO smart contracts to query, addresses will be known and chain-specific. GoraAso rateAso(0xaaaaaaaaaaaaaaaaaaaaa); GoraAso dowJonesAso(0xbbbbbbbbbbbbbbbbbbbbb); // Local storage to track requests in flight. enum RequestType { None, BitcoinPrice, DowJones }; mapping(bytes32 => RequestType) requests; // Values to keep up to date. Byte strings for simplicity, but in // real-world apps these are usually unpacked into more suitable formats. bytes bitcoinPrice; bytes dowJones; // Request a Bitcoin price update. function requestBitcoinPrice() external { bytes[] memory reqParams = new bytes[](2); reqParams[0] = bytes("btc"); reqParams[1] = bytes("usd"); bytes32 reqId = rateAso.request(reqParams); requests[reqId] = RequestType.BitcoinPrice; } // Request a Dow Jones index update. function requestDowJones() external { bytes32 reqId = dowJonesAso.request(new bytes[]()); requests[reqId] = RequestType.DowJones; } // Handle oracle responses. function __goraAsoResponse(bytes32 reqId, bytes calldata value) external { if (requests[reqId] == RequestType.BitcoinPrice) bitcoinPrice = value; else if (requests[reqId] == RequestType.DowJones) dowJones = value; else revert("Response to an unknown request"); delete requests[reqId]; } |
For complete working examples demonstrating uses of Gora ASO, please see the examples repository.
Every ASO relies on an executor oracle (executor) for basic lower-level blockchain oracle operations. Separating ASO's and executors allows for more flexibility, failover capabilities and a seamless customer upgrade path from shared to private infrastructure. Gora recommends new ASO customers to start with a shared executor.
Features | Shared executor | Custom executor |
---|---|---|
Managed by | Gora | ASO owner |
Requires setup and configuration | No | Yes |
Private data sources | Not supported | Configurable |
Node software customization | Not supported | Possible |
Node hardware capabilities | Limited | Up to ASO owner |
Payment options | GORA token | Any ERC20 token |
Shared executors rely on distributed networks of nodes run by general public. This may not be suitable for certain use cases. For example, when private data (such as keys) is used for querying data sources, or when oracle programs use exceptionally large amounts of computing resources.
For these kinds of situations, Gora provides a way for customers to deploy their own executors. Once customer deploys an executor smart contract, they can bring up a separate node network under their own management. Standard Gora software which can work with private authentication keys can be used to run it, or Gora can customize its node software for customer's specific needs.
At this time, creating custom executors is a semi-manual process, with a completely automated tool being on the roadmap. If you would like to explore this option, please contact Gora.
Classic oracle is the original Gora product designed to query any type of data source. On EVM-compatible networks, smart contracts are almost always written in Solidity, so this is the language Gora uses. For a quick hands-on introduction, see Developer Quick Start (EVM). For a more complete overview as well as an API reference, read on.
Gora functionality is accessed by calling methods of Gora main smart contract. To get started, you need Gora main contract address for the blockchain network that you are going to use. The preferred way to find it is to check the home page of Gora Explorer for the network in question. For example, Gora Explorer for Base mainnet. Gora main contract address is shown next to "Gora App" label.
With Gora main contract address, you can create a Gora API Solidity object in your smart contract and start making Gora calls. For example, read total amount of tokens currently staked in this Gora network:
1 2 3 |
address constant goraMainAddr = address(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa); Gora gora = Gora(goraMainAddr); uint totalStake = gora.totalStake(); |
Oracle data is requested from Gora by calling request method of Gora main smart contract. In its simplest form, it takes just two positional arguments:
Argument # | ABI Type | Description |
---|---|---|
0 | string | Data source specification |
1 | bytes | Data source parameter |
For example:
1
|
bytes32 reqId = gora.request("http://example.com/mydata", bytes("substr:0,2")) |
More precisely, Gora request method arguments have the following meanings:
gora://
) this parameter contains a value extraction specification. It describes how oracle-returned value is to be extracted from data provided by the source. For example, with a JSON endpoint that returns { "score": 123
}
one would specify: jsonpath:$.score
. Gora supports a number of value extraction options which will be explained in detail below. Special Gora sources will be described separately.Return value of the request method is a unique identifier for the created request. It is necessary to map returned oracle values to requests when making multiple oracle calls, to manipulate created requests or to access their properties.
After your Gora request is created and committed to public blockchain, it should be picked up and processed by Gora nodes in short order. Data extracted by nodes according to your specifications will be put through consensus by Gora smart contracts. On successful verification, Gora main smart contract will call the method you specified in your request and provide the resulting value. Your data-receiving method must only accept two arguments:
Argument # | ABI Type | Description |
---|---|---|
0 | bytes32 | Request ID |
1 | bytes | Oracle value |
Namely:
For use cases that require more flexibility, Gora supports oracle requests that execute user-supplied Web Assembly to produce an oracle value. This enables querying data sources determined at runtime as well as processing queried data in arbitrary ways. User-supplied code is executed off-chain by Gora nodes and is subject to resource limits.
To make use of this feature, developers write their off-chain programs utilizing Gora off-chain API. They may use any language that compiles to Web Assembly. We recommend C language due to its simplicity and ubiquity, and Clang compiler because it generates Web Assembly binaries directly. E.g.:
$ clang example.c -Os --target=wasm32-unknown-unknown-wasm -c -o example.wasm
Compiled binary is then encoded as Base64Url (URL-safe variant of Base64) and included with the request to a special URL defined by Gora to handle off-chain computation requests. In simpler form, where web assembly executable binary is provided in smart contract source code, this URL has the following format: gora://offchain/v<API version>/basic?body=<Base64Url-encoded WASM binary>[optional positional arguments]
.
The executable body can also be supplied in binary form as the data source parameter which is often convenient with larger executables or automated builds. In that case, the body
data source URL parameter is omitted. Current Gora offchain API version is 0
. So, for example, to execute a program with two positional arguments ("red"
and "apple"
) one would specify the following URL: gora://offchain/v0/basic?arg=red&arg=apple&body=AGFzbQEAAAABhoCAg...
To convert binaries into Base64URL encoding, basenc
command-line utility, normally included with Linux and MacOs, can be used:
$ basenc --base64url example.wasm AGFzbQEAAAABhoCAgAABYAF/AX8CuoCAgAACA2Vudg9fX2xpbmVhcl9tZW1vcnkCAAEDZW52GV9f aW5kaXJlY3RfZnVuY3Rpb25fdGFibGUBcAAAA4KAgIAAAQAHjICAgAABCGdvcmFNYWluAAAMgYCA gAABCpGAgIAAAQ8AIABBgICAgAA2AghBAAsLk4CAgAABAEEACw1IZWxsbyB3b3JsZCEAAMKAgIAA B2xpbmtpbmcCCJuAgIAAAgCkAQAJZ29yYV9tYWluAQIGLkwuc3RyAAANBZKAgIAAAQ4ucm9kYXRh Li5MLnN0cgABAJGAgIAACnJlbG9jLkNPREUFAQQGAQAApoCAgAAJcHJvZHVjZXJzAQxwcm9jZXNz ZWQtYnkBBWNsYW5nBjE2LjAuNgCsgICAAA90YXJnZXRfZmVhdHVyZXMCKw9tdXRhYmxlLWdsb2Jh bHMrCHNpZ24tZXh0 $
Gzip compression can be applied before encoding to reduce blockchain storage use:
gzip < example.wasm | basenc --base64url
Gora will automatically recognize and decompress gzipped Web Assembly binaries.
Oracle programs interact with the host node via Gora off-Chain API. It is essentially a customized Web Assembly environment that provides functionality to query data sources, fetch results, write log messages and more. A key part of this API is support for repeated program execution in the context of the same oracle request. This is necessary because Web Assembly programs cannot efficiently pause while waiting for asynchronous operations, such as receiving data from online sources.
Gora off-chain API is made available to C programs by including gora_off_chain.h
header file. When compiling via ASO control panel, it is made available for inclusion automatically. It defines the following custom functions:
void gora_request_url(const char* url, const char* value_specs)
value_specs
argument contains one or more value extraction specifications, separated by tab characters.void gora_set_next_url_param(const char* value)
gora_request_url()
. For example, after calling gora_request_url("https://example.com/?a=##&b=##")
, one can call gora_set_next_url_param("one")
, then gora_set_next_url_param("two")
which would result in URL "https://example.com/?a=one&b=two"
being requested. This allows having predefined templates for data source URLs and filling them at runtime.void gora_log(const char* message, const int level)
In addition to functions, Gora off-Chain API defines a context data structure. It is designed for passing data from host node to oracle program as well as preserving current state between execution stages (more on that later). An instance of this structure is passed to oracle program whenever it executes. It contains:
Complete definition of the context structure is contained in gora_off_chain.h
header file which all oracle program developers are advised to peruse.
Execution of oracle programs in stages is necessary because, like most low-level system languages, Web Assembly does not support asynchronous calls. When a Web Assembly program needs to retrieve data from a source that cannot return it instantly (e.g. a network endpoint), it has to either constantly check for data arrival in a loop (very inefficient) or rely on runtime environment to call it when the data is ready. Gora off-chain API implements a variant of the second approach.
Gora host node executes the program repeatedly, performing asynchronous operations between executions which are called stages. A stage starts when program's main function is called by the host node and ends when this function returns. During a stage, the program can schedule HTTP(S) requests, possibly using URL templates that it can fill at run time. When a stage ends, these requests are executed by the host node. On their completion, next stage commences.
Request results are made available to the program via the context structure. The context contains current stage number, so program always knows which stage it is at. It also has persistent memory space to share data between stages. Finishing a stage, the program's main function returns a value telling the host node what to do next: execute the next stage, finish successfully or terminate with a specific error code. For a hands-on primer of using staged execution, please see example programs.
Developer Quick Start (EVM) is a package of code examples and scripts to help developers start using Gora from their EVM blockchain applications. It contains instructions for Gora local development environment, example applications also usable as templates and Solidity compiler and EVM node Linux binaries.
The following extensively commented examples are provided as hands-on documentation and potential templates for your own applications:
- example_basic.sol - makes the simplest possible type of query, fetching a predefined value from a Gora built-in test data source.
- example_off_chain.sol - demonstrates Gora off-chain computation capability by making two runtime-defined JSON API requests to fetch air temperature at a British postcode.
Following the steps below will set you up with a complete environment for compiling and deploying Gora smart contract examples.
Open a terminal session and execute: uname. If this prints out Linux, continue to the next step. If the output is anything else, you may proceed at your own risk, but with a non-Unix OS you will almost certainly fail.
Install Git if not already done so, then run:
git clone https://github.com/GoraNetwork/developer-quick-start
You should get an output like:
Cloning into 'developer-quick-start'... remote: Enumerating objects: 790, done. remote: Counting objects: 100% (232/232), done. remote: Compressing objects: 100% (145/145), done. remote: Total 790 (delta 156), reused 159 (delta 85), pack-reused 558 (from 1) Receiving objects: 100% (790/790), 67.78 MiB | 1.43 MiB/s, done. Resolving deltas: 100% (469/469), done.
cd developer-quick-start/evm npm i
You should then see something like this:
added 9 packages, and audited 10 packages in 3s 3 packages are looking for funding run npm fund for details found 0 vulnerabilities
If no errors popped up, proceed to the next step.
./start_dev_env
. The script will start up, displaying log output from local EVM nodes as well as local Gora node. It must be running while you deploy and run the example scripts. To terminate the script, ending your development session, hit, Ctrl-C
.Public network configuration is set via environment variables. For example, to use Base Sepolia you would execute:
export GORA_EXAMPLE_EVM_MAIN_ADDR=0xcb201275cb25a589f3877912815d5f17f66d4f13 export GORA_EXAMPLE_EVM_API_URL=https://sepolia.base.org export GORA_EXAMPLE_EVM_KEY=./my_base_sepolia_private_hex_key.txt
./my_base_sepolia_private_hex_key.txt
is the example path to a text file containing private key for the account you want to use for deployment, in hex form. It can usually be found in account tools section of wallet software such as Metamask. The environment variables will be picked up by the example-running script discussed below. It should be possible to deploy example scripts to any public EVM network using this method. Deploying to a mainnets is, however, strongly discouraged for security reasons.
For local development environment (option A in step 4 above), open another terminal window and change to the same directory in which you started the setup script. For public network configurtion (option B in step 4), please remain in the same terminal session.
Then execute:
./run_example basic
or
./run_example off_chain
This should compile, deploy and run the example, providing detailed information on the outcome. For further details, consider Solidity examples. You are welcome to modify the examples source code and try it repeating the step above.
Gora EVM local development environment relies on the following pieces of software:
- Solidity compiler (
solc
binary). Used to compile examples and potentially developer's own code.- Geth EVM node software (
geth
binary). Provides local blockchain functionality to model master (L1) and slave (L2) EVM networks. Both instances of Geth are run in development mode (with--dev
switch).- Gora smart contracts (files with
.compiled
extension), compiled into combined JSON format.
start_dev_env
script starts Geth instance, deploys Gora smart contracts and stays in the foreground, displaying log messages from the above as they come. Contrary to Gora Developer Quick Start package for Algorand, it must be running at all times to run Gora smart contracts locally. There is no way to start a Gora node or its local blockchain on-demand on per-example basis. To end your development session and terminate the script, hit Ctrl-C in the terminal window running it.
Customer applications interact with Gora via smart contract calls. For a quick hands-on introduction to using Gora from your smart contracts, please see Gora Developer Quick Start GitHub repository. For a complete reference and instructions on calling Gora from JavaScript, read on.
Gora requests are made by calling request
method of the main Gora smart contract. This contract's application ID is a part of Gora network configuration and can be found using info
command of the Gora CLI tool. For example (with irrelevant output removed):
$ ./gora info ... Main app ID: 439550742 ... $
request
method accepts the following arguments:
Name | ABI Type | Description |
---|---|---|
request_args |
byte[] |
Encoded request specification |
destination |
byte[] |
Encoded destination call specificaiton |
type |
uint64 |
Request type ID |
key |
byte[] |
Unique request key |
app_refs |
uint64[] |
App references to pass through to destination |
asset_refs |
uint64[] |
Asset references to pass through to destination |
account_refs |
address[] |
Account references to pass through to destination |
box_refs |
(byte[],uint64)[] |
Box references to pass through to destination |
A request specification is an instance of a structured Algorand ABI type. The exact ABI type depends on the type of oracle request being specified, but it is always supplied to the request method encoded as a byte string. This allows Gora to add new request types without changing its smart contracts.
Currently Gora supports three request types:
- Type #1 - classic requests, querying sources predefined by Gora
- Type #2 - general URL requests, for arbitrary URLs and advanced data extraction methods
- Type #3 - off-chain computation requests
At the highest level of Algorand ABI type definition, all of these types have the following structure:
tuple(source_spec[], aggregation, user_data)
.
Where:
source_spec[]
- array of source specifications. A single oracle request can query multiple sources.aggregation: uint32
- how results from the sources are aggregated (0
- no aggregation,1
- maximum,2
- minimum,3
- average).user_data: byte[]
- any user-supplied data to attach to request and its response
A a source specification is a structured Algorand ABI type instance that describes an oracle source query. Its exact ABI type depends on the request type and is described below.
This is the original Gora request type which relies on oracle source definitions bundled with GNR. Source specifications for requests of this type contain the following fields:
Name | ABI Type | Description |
---|---|---|
source_id |
uint32 |
numeric id of an oracle source |
source_args |
byte[][] |
positional arguments to the source |
max_age |
uint32 |
maximum age of source data in seconds to be considered valid |
Available sources and arguments applicable to them can be examined by running: gora dev-sources
. The list of classic sources is defined by Gora is subject to extension in future releases.
Parametrized sources
To add flexibility to classic requests, certain source properties can be specified on per-query basis.
For example, if a source provides many pieces of data from the same endpoint, it is more convenient to let the requester specify the ones they want than to define a separate source for each. This is achieved by parametrizing the value_path
property. Setting it to ##0
in the oracle source definition will make Gora nodes take its value from 0'th argument of the request being served. Parameter placeholders can just as well be placed inside strings where they will be substituted, e.g. http://example.com/##2&a=123
.
The following oracle source definition properties can be parametrized: url
, value_path
, timestamp_path
, value_type
, value
, round_to
, gateway
. Substituted values are always treated as strings. For example, when supplying a parameter to set round_to
field to 5
, the string "5"
must be used rather than numeric value of 5
.
This type of oracle request does not depend on a pre-configured list of oracle sources and allows authentication via third party without compromising decentralization. Source specifications for requests of this type contain the following fields:
Name | ABI Type | Description |
---|---|---|
url |
byte[] |
source URL to query |
auth_Url |
byte[] |
authenticator URL |
value_expr |
byte[] |
expression to extract value from response |
timestamp_expr |
byte[] |
expression to extract timestamp from response |
max_age |
uint32 |
maximum age of data in seconds to be considered valid |
value_type |
uint8 |
return value type: 0 for string, 1 for number |
round_to |
uint8 |
number of digits to round result to (0 for no rounding) |
gateway_url |
byte[] |
gateway url (not for general use) |
reserved_0 |
byte[] |
reserved for future use |
reserved_1 |
byte[] |
reserved for future use |
reserved_2 |
uint32 |
reserved for future use |
reserved_3 |
uint32 |
reserved for future use |
Third-party authentication
General URL requests support using third party services to access sources that require authentication. For example, a price data feeds provider may protect their paid endpoints by requiring an access key (password) in URLs. Since everything stored by the blockchain is public, authentication keys cannot be held by smart contracts or included in oracle requests. Node operators may configure their own access keys for some sources, but not in the general case. Third-party authentication services that issue one-time authentication keys on per-request basis are designed to fill that gap. When auth_url field in the source specification is filled, Node Runner software will call this URL and receive a temporary auth key. The authenticator service will check that the node runner and the oracle request are both eligible to receive it.
For use cases that require even more flexibility, Gora supports oracle requests that execute user-supplied Web Assembly code. The code is executed off-chain by Gora network nodes and is subject to resource limits.
To make use of this feature the developer must write their program using Gora Off-Chain API in any language that compiles to Web Assembly. Compiled binary is then made available to Gora network nodes in one of the three ways: verbatim as a request parameter (for small programs), in on-chain box storage or as a download at a public URL. Request specification ABI type for this kind of request has the following structure:
Name | ABI Type | Description |
---|---|---|
api_version |
uint32 |
minimum off-chain API version required |
spec_type |
uint8 |
how executable is specified (see below) |
exec_spec |
bytes[] |
executable specification |
exec_args |
bytes[][] |
positional arguments to the executable |
reserved_0 |
bytes[] |
reserved for future use |
reserved_1 |
bytes[] |
reserved for future use |
reserved_2 |
uint32 |
reserved for future use |
reserved_3 |
uint32 |
reserved for future use |
spec_type
value determines what is contained in exec_spec
as follows:
0
- executable body itself1
- 8-byte app ID followed by box name for reading from on-chain box storage2
- URL to fetch the executable from
To get a grasp of Gora Off-Chain API and execution model, start with this example program: example_off_chain_basic.c. It returns the phrase "Hello world!" as an oracle value and is self-explanatory. To compile it, install Clang C compiler version 12 or newer and run:
clang example_off_chain_basic.c -Os --target=wasm32-unknown-unknown-wasm -c -o example_off_chain_basic.wasm
For a more advanced example, featuring URL requests and asynchronous operations, see: example_off_chain_multi_step.c.
This program does useful work and is extensively commented. It takes a British postcode as a parameter, queries two data sources, building their URLs dynamically, and returns current air temperature in the area of said postcode. This requires two data-retrival operations: getting postcode geographical coordinates and querying current weather at them.
Because of certain limitations of Web Assembly, programs cannot efficiently pause while waiting to receive data from extrnal sources such as URLs. To work around that, Gora off-chain programs are run in steps. Steps are essentially repeated executions of the program with a shared context that includes current execution number. A step starts when the program's main function is called by the executing node and ends when it returns.
During a step, the program can schedule HTTP(S) requests, possibly using URL templates that it can fill at run time. When the step ends, these requests are executed by the Gora node and on their completion, the next step commences. The program can access request results as well as other node-provided data such as the number of step currently executing via data structure passed to it as an argument.
Finishing a step, the program always returns a value which tells the Gora node what to do next: execute another step, finish successfully or terminate with a specific error code. For the list of valid return values, see gora_off_chain.h header file.
To compile this example program, run:
clang example_off_chain_multi_step.c -Os --target=wasm32-unknown-unknown-wasm -c -o example_off_chain_multi_step.wasm
To execute the compiled binary using Gora CLI and default test destination app, run:
gora request --off-chain ./off_chain_example.wasm --args sm14hp
This feature allows requests of type 1 and 2 to fetch multiple pieces of data from the same source response. Normally, value_path
property contains a single expression, so just one value is returned by an oracle request. To return multiple values, it is possible to specify multiple expressions separated by tab character. For example: $.date\t$.time\t$.details.name
. Since an oracle return value must be a single byte string for the consensus to work, returned pieces of data are packed into Algorand ABI type - an array of strings:
1
|
const multiResponse = new Algosdk.ABIArrayDynamicType(Algosdk.ABIType.from("byte[]")); |
To access individual results, smart contract handling the oracle response must unpack this ABI type. Nth string in the array will correspond to the nth expression in the valuePath
field.
Certain kinds of data, such as cryptocurrency exchange rates, are so volatile that different Gora nodes are likely to get slightly different results despite querying them at almost the same time. To achieve consensus between nodes when using such sources, Gora can mathematically round queried values.
A source that supports rounding will have "Round to digits" field when shown with gora dev-sources
command. Usually, the rounding setting will be parametrized, for example: "Round to digits: ##3". This means that the number of significant digits to round to is supplied in parameter with index 3. The number must be provided in string representation, like all parameters. Rounding will only affect the fractional part of the rounded number, all integer digits are always preserved. For example, if rounding parameter is set to "7", the number 123890.7251
will be rounded to 123890.7, but the number 98765430
will remain unaffected.
While Gora's main purpose is to interact with smart contracts, it is sometimes desirable to access its functionality from normal Linux software. Examples below will be given in JavaScript, but they can be adapted to any language supported by the Algorand API, such as Python or Go. We start by building the request spec ABI type to encode our request. It can be accomplished in a single call, but will be done in steps here for clarity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
const Algosdk = require("algosdk"); const basicTypes = { sourceArgList: new Algosdk.ABIArrayDynamicType(Algosdk.ABIType.from("byte[]")), sourceId: Algosdk.ABIType.from("uint32"), maxAge: Algosdk.ABIType.from("uint32"), userData: Algosdk.ABIType.from("byte[]"), aggregation: Algosdk.ABIType.from("uint32"), }; const sourceSpecType = new Algosdk.ABITupleType([ basicTypes.sourceId, basicTypes.sourceArgList, basicTypes.maxAge ]); const requestSpecType = new Algosdk.ABITupleType([ new Algosdk.ABIArrayDynamicType(sourceSpecType), basicTypes.aggregation, basicTypes.userData ]); |
Now we will use requestSpecType
ABI type that we just created to encode a hypothetical Oracle request. We will query two sources for USD/EUR price pair and receive their average value. The data must be no more than an hour old in both cases. The sources are predefined in Gora with IDs 2 and 5, but one specifies currencies mnemonically while the other does it numerically:
1 2 3 4 5 6 7 8 |
const requestSpec = requestSpecType.encode([ [ [ 2, [ Buffer.from("usd"), Buffer.from("eur") ], 3600 ], [ 5, [ Buffer.from([ 12 ]), Buffer.from([ 44 ]) ], 3600 ], ], 3, // average it Buffer.from("test") // let the receiving smart contract know it's a test ]); |
The requestSpec
variable can now be used for spec
argument when calling the request
method for Gora main smart contract.
Results of an oracle request are returned by calling dest_method
method of the smart contract specified in dest_id
. The method gets passed the following two arguments:
type: uint32
- response type; currently is always1
.body: byte[]
- encoded body of the response (details below).
The body
argument contains an ABI-encoded tuple of the following structure:
byte[]
- request ID. Currently the same as Algorand transaction ID of therequest
smart contract call that initiated the request.address
- address of the account making the requestbyte[]
- oracle return value, more details belowbyte[]
- data specified inuserData
field of the requestuint32
- result error code, see belowuint64
- bit field with bits corresponding to the request sources; if n'th bit is set, the n'th source has failed to yield a valid value.
Result error codes
0
- normal result.1
- result was truncated because it was over the allowed size. Result size limit is configured in Node Runner software and depends on maximum smart contract arguments size supported by Algorand.
Unless the numeric type has been explicitly specified for the return value, it will be encoded as a string. If value expression is a JSON path that matches an object, it will stringified, e.g. '{ "date": "01-01-2020", "price": 123 }'
.
Numeric oracle return values
When returned oracle value is a number, it is encoded into a 17-byte array. 0
's byte encodes value type:
0
- empty value (not-a-number, NaN)1
- positive number2
- negative number
Bytes 1 - 8
contain the integer part, 9 - 17
- the decimal fraction part, as big endian uint64's. For example, 0x021000000000000000ff00000000000000
in memory order (first byte has 0 offset) decodes as -16.255
Troubleshooting Gora applications begins with making oracle requests and looking at how they are handled in each processing phase. For that, we recommend using Gora CLI tool, a Gora observer node and Algorand Dapp Flow web app. The rest of this section will walk you through setting them up and using them to trace execution of a Gora request.
Gora observer node is a node set up and running on a Gora network for the purpose of monitoring requests. An observer node is not required to run continuously or have any GORA tokens staked. When using Developer Quick Start, setting up an observer node is not necessary because it includes a full Gora node. For troubleshooting applications on Algorand testnet or mainnet, if you are not already running a normal Gora node on the same network, set on up following the Getting Started section above.
Now you can find out Algorand address of the application from which you are making Gora requests. This can be done with Algorand Dapp Flow Explorer: enter your application ID into the search box and press Enter which should take you to application transactions page. The address should be displayed under "Application account" label.
Make sure you have set up your observer node as its configuration is used by Gora CLI tool. Now run the tool to find out Gora main smart contract ID:
$ gora info
You should get output containing a string like:
Main app ID: 439550742
Now you can use Dapp Flow to check that oracle request calls are being made from your application to correct Gora smart contract. Try running your app, then search on Dapp Flow for transactions to Gora main app ID. There must be an application call transaction from your app address just made.
Once your Gora request call gets stored on the blockchain, it is up for detection and processing by Gora nodes. That including your observer node, which you will now utilize to monitor processing of your requests. If you are not using Developer Quick Start, you will need to enable debug output on your node. Open your node config file (~/.gora
by default) and under "deployment"
section add the following lines:
"logLevel": 5
Make sure to add a comma to the previous line if there is one or you will get a config syntax error when trying to start the node. Restart the node if it is already running.
If your observer node hasn't been running, start it now and keep an eye on its log messages: either by running it in the foreground or by tailing logs with docker logs -f <node container name>
.
Now when your Gora blockchain app makes another request, you should see your node pick up the request and log detailed messages on various phases of its processing. For example, with a General URL request:
2023-12-10T20:46:54.432Z DEBUG Handling call "main#1003.request" from "Z7PANAMW2I7MEHTTT24U2G5UJXUSIO6QORYCJV6YVZZQNBVQ2Z22C4P5XI", round "81754" 2023-12-10T20:46:54.441Z INFO Processing oracle request "JHPCPIL4BP2GN5F7PQRAJEC6MBRHYMVALUZMMDZL7AWXGNZZATWA", destination: "1516.handle_oracle_url" 2023-12-10T20:46:54.441Z DEBUG Querying URL source: "https://coinmarketcap.com/currencies/bnb/, "regex:>BNB is (?:up|down) ([.0-9]+)% in the last 24 hours, "", "" 2023-12-10T20:46:54.507Z DEBUG Fetching "https://coinmarketcap.com/currencies/bnb/", time limit (ms): 5000, size limit (bytes): 1048576 2023-12-10T20:46:54.548Z DEBUG Querying URL source: "https://coinmarketcap.com/currencies/solana/, "regex:>Solana is (?:up|down) ([.0-9]+)% in the last 24 hours, "", "" 2023-12-10T20:46:54.627Z DEBUG Fetching "https://coinmarketcap.com/currencies/solana/", time limit (ms): 5000, size limit (bytes): 1048576 2023-12-10T20:46:54.865Z DEBUG Fetched "https://coinmarketcap.com/currencies/solana/", "315317" bytes, starting with: "<!DOCTYPE html><html"... 2023-12-10T20:46:54.886Z DEBUG Result #1, source "https://coinmarketcap.com/currencies/solana/": "6.41", for "JHPCPIL4BP2GN5F7PQRAJEC6MBRHYMVALUZMMDZL7AWXGNZZATWA" 2023-12-10T20:46:55.342Z DEBUG Decoding gzip 2023-12-10T20:46:55.360Z DEBUG Fetched "https://coinmarketcap.com/currencies/bnb/", "335244" bytes, starting with: "<!DOCTYPE html><html"... 2023-12-10T20:46:55.363Z DEBUG Result #0, source "https://coinmarketcap.com/currencies/bnb/": "0.53", for "JHPCPIL4BP2GN5F7PQRAJEC6MBRHYMVALUZMMDZL7AWXGNZZATWA" 2023-12-10T20:46:55.364Z DEBUG Result for "JHPCPIL4BP2GN5F7PQRAJEC6MBRHYMVALUZMMDZL7AWXGNZZATWA": 6.41 (number, aggregation: "2") 2023-12-10T20:46:55.377Z DEBUG Using seed: "0x1ea6cbe0dac0d99beb3903648fc155327c93c870c08106a9b66a7b271e7345d3" 2023-12-10T20:46:55.383Z DEBUG Alloted "1000004424" vote(s) for "JHPCPIL4BP2GN5F7PQRAJEC6MBRHYMVALUZMMDZL7AWXGNZZATWA", zIndex: "1" 2023-12-10T20:46:55.403Z DEBUG Creating verify txn to vote on "JHPCPIL4BP2GN5F7PQRAJEC6MBRHYMVALUZMMDZL7AWXGNZZATWA": { suggestedParams: { flatFee: true, fee: 0, firstRound: 81755, lastRound: 81764, genesisID: 'sandnet-v1', genesisHash: 'RXrzSgzbMh2FXnMJPwqL2UGeyIdbiks2G1oUvDS7fA8=', minFee: 1000 }, from: 'GBS6GNRJIOD3SFHQGCXT7QBUF2V6G7HHG7J3M3XYSAF57FIN4RN53DTRTU', appIndex: 1003, appArgs: [ '0x23fd2961', '0x8944db7ce5abc02130dcc5bb96ee1c8a7c3a1ee8022b0bfb81b28581764b4695f60dfcaf9ffe2193f538c0df2d43e7b4a9f85a0f4cc12e4dd5d2df8bb0d1f034', '0xd50e00ddaa15a2f5181e46c3910100df4c5808230eef87df14d56ea5a7d40b4a468c5c656f3ec347a5344dc267df2aab6fdc92d711649fe692804c1614b98e47112b67866010c6ac1de6bcf26a51f609', '0x1ea6cbe0dac0d99beb3903648fc155327c93c870c08106a9b66a7b271e7345d3', '0x0000000000000001', '0x0000000000000002', '0x0000000000000003', '0x0000000000000001', '0xb3cf668b6f5b53016300c0f95dbd981ef336588d3753ae4bf77b29132afefb78', '0x55e47eeb0b4579748653a796eace4ac2b87a836e30375e2b1a1bdcc81dce86bf978a7fa15bc7d7446919fe923abdb361de0bdf61252fd8db49e805e0f17ec563000000003b9ab8b8000000e8d4a51000000000000000000a0000000000000000d9fd2c74d7ff4f2eaf66d681a0f53f9368213eac7b75719ad7aa2e96461d2a5a80', '0x0000000000000004' ], accounts: [ 'YHZYUAYUIYNXFMLK5WZ7PYGHVQUIEYULHAKGF5MCYSG76OYP2TYT2WQZRM', '3ACWF4HKPTGU555RKFF6KETS56EOEBO4OSL4BTS46XDHHIHPTNOBY4TRSU', 'TRWQJHM24P64L2XY35IFCQ4DXGMBBVKB5VP6IVDRSQYN22R2VTBHTR7JB4', '3H6SY5GX75HS5L3G22A2B5J7SNUCCPVMPN2XDGWXVIXJMRQ5FJNAF6XE4Y' ], foreignApps: [ 1009 ], boxes: [ { appIndex: 1003, name: '0xb3cf668b6f5b53016300c0f95dbd981ef336588d3753ae4bf77b29132afefb78' }, { appIndex: 1003, name: '0x55e47eeb0b4579748653a796eace4ac2b87a836e30375e2b1a1bdcc81dce86bf' }, { appIndex: 1009, name: '0x978a7fa15bc7d7446919fe923abdb361de0bdf61252fd8db49e805e0f17ec563' } ], onComplete: 0 } 2023-12-10T20:46:55.407Z DEBUG Blockchain-voting on "JHPCPIL4BP2GN5F7PQRAJEC6MBRHYMVALUZMMDZL7AWXGNZZATWA", seed: "0x1ea6cbe0dac0d99beb3903648fc155327c93c870c08106a9b66a7b271e7345d3" (real), VRF proof: "0xd50e00ddaa15a2f5181e46c3910100df4c5808230eef87df14d56ea5a7d40b4a468c5c656f3ec347a5344dc267df2aab6fdc92d711649fe692804c1614b98e47112b67866010c6ac1de6bcf26a51f609", VRF result: "0x8944db7ce5abc02130dcc5bb96ee1c8a7c3a1ee8022b0bfb81b28581764b4695f60dfcaf9ffe2193f538c0df2d43e7b4a9f85a0f4cc12e4dd5d2df8bb0d1f034", request round: "81754", round window: "81755" - "81764" 2023-12-10T20:46:55.418Z DEBUG Calling "voting#1009.vote" by "YHZYUAYUIYNXFMLK5WZ7PYGHVQUIEYULHAKGF5MCYSG76OYP2TYT2WQZRM", id: "68b5c889528b142a", args: { suggestedParams: { flatFee: true, fee: 2000, firstRound: 81755, lastRound: 81764, genesisID: 'sandnet-v1', genesisHash: 'RXrzSgzbMh2FXnMJPwqL2UGeyIdbiks2G1oUvDS7fA8=', minFee: 1000 }, method: 'vote', methodArgs: [ '0x8944db7ce5abc02130dcc5bb96ee1c8a7c3a1ee8022b0bfb81b28581764b4695f60dfcaf9ffe2193f538c0df2d43e7b4a9f85a0f4cc12e4dd5d2df8bb0d1f034', '0xd50e00ddaa15a2f5181e46c3910100df4c5808230eef87df14d56ea5a7d40b4a468c5c656f3ec347a5344dc267df2aab6fdc92d711649fe692804c1614b98e47112b67866010c6ac1de6bcf26a51f609', '0x408f580000000000', '0x4097b00000000000', '0xea1f43d7', '0xcfde068196d23ec21e739eb94d1bb44de9243bd0747024d7d8ae730686b0d675', '0x334143574634484b50544755353535524b4646364b4554533536454f45424f344f534c34425453343658444848494850544e4f42593454525355', '0x3ff0000000000000', '0x49de27a17c0bf466f4bf7c2204905e60627c32a05d32c60f2bf82d73373904eccfde068196d23ec21e739eb94d1bb44de9243bd0747024d7d8ae730686b0d67500500063000000000000000000000000001101000000000000000600000000000000290000', '0x41cdcd6da4000000', '0x3ff0000000000000', '0x00' ], note: '', appID: 1009, sender: 'YHZYUAYUIYNXFMLK5WZ7PYGHVQUIEYULHAKGF5MCYSG76OYP2TYT2WQZRM', boxes: [ { appIndex: 1009, name: '0xd80562f0ea7ccd4ef7b1514be51272ef88e205dc7497c0ce5cf5c673a0ef9b5c' }, { appIndex: 1009, name: '0xa55bf54aa9d489c3395a844d7476efd08296875951191e1b96f35a3cd69a6981' } ], appAccounts: [], appForeignApps: [], appForeignAssets: [], lease: '0x49de27a17c0bf466f4bf7c2204905e60627c32a05d32c60f2bf82d73373904ec' } 2023-12-10T20:47:01.326Z INFO Submitted 1000004424 vote(s) on request "JHPCPIL4BP2GN5F7PQRAJEC6MBRHYMVALUZMMDZL7AWXGNZZATWA"
If you see log messages with the INFO
prefix, but none with DEBUG
, then you have not enabled debug logging and need to ensure that you have followed the instructions in the beginning of this section properly. When running an observer node with no stake, it is normal not to see messages after "Using seed...".
Issues with Gora customer applications often crop up at this stage. These are most frequently caused by errors in Gora request encoding or data source specification.
In case of incorrectly encoded request, the node will fail to decode the request correctly and log an error message beginning with Error parsing request...
. Make sure you are encoding the request ABI type properly, consulting examples in Developer Quick Start if necessary.
For problems with data sources, examine log messages after Querying....
. If there are no errors reported, check debug messages carefully to make sure that data source URLs queried are correct, the content returned is valid and data extraction expressions are matching it as intended. Currently nodes have no way of explicitly reporting failures to customer smart contracts and will simply return an empty result in most scenarios.
The last phase of processing where a Gora request can fail starts when node voting concludes in consensus and a call is made to the destination smart contract. This may happen because customer's destination app is either specified incorrectly or fails during processing of Gora response.
The destination call is always initiated by just one Gora node. In multi-node Gora networks, it is not possible to reliably predict which one it will be, so one cannot rely on node logs in the this (most common) scenario. The recommended way of debugging such issues is using Developer Quick Start. It provides a local development network with a single node, making the destination call logs always available.
If your application is failing at this stage, examine the error folllowing Calling "voting#...
message in your local development node logs. An error occuring inside your destination application will be reported in typical Algorand smart contract error format. Bear in mind, that the destination call is made in an inner transaction inside Gora voting smart contract and interpret TEAL source context accordingly.
To mininize risks of making error in repsonse handling, we recommend using Gora Python library available as a PIP package.
Developer Quick Start for Algorand is a package of code examples and scripts to help developers start using Gora from their blockchain applications. It contains:
- Instructions on how to setup and use a local Gora development environment
- Example applications, also usable as templates
- Info on commands and tools for troubleshooting your Gora applications
All DQS instructions are written and tested on Linux. Mac users reported success with most of the steps described here and are welcome to follow them at their own risk. Readers must be comfortable with using command-line tools, including tools for blockchain of their choice.
There are four essential pieces to a Gora Algorand development environment:
- An Algorand node providing local simulated Algorand network
- Algorand Python libraries for smart contracts and blockchain APIs
- Deployed Gora smart contracts
- A Gora development-only node running and connected to the above
The following Algorand software must be installed and functioning:
Refer to documentation at the above links for download and installation instructions. If using a different package to setup your Algorand node, such as AlgoKit, find out its Algod API connection port number and have it handy. If it differs from 4001, you will need to enter it during setup of Gora software.
To install and configure Gora software for your development environment, run python3 setup.py and follow the prompts. Gora tools will be downloaded and config files created for you automatically in the checkout directory.
When the above script finishes, you will have Gora smart contracts deployed to local network in your Algorand Sandbox install and a Gora node set up for them. This will form a local development-only single-node Gora network necessary to serve your locally tested applications.
For local oracle requests to be served, your development Gora node must be running whenever they are made. There are two ways to ensure this. One is to run it temporarily from a script that executes your application test cycle. This is what example apps in this repository do; details can be gleaned from their source code. Another way is to run the node continuously for the duration of your development session. To start it with output to the terminal, change to the checkout directory and run: GORA_CONFIG_FILE=./.gora ./gora_cli docker-start
. To make it run in the background, add --background
switch to the above command. To see node's log messages, run docker logs gora-nr-dev
.
This repository includes several example PyTeal applications demonstrating the use of Gora oracle. They will be considered below in the order of complexity. Example apps are built with Algorand's Beaker framework and are commented to make them accessible for novice developers.
To run an example app, execute it with Python, e.g. python example_const.py
. You should get an output like:
Loading config from "./.gora" Main app ID: 1004 Using local account ETKGKDOICCD7RQRX7TX24RAAM2WTHP7L4EGIORVLJEKZO7FWNY27RUTF3E Deploying the app... Done, txn ID: 3GH2465S6GPWRGHZQPHRQ7SHU7YOLXVPQVY64IJM2PVF4MSBM57A App ID: 1280 App address: DPF45GKEB2H7P7HJNRHYNJXZTCSPWMLBIOFR5ZM6V2FJTPMNJ7C2VBQRHA Token asset ID: 1003 Initializing app for GORA... Setting up Algo deposit... Setting up token deposit... Calling the app Confirmed in round: 16598 Top txn ID: USH3IB32OH5QQHGKHQGWLTW46QCOEKWQGCJ472G6FXG2VG2LLHPA Running: "./gora docker-status" Background development Gora node not detected, running one temporarily Running: "./gora docker-start" Gora CLI tool, version N/A gora-nr-dev 2023-11-13T13:28:26.679Z DEBUG Applying GORA_CONFIG environment variable 2023-11-13T13:28:27.557Z INFO Starting Gora Node Runner 2023-11-13T13:28:27.909Z INFO Version: "1.1.30" 2023-11-13T13:28:27.909Z INFO Built on: "Sat, 11 Nov 2023 22:07:48 GMT" 2023-11-13T13:28:27.909Z INFO Revision: "59652555bf372e85185d8cad47b99d3a8eb032ea" 2023-11-13T13:28:27.909Z INFO Smart contracts revision: "1535e07cc84cdfea2ac8d0ec4bcb854c9f7d21ba" 2023-11-13T13:28:27.909Z INFO Docker image: "107782235753.dkr.ecr.eu-central-1.amazonaws.com/gora-nr:v1.1.30" 2023-11-13T13:28:27.910Z INFO Docker image hash: "705d77c0330c8a1ddd07c1c2618e0ca5cf1debd583e4fa0b49d9f4fa2398a07b" 2023-11-13T13:28:27.986Z DEBUG Blockchain server host is local, changing it to "host.docker.internal" to make it work under Docker 2023-11-13T13:28:28.151Z INFO Using Algorand API server: "http://host.docker.internal:4001/", port: "4001" 2023-11-13T13:28:28.191Z DEBUG Block seed is available 2023-11-13T13:28:28.195Z DEBUG Using network config override 2023-11-13T13:28:28.258Z INFO Main address: "I5EY62R2X5PSONSKWEEXZAUC5WZ3XQZPUQOA2RQKLFNKKKM5BPXWN7EFEQ" 2023-11-13T13:28:28.258Z INFO Participation address: "MA2XUHMW4F2HWJSXMX6GVFJSV4QDJLS4U2HDELEX75QQH2YE4LZSMVZIOE" 2023-11-13T13:28:28.259Z INFO Main smart contract: "1004" 2023-11-13T13:28:28.259Z INFO Voting smart contracts: "1010, 1014, 1018" 2023-11-13T13:28:28.259Z INFO Token asset ID: "1003" 2023-11-13T13:28:28.261Z INFO Last blockchain round: "16600" 2023-11-13T13:28:28.266Z INFO Staked amount: 1000000000000 microGORA 2023-11-13T13:28:28.266Z INFO Deposits: 70000 microALGO, 7000000000 microGORA 2023-11-13T13:28:28.324Z INFO Oracle sources set up: 31 2023-11-13T13:28:28.401Z INFO Processing round "16598" only 2023-11-13T13:28:28.419Z DEBUG Handling call "main#1004.request" from "DPF45GKEB2H7P7HJNRHYNJXZTCSPWMLBIOFR5ZM6V2FJTPMNJ7C2VBQRHA", round "16598" 2023-11-13T13:28:28.423Z INFO Processing oracle request "2L7P3TYMSNBMBGMW2RFZESVXYB4W5NFH42KG5GBTU6UY53ZBIOIQ", destination: "1280.handle_oracle_const" 2023-11-13T13:28:28.424Z DEBUG Querying source #1, args: 2023-11-13T13:28:28.424Z DEBUG Result #0, source "1": 1, for "2L7P3TYMSNBMBGMW2RFZESVXYB4W5NFH42KG5GBTU6UY53ZBIOIQ" 2023-11-13T13:28:28.425Z DEBUG Result for "2L7P3TYMSNBMBGMW2RFZESVXYB4W5NFH42KG5GBTU6UY53ZBIOIQ": 1 (number, single) 2023-11-13T13:28:28.433Z DEBUG Using seed: "0x9d2b280c6aacbff4357c2f1fc0ba0e94f46160f4a2368f763a947944878abc86" 2023-11-13T13:28:28.438Z DEBUG Alloted "999982301" vote(s) for "2L7P3TYMSNBMBGMW2RFZESVXYB4W5NFH42KG5GBTU6UY53ZBIOIQ", zIndex: "4" 2023-11-13T13:28:28.456Z DEBUG Creating verify txn to vote on "2L7P3TYMSNBMBGMW2RFZESVXYB4W5NFH42KG5GBTU6UY53ZBIOIQ": { suggestedParams: { flatFee: true, fee: 0, firstRound: 16599, lastRound: 16608, genesisID: 'sandnet-v1', genesisHash: 'RXrzSgzbMh2FXnMJPwqL2UGeyIdbiks2G1oUvDS7fA8=', minFee: 1000 }, from: 'GBS6GNRJIOD3SFHQGCXT7QBUF2V6G7HHG7J3M3XYSAF57FIN4RN53DTRTU', appIndex: 1004, appArgs: [ '0x23fd2961', '0x46a261eaa8af75c2af39cc8232d849fb77def96e264a6fb02b14e5563a2e9ac5ff3513bc613405c6461898523280e17596543f4da7461910f4cb8662b6437d87', '0xf02acf8d37b7ae2d55be012bebbaab21322aea4ec214c5ba5b1def593906b29c1949842a74e904b7b7030ab6d003e6ccebab7efa7e7fa897a09e6bdc4cfac4eb9f11f6930761335de7b57f0643dd4108', '0x9d2b280c6aacbff4357c2f1fc0ba0e94f46160f4a2368f763a947944878abc86', '0x0000000000000001', '0x0000000000000002', '0x0000000000000003', '0x0000000000000001', '0x8ca52b2d1e080d74325852bf3d76bd6a8c4b335c198cab591e67df8e27476e6a', '0x3a66f2b5dba56c2aac3705641397a3aa5c8ee4f5c3ee84877e80346a9cb48bb58d6884389847ca9e3d115a7fdac390ac42b04ed8a1cb57c532181afe549ccebe000000003b9b31b5000000e8d4a51000000000000000000800000000000000009ddd285c2891cb74b21b2bbada87c4298459c3367a477eb3be6f00e5cb8eb2ae80', '0x0000000000000004' ], accounts: [ 'MA2XUHMW4F2HWJSXMX6GVFJSV4QDJLS4U2HDELEX75QQH2YE4LZSMVZIOE', 'I5EY62R2X5PSONSKWEEXZAUC5WZ3XQZPUQOA2RQKLFNKKKM5BPXWN7EFEQ', 'VSFZF5BRBVJY7P5QQN73JQ27DX3RP6PWSHW4I3SFFFZYFNTGCM3ZC2DHLE', 'TXOSQXBISHFXJMQ3FO5NVB6EFGCFTQZWPJDX5M56N4AOLS4OWKXAPCZFSY' ], foreignApps: [ 1010 ], boxes: [ { appIndex: 1004, name: '0x8ca52b2d1e080d74325852bf3d76bd6a8c4b335c198cab591e67df8e27476e6a' }, { appIndex: 1004, name: '0x3a66f2b5dba56c2aac3705641397a3aa5c8ee4f5c3ee84877e80346a9cb48bb5' }, { appIndex: 1010, name: '0x8d6884389847ca9e3d115a7fdac390ac42b04ed8a1cb57c532181afe549ccebe' } ], onComplete: 0 } 2023-11-13T13:28:28.463Z DEBUG Blockchain-voting on "2L7P3TYMSNBMBGMW2RFZESVXYB4W5NFH42KG5GBTU6UY53ZBIOIQ", seed: "0x9d2b280c6aacbff4357c2f1fc0ba0e94f46160f4a2368f763a947944878abc86" (real), VRF proof: "0xf02acf8d37b7ae2d55be012bebbaab21322aea4ec214c5ba5b1def593906b29c1949842a74e904b7b7030ab6d003e6ccebab7efa7e7fa897a09e6bdc4cfac4eb9f11f6930761335de7b57f0643dd4108", VRF result: "0x46a261eaa8af75c2af39cc8232d849fb77def96e264a6fb02b14e5563a2e9ac5ff3513bc613405c6461898523280e17596543f4da7461910f4cb8662b6437d87", request round: "16598", round window: "16599" - "16608" 2023-11-13T13:28:28.478Z DEBUG Calling "voting#1010.vote" by "MA2XUHMW4F2HWJSXMX6GVFJSV4QDJLS4U2HDELEX75QQH2YE4LZSMVZIOE", id: "23e1ed9b96248aff", args: { suggestedParams: { flatFee: true, fee: 2000, firstRound: 16599, lastRound: 16608, genesisID: 'sandnet-v1', genesisHash: 'RXrzSgzbMh2FXnMJPwqL2UGeyIdbiks2G1oUvDS7fA8=', minFee: 1000 }, method: 'vote', methodArgs: [ '0x46a261eaa8af75c2af39cc8232d849fb77def96e264a6fb02b14e5563a2e9ac5ff3513bc613405c6461898523280e17596543f4da7461910f4cb8662b6437d87', '0xf02acf8d37b7ae2d55be012bebbaab21322aea4ec214c5ba5b1def593906b29c1949842a74e904b7b7030ab6d003e6ccebab7efa7e7fa897a09e6bdc4cfac4eb9f11f6930761335de7b57f0643dd4108', '0x408f600000000000', '0x4094000000000000', '0xbbdd1de0', '0x1bcbce99440e8ff7fce96c4f86a6f998a4fb3161438b1ee59eae8a99bd8d4fc5', '0x4935455936325232583550534f4e534b574545585a41554335575a3358515a5055514f413252514b4c464e4b4b4b4d35425058574e3745464551', '0x3ff0000000000000', '0xd2fefdcf0c9342c09996d44b924ab7c0796eb4a7e6946e9833a7a98eef2143911bcbce99440e8ff7fce96c4f86a6f998a4fb3161438b1ee59eae8a99bd8d4fc500500063000000000000000000000000001101000000000000000100000000000000000000', '0x41cdcd426e800000', '0x4010000000000000', '0x00' ], note: '', appID: 1010, sender: 'MA2XUHMW4F2HWJSXMX6GVFJSV4QDJLS4U2HDELEX75QQH2YE4LZSMVZIOE', boxes: [ { appIndex: 1010, name: '0x47498f6a3abf5f27364ab1097c8282edb3bbc32fa41c0d460a595aa5299d0bef' }, { appIndex: 1010, name: '0x6e8e497a81d11786378b1419468bf2315758b0e1b6bfc4ecd4c8837bd48580f0' } ], appAccounts: [], appForeignApps: [], appForeignAssets: [], lease: '0xd2fefdcf0c9342c09996d44b924ab7c0796eb4a7e6946e9833a7a98eef214391' } 2023-11-13T13:28:34.589Z INFO Submitted 999982301 vote(s) on request "2L7P3TYMSNBMBGMW2RFZESVXYB4W5NFH42KG5GBTU6UY53ZBIOIQ" Waiting for for oracle return value (up to 10 seconds) Received oracle value: 1.0
Note the last line: Received oracle value: 1.0
. It shows the value returned by the oracle which has been successfully processed and stored by the executed app. If your Gora development node is already running, the date-prefixed log messages above will be found in its output rather than in script's output above. Let us now look at example apps in more detail.
1
. The request is prepared without using Gora support libary, to make the process more explicit. Since no external sources are queried, this example can even be run offline.##signKey
as the second source parameter which makes Gora nodes generate a temporary signature key bound to the request and the node. The data source server being queried will check the validity of the request and the node's stake on the blockchain. This allows opening a data source to Gora users only without requiring them to provide authentication data or exposing it on the blockchain.do_nothing_N()
dummy methods and increase 4th parameter of run_demo_app()
to raise op code budget even more.example_off_chain_multi_step.c
. The WebAssembly resulting from compilation of this code is included in the example app verbatim, as a byte string constant. For more off-chain executable specification options as well as other details on the off-chain computation feature, refer to Gora off-chain API documentation.Algorand Dapp Flow web app can be used to inspect related application transactions.
To connect it to your local Algorand network, open the drop-down menu under the logo in the top left cornet and select "Sandbox". Use application or transaction IDs from the tested app output to find and inspect transactions of interest.
Gora is a decentralized blockchain oracle. To produce its values, it relies on a network of independent blockchain-connected nodes running Gora Node Runner (GNR). Gora token issuance and distribution, required to bootstrap consensus verification, are usually done once by the network owner and are beyond the scope of this discussion. For the purposes of this document, setting up a Gora node network comes down to setting up GNR for each node operator.
The GNR is distributed as a Linux-based Docker image that runs on any docker-enabled customer host or in Amazon cloud as an AWS Fargate application. GNR is managed via Gora CLI tool - a self-contained command-line executable that encapsulates all required functionality for Gora node operators or power users.
To become a Gora node operator, one should possess the following:
docker
group, then logging out and back in.wget
utility run: wget https://download.gora.io/latest-release/linux/gora -O gora
To kick off the initialization process, run ./gora init. You will be guided through the steps with prompts and messages. You can abort the process at any time by pressing Ctrl-C. During initialization, Gora CLI tool will:
~/.gora
configuration file with info on used accounts as well as access details for blockcians API nodes that your Gora node will use.To start over, rename or delete the produced config file ~/.gora
.
Before continuing, make sure that you have installed Docker and added yourself to the `docker` group on your host. To start your node, execute: ./gora docker-start
. When run for the first time, it will pull GNR docker image from Gora docker registry. As the node launches, it starts logging its progress to standard output, e.g.:
2024-10-04T11:19:57.613Z INFO Starting Gora Node Runner 2024-10-04T11:19:57.984Z INFO Version: "1.1.65" 2024-10-04T11:19:57.984Z INFO Built on: "Sat, 02 Nov 2024 02:54:17 GMT" 2024-10-04T11:19:57.984Z INFO Revision: "f9e66e7918f43326974d7ac345bec4bee734a0df" 2024-10-04T11:19:57.984Z INFO Smart contracts revision: "ac9e053d68dd6718c02ed36b76cd9ba478a390bb" 2024-10-04T11:19:57.984Z INFO Docker image: "107782235753.dkr.ecr.eu-central-1.amazonaws.com/gora-nr:v1.1.65" 2024-10-04T11:19:57.985Z INFO Docker image hash: "dd20b9d55e7081687b3cacfba9d1e93a49e924c4e8e7a582cbedc86b6285c55d" 2024-11-04T11:19:58.951Z INFO EVM network "baseMainnet" enabled, chain Id "8453", main contract "Gora" at "0xd4c99F88095F32dF993030d9a6080e3BE723F617" 2024-10-04T11:20:01.020Z INFO Waiting for next oracle request
To make the node switch into background upon startup, add --background
to the command. For a quick check of instance's status, use gora docker-status
. To inspect instance's logs, use docker log command, e.g.: docker logs -fn 100 gora-nr
.
Running your Gora node on AWS is a lower maintenance option, altough it is not as flexible or economical as running it locally. To begin, download and install AWS CLI. You should then be able start your Gora node right away by running ./gora aws-start
. During a first-time execution, several AWS configuration items will be set up for you, producing an output like:
Creating security group "gora-nr-sg" Creating log group "gora-nr-logs" Registering task definition "gora-nr-task"
Then you should see the kind of output that would appear every time you start your AWS Gora node up:
Startup initiated, task ID: "2468d56dff884c9ca536fb2e537f8928"
This means that AWS has been asked to start your node up and it should be online shortly. You can check its current status by executing ./gora aws-status
which should eventually produce an output like:
State: Running Started at: 2022-07-04T17:33:08.803Z Uptime: 2 min. Task ID: "2468d56dff884c9ca536fb2e537f8928"
This confirms your Gora node has been started by AWS. To check up on it, you can always inspect its logs via AWS web UI or by running ./gora aws-log
.
To stop a Gora node running locally in the foreground, hit Ctrl-C
. If it is running in the background, execute ./gora docker-stop
. To stop a node on AWS, run ./gora aws-stop
.
Gora CLI tool is updated with gora update
command. It checks whether there is a more recent version than the one being run, and if so, offers to upgrade it by downloading and replacing the gora
binary. Current binary will be backed up. GNR is distributed as a docker image, so it will be automatically updated whenever your Gora node is started. To ensure that you are running the latest version, simply stop and start your node again.
Your can move your Gora installation to a new server without setting it up from scratch. Copy the gora
binary to a new location of your choice and ~/.gora
configuration file to your home directory on the new server. Make sure you have Docker installed and enabled for the user that runs Gora node on the new server as well. When you start your node for the first time at the new location, it may take some time to fetch the GNR docker image. Make sure not to run multiple nodes off the same configuration at the same time.
A Gora node configuration is defined by the blockchain accounts it is linked to as well as various customizations via configuration variables. These are set during the initialization process described above and usually do not need to change. But for basic troubleshooting or developer customization purposes, here is an overview.
Gora config file contains settings specific to your Gora node in JSON format. When deploying a node to cloud or launching it locally, Gora CLI tool reads this file and passes its contents to GNR via Docker container environment settings. This makes local configuration available to GNR without using docker mounts. The default location of Gora config file is ~/.gora
.
A value extraction specification tells oracle how to extract a specific piece of data from a response returned by the data source. It consists of up to three parts, separated by colons: method, expression and an optional rounding modifier. For example, substr:4,11 tells Gora that it needs to return a substring from data source output, starting at 4th and ending at 11th character.
Gora supports several extraction methods and expression formats:
Expression type | Example | Best for |
---|---|---|
JSONPath | jsonpath:jsonpath:$.data.temperature |
JSON documents |
XPath | xpath:/p/a |
XML documents |
Regular expression | regex: the magic number is ([0-9]+) |
Structured text |
Character Substring | substr:0,10 |
Unstructured text |
Byte fragment | bytes:2,4 |
Unstructured binary data |
An optional rounding modifier is used to round floating-point values to certain amount of digits after the point. This may be necessary with some types of values such as cryptocurrency exchange rates. They can be so volatile that different Gora nodes are likely to get slightly different results despite querying them at almost the same time. That would prevent the nodes from achieving consensus and confirming the value as authentic. Adequate rounding gets us around this issue.
For instance, if you specify jsonpath:$.rate:3
, the responses { "rate":
1.2344 }
and { "rate": 1.2342 }
that may be received by different Gora nodes will yield the same value "1.234"
. The nodes will achieve consensus and you will get "1.234"
as the resulting oracle value. Rounding only affects fractional part of the rounded number, all whole part digits are preserved. For example, if rounding parameter is set to 4
, the number 1.12345
will be rounded to 1.1234
; but, for exmaple, the number 12345678
will remain unaffected.
Gora is always working to improve its core product as well as cater for new market demands. In 2025, we plan to accomplish the following goals:
Currently running a Gora node requires a Linux server or an AWS cloud account. Soon we will make it possible with any Javascript-enabled web browser. This will allow anyone with a web3 wallet to instantly become a node operator, earning rewards and strengthening the distributed nature of Gora node networks. ASO customers will be able to get started much more easily with their own node networks too. We have been working on making our node software codebase browser-compatible for a while and we are now finalizing this work.
Solana usage has grown tremendously in recent times. We are now building proof-of-concept implementations of our key components and will soon be prepared to fully tap into that ecosystem.
Building on the platform-agnostic nature of our revamped codebase, we are planning a React Native superapp. It will provide a single point of control for all Gora functions: a local Gora node, Gora-specific blockchain explorer, ASO control panel and server node deployments manager. Apart from user convenience, it will bring some unique opportunities such as direct feeding sensor data from any IOT or mobile device into blockchain.
Currently ASO customers willing to deploy their own executor oracles must follow a semi-manual process with the help of our staff. We are planning to make it fully automated using a factory smart contract.
Gora aims to accelerate the development of dApps that are useful in the day to day lives of millions of users. Gora will accomplish this by providing the infrastructure necessary for developers and organizations to build applications that make use of real world, off-chain data. Furthermore, Gora will enable developers to build applications that utilize off-chain computation.
In 2022, the number of daily users for decentralized applications surpassed 2.4 million daily users, with several billion dollars in volume. Global uncertainty has lead to increased adoption of cryptocurrencies, especially in emerging markets. This increased adoption of cryptocurrencies has familiarized people with wallets and the mechanics of transacting on-chain. As the general population becomes more comfortable using decentralized applications, demand for oracles to provide real world data will see a similar rise.
Along with record numbers of new users, the blockchain space has also seen record number of hacks and exploits, many of these being through bridges/oracles hacks and manipulation. This highlights the need for security as a top priority for any oracle solution such as Gora.
The primary goal of this litepaper is to highlight the organization and backers of Gora, and to highlight compliance of the protocol. In addition, it will aim to provide a high level overview of the functioning mechanism of the oracle, and the types of feeds Gora plans to launch with. This litepaper is accompanied by an in-depth economic design paper. Technical details of the network will be released in a future whitepaper closer to the launch date of the protocol.
The Gora vision is to deliver an Oracle solution that brings mainstream adoption to web3, and empower an ecosystem of decentralized applications to solve real-world user problems.
Our mission is to advance the state-of-the-art in oracle and blockchain reliability, safety, and performance by providing a flexible and modular oracle architecture. This architecture should support frequent upgrades, fast adoption of the latest technology advancements, and first-class support for new and emerging use cases.
We envision a decentralized, secure, and scalable network governed and operated by the community that uses it. When infrastructure demands grow, the computational resources of the oracle network scale up horizontally and vertically to meet those needs. As new use cases and technological advances arise, the network should frequently and seamlessly upgrade without interrupting users. Infrastructure concerns should fade into the background, and security should be guaranteed without trading off privacy or decentralization.
To achieve this Gora will:
- Hire and partner with highly skilled computer scientists, cryptographers, mathematicians and statisticians, data scientists and more
- Build a modular application that allows development and upgrades of specific modules
- Work with 2-3 audit firms to review Gora's Code
- Perform millions of simulations that model the trajectory of the token based on starting parameters
Oracle services tend to generally build out co-chains or blockchains to be able to handle requests, and then interface onto one or more blockchains. While this method allows for growth beyond a single blockchain, the technical infrastructure required is greater than building specifically for a single blockchain.
When designing Gora, we made the intentional choice to focus on building an independent network. Therefore, GoraNetwork may be categorized as a Layer-2 network, with a distributed set of nodes. This means a much faster time to market, and a greater focus on security by reducing the number of attack vectors than building GoraNetwork as a Layer 1 network. This does not limit GoraNetwork, as the distributed nodes can interface with other blockchains as necessary.
Another consideration in the level of decentralization (aka permission to participate). Although GoraNetwork is not in and of itself a blockchain, it is very similar in that it is a distributed system of nodes and data providers, working together to affect the blockchain state - and as such, a level of permission, if any, needs to be determined.
On one spectrum, an permissioned Oracle service can require knowledge the feed provider is, and only a select number of these known feed providers to submit feeds. On the other end of the spectrum, a permissionless service will neither require nor care who signs up to provide feeds.
In their article “When Permissioned Blockchains Deliver More Decentralization Than Permissionless” (Bakos et al.)[1], the authors make a compelling argument that “while distributed architectures may enable open access and decentralized control, they do not preordain these outcomes…permissionless access may result in essentially centralized control, while permissioned systems may be able to better support decentralized control”. What this means is just because a system is built to be decentralized and distributed, it will take time to get there, largely due to malicious forces or large actors with economies of scale taking over at before the network is able to achieve network effects.
The Gora team believes that decentralizing as much as possible is essential but agrees with the authors above that some form of permission is necessary, especially at the beginning. This is implemented via a deposit mechanism, where Node runners must stake and deposit a certain amount of network tokens to participate. This raises the barrier to entry, with the goal being to make being a malicious actor or a poor-quality provider as unattractive as possible. Furthermore, aggregated data feeds will allow community members to vote in or vote out feed providers. This way, only feed providers that are institutional and can guarantee high quality data are allowed to participate (unless the community decides otherwise).
The oracle problem, at its core, means having to trust an entity in a world that should be considered trustless. It could be argued that the only real solution to this problem would be to conduct all activities that an oracle provides onto the blockchain. For example, if ALGOs were only ever exchanged for USDC on-chain, and USDC was only ever spent by individuals on chain, the exchange price of USDC/Algo may be determined on the blockchain, hence solving the oracle problem. However, basketball cannot be played on the blockchain, nor does the blockchain have a weather system.
One possible solution might be having everyone watching the game input the score on the blockchain, but what if the loser of a match has more fans, and feel like they deserved a penalty? With the advent of social media, coordinated social ‘spamming’ a system can and does go viral such as the ‘Bum rush the charts’ campaign to influence iTunes charts[2], or meme-fueled GameStop buying frenzy meant mainly to bankrupt large hedge funds[3].
By such definitions, the oracle problem may be considered unsolvable. While a detailed analysis of the oracle problem is outside the scope of this document, an alternative to such a strict definition is that as long as data is sourced from multiple reliable sources, and poor quality or malicious participants stand to lose much more than they gain (in addition to having a high cost barrier to entry), the system should remain secure. In fact, almost all major blockchains are designed as such.
- [1] Bakos, Yannis and Halaburda, Hanna and Mueller-Bloch, Christoph, When Permissioned Blockchains Deliver More Decentralization Than Permissionless (September 25, 2019). Communications of the ACM, Available at SSRN: https://ssrn.com/abstract=3715596 or http://dx.doi.org/10.2139/ssrn.3715596
- [2] Gilliatt, N., & Gilliatt, N. (2007, March 21). Distributed viral social media spam. Social Media Today. https://www.socialmediatoday.com/content/distributed-viral-social-media-spam
- [3] Darbyshire, M. (2021, October 18). Almost 900,000 accounts traded GameStop at peak of meme stock craze. Financial Times. https://www.ft.com/content/df758a2a-6caf-4d5f-ab70-bb5815922b91
Gora is incorporated in Switzerland, in the city of Zug, also known as Crypto Valley. Switzerland became one of the first countries in the world to enact legal regulations for blockchain technology, creating legal certainty for developers, customers and investors. The integrity of the financial regulatory framework makes Switzerland the gold standard among jurisdictions, and cryptocurrencies are subject to the same rules as real monetary assets.
Gora plans to eventually be a fully decentralized protocol, where the organization has none to little say in the direction of the protocol. However, during the first 1-2 years of operation, the core team will directly affect the direction of the protocol, and as such requires the team to be compliant with local and international regulations, especially regarding the handling of monetary investments.
Gora is comprised of both full time and part-time contractors, and utilizes strategic advisors to help build the protocol. This section describes our team members.
Gora is advised by several experts with a wide range of skills. The section below describes our appointed advisors.