Overview
The fio.oracle middleware is a standalone node application that monitors activity on the FIO and Ethereum chains and when token and/or domain wraps and unwraps are detected on one chain, it initiates the same action on the other chain, thus acting as a bridge.
The fio.oracle middleware is meant to be run by some number of “oracles” who provide the consensus layer for approving a wrap or unwrap actions. For the initial release, there are three FIO Block producers who have agreed to run the oracle middleware. All three of these BP oracles must approve every wrap or unwrap transaction for it to succeed.
For example, if a user wants to wrap their domain, they first call the wrapdomain action on the FIO chain. Each of the three BP oracles independently are monitoring the FIO chain and see the wrapdomain action. Each of these oracles independently then calls wrapnft on the Polygon chain. If all three oracles execute the transaction on Polygon, the wrap succeeds.
Links
Repository: https://github.com/fioprotocol/fio.oracle
Wrapping Wiki: FIO Token and Domain NFT wrapping
Epic:
Jira Legacy |
---|
server | System JIRA |
---|
serverId | 5f0d8161-d4cf-3d17-96b1-53b2b2b5013d |
---|
key | BD-2195 |
---|
|
Overview of development environment: [FIP-17.a] FIO Token Wrapping
Wrap use case: Wrap
Unwrap use case: Unwrap
Overview
Oracle code will be run by designated FIO Chain BPs with access to:
FIO Chain: Existing FIO BPs already run FIO nodes.
Ethereum Chain: Local Ethereum node is preferred, but Infura or Etherscan can also be used.
Oracles will:
On the FIO Chain
On the Ethereum Chain
Oracle Go Prototype
The original Oracle prototype code was written in Go and is located at: https://github.com/blockpane/fio.oracle
The FIO wrapping/unwrapping contract
...
See Other Options Considered section on wiki page
...
Scopelift
View file |
---|
name | FIO-Addresses-On-Ethereum-Proposal_ScopeLift.pdf |
---|
|
...
LiquidApps
...
...
Chainlink
...
Band Protocol
...
The FIO wrapping/unwrapping contract specification is detailed in FIP 17.a: https://github.com/fioprotocol/fips/blob/master/fip-0017a.md
Github fio.oracle
contract: https://github.com/fioprotocol.atlassian.net/browse/BD-2195
Research links
Third-party applications
...
Technology
...
Overview
...
Notes
The Ethereum ERC20 wrapping/unwrapping contract
The Ethereum wrapping/unwrapping contract specification is detailed on the wiki: fio.erc20 - wFIO Contract Specification
Github fio.erc20
repository: https://github.com/fioprotocol/fio.erc20
Misc. Requirements
Issue | Summary | Decision |
---|
Storage of latest block number | The Oracle is getting the latest action from FIO history every 5 seconds. But when we restart the server, we read the all latest actions and calling wrap function from the start. Of course, it doesn't mint again but I think wrapping time can be long in this case. To prevent this problem, we need to save the latest block number to database or any external storage.what do you think about my suggest? | Should we track block number in the oracleledger table? Suggest that Oracles store a log file of all the transactions locally. If they then go down they can grab a time stamp or transaction ID from the log file to know which transactions have not yet been processed. If an oracle server crashes and the logs are lost, then the oracle would have to re-process all transactions (and rebuild the log file). This would be a one time process. Decision: We will start with using log files, but will ask BPs their opinion on this solution.
Jira Legacy |
---|
server | System JIRA |
---|
serverId | 5f0d8161-d4cf-3d17-96b1-53b2b2b5013d |
---|
key | BD-2563 |
---|
|
|
Admin front-end UI | Is there a need for a front-end UI to review different transactions, or can we just rely on table lookups, etc.? | Jira Legacy |
---|
server | System JIRA |
---|
serverId | 5f0d8161-d4cf-3d17-96b1-53b2b2b5013d |
---|
key | BD-2564 |
---|
|
|
Process a single transaction at a time | Given the complexity of validating wrap and unwrap transactions all the way through to finality, both Todd and Alex have suggested that we limit oracles to only process a single transaction at a time | Given that we are relying on manual monitoring of wrap/unwrap transactions to detect failures, limiting the oracle to a single transaction no longer makes sense. In other words, the oracle is now only doing the simple action of calling wrap or unwraptokens and is not doing any failover. So, a “single transaction” is already very small and contained and it would be difficult to NOT process only a single transaction at a time.
|
How often should get_actions be polled? | Need to determine how often to call get_actions on the FIO chain. | |
Transaction Retry | Because we are putting limited validation logic in the oracle, it may be necessary for Oracles to take action on failed transactions. It would be helpful to have a “transaction retry” function that can be called that retries specific wrap/unwrap transactions. | Jira Legacy |
---|
server | System JIRA |
---|
serverId | 5f0d8161-d4cf-3d17-96b1-53b2b2b5013d |
---|
key | BD-2561 |
---|
|
|
Log events and exceptions | All events and errors should be logged. Because there will be limited validation, these logs will be the primary way for oracle node operators to troubleshoot issues. | Jira Legacy |
---|
server | System JIRA |
---|
serverId | 5f0d8161-d4cf-3d17-96b1-53b2b2b5013d |
---|
key | BD-2562 |
---|
|
|
V1 History | The initial implementation calls get_actions against V1 history. Should we look at supporting Hyperion or other history solutions? | Discussed the V1 history implementation with the Oracle BPs and because it is a relatively simple call, it is not worth designing additional support for different history solutions. Once we deploy they will see how well it fits into their environments. |
Setting gasPrice and gasLimit | What is the best way to enable Oracle BPs to set the Eth gas prices and limit? | Pawel: Recommends manually setting in an environment variable (or something similar). |
Functionality
There are two main use cases that concern the Oracle, Wrap and Unwrap. These are detailed below.
Watchdog routines
In addition to wrap/unwrap, the Oracle should also have certain watchdog/monitoring routines that ensure the processes and routines executed by the Oracle healthy. For example:
Make sure a loop didn’t get stuck.
Ensure there are no blocked channels.
Ensure the health of the daemon itself.
Wrap
Wrap creates wFIO on the Ethereum chain.
See the following page for an overview of the Wrap use case: Wrap
Functionality | |
---|
Oracle Initialization | |
Alice (via dApp) calls wraptokens inside the fio.oracle contract on FIO chain | Example: Calling wraptokens using Cryptonym: Image Added |
fio.oracle contract actions
| fio.oracle contract actions:
Parameter Validation ( ensuring amount, token codes, pubkey and fees are all properly set ) Search oracle registration table (contains all registered oracles) and tally up the total number of registered oracles Collect Oracle fees, sort, and find the median. Send fee to all oracles. Emplace wrapping details inside the oracleldgrs table. Image AddedSend the wrapped amount from Alice to fio.oracle contract. Collect FIO/BP fees Increase Alices RAM by 512. Send successful response to Alice
|
Oracle monitors get_actions API on V1 History node | Every 5 seconds Oracle polls the get_actions API on History node Plugin to detect activity on the wrapping account (fio.oracle) Code Block |
---|
curl -s -XPOST http://44.242.158.42:8080/v1/history/get_actions -d '{
"account_name": "fio.oracle",
"pos": -1
}'
|
Retrieve all actions For each action, if block_num > actionIndex then validate and process the action (call ERC-20 wrap ) Set actionIndex = block_num of most recent wraptokens action
Code Block |
---|
const actionIdx = config.oracleCache.get("actionIndex");
const dataLen = Object.keys(data.data.actions).length;
var array = Array();
for (var i = 0; i<dataLen;i++){
if (data.data.actions[i].block_num > actionIdx) {
array.push(data.data.actions[i]);
}
config.oracleCache.set("actionIndex", data.data.actions[dataLen-1].block_num)
}
return array; |
Since there eventually may be very many transactions, it may make sense to walk backward through the table using "pos" and "offset". Maybe you grab the most recent 5 actions and see if any of them are new. If ALL of them are new, then you need to grab the next 5 actions, etc. Jira Legacy |
---|
server | System JIRA |
---|
serverId | 5f0d8161-d4cf-3d17-96b1-53b2b2b5013d |
---|
key | BD-2566 |
---|
|
|
wrap transaction finality monitoring on FIO Chain
| |
Oracle validates the FIO chain wraptokens transaction. | Code Block |
---|
if (wrapData[i].action_trace.act.data.memo == "Token Wrapping") |
|
Responding to invalid wraptokens transaction | |
Oracle executes wrap on fio.erc20 contract on Ethereum chain
| Code Block |
---|
wrap(ethaddress, FIO (SUF) amount, obtid);ex. wrap(“0x00000000000”, 10000000, “0x123456789”); |
|
fio.erc20 contract actions for pre-consensus calls to wrap function
| fio.erc20 contract actions:
|
fio.erc20 contract actions for final consensus call to wrap function
| Example of three oracles calling wrap: Code Block |
---|
Using these oracle, we call the wrap function on FIO token contract using web3.js like below.
instance.wrap[accounts[5], 10000, "99e20de9bb9f178f3ff1328c089925daff1d5dcb1da7dceaad7fc5126a08eaf5". {from: accounts[1]});
instance.wrap[accounts[5], 10000, "99e20de9bb9f178f3ff1328c089925daff1d5dcb1da7dceaad7fc5126a08eaf5". {from: accounts[2]});
instance.wrap[accounts[5], 10000, "99e20de9bb9f178f3ff1328c089925daff1d5dcb1da7dceaad7fc5126a08eaf5". {from: accounts[3]}); |
If an Oracle does not have enough ETH to cover the transaction, how do we notify the oracle operator that there is a problem? When the last oracle calls wrap , the transaction is executed
|
ERC-20 wrap validation | TBD: Adam to document how the contract handles invalid failed transactions. Put link to content here. Adam Androulidakis |
Responding to invalid ERC-20 wrap transaction | |
wrap transaction finality monitoring on Ethereum
| Alice receives the Ethereum Transaction ID and is responsible for monitoring the status. Unless Alice is using some kind of custom application, she only has access to her Ethereum address and has to sit there and wait to see if WFIO appears. Is this the expectation? If Alice sees that it has failed, what does she do?
Does the oracle do any kind of monitoring or validation of the overall transaction (beyond responding to the ERC-20 Event) Decision: Based on 5/13 discussion with Luke/Pawel/Dev team, no additional monitoring of the transaction on the Ethereum chain is required. Decision: Based on 5/13 discussion with Luke/Pawel/Dev team, only limited monitoring of the transaction on the Ethereum chain is required. All of the following do NOT need to be monitored or tracked by the Oracle: A transaction gets stuck in a pre-consensus state. A transaction disappears (e.g., ending up on a fork, etc.) It is common to have transactions go into a mempool, and then transaction ends up in an uncle (orphan) block. If the transaction in the uncle block has not been validated elsewhere, then it should be returned to the mempool. But, there are situations where it can disappear from the mempool.
Waiting to make sure the block has reached finality.
|
Exception handling
Error condition | Trigger | Oracle Action |
---|
Invalid chain | Chain passed to wraptokens action is not Ethereum (Note: this restriction is not enforced in the FIO Contract to allow for wrapping chain expansion without deployment of code) | Oracle logs error and does no further processing of the transaction. |
Invalid Ethereum address | Public address passed to wraptokens action is not a valid Ethereum address (Note: this restriction is not enforced in the FIO Contract to allow for wrapping chain expansion without deployment of code) | Oracle logs error and does no further processing of the transaction. |
Unwrap
Unwrap converts wFIO on Ethereum chain to FIO Tokens on FIO chain.
See the following page for an overview of the Unwrap use case: Unwrap
Functionality | |
---|
Oracle Initialization | |
Alice (dApp) executes unwrap on Ethereum chain
| unwrap(fio address, amount); ex. unwrap(hard@edge, 100000000); Image Added |
ERC-20 unwrap validation | TBD: Adam to document how the contract handles invalid failed transactions. Put link to content here. Adam Androulidakis |
fio.erc20 contract actions for valid unwrap transaction
| fio.erc20 contract actions
|
Oracle monitors unwrap event for transfers | Code Block |
---|
fioContract.getPastEvents('unwrapped',{
// filter: {id: 1},
fromBlock: lastBlockNumber,
toBlock: 'latest'
}
|
|
Oracle validates unwrap transaction | |
If exceptions are found, Oracle takes action to unwind transaction | |
unwrap transaction finality monitoring on Ethereum chain
| |
Oracle executes upwraptokens on fio.oracle contract on FIO chain | Call the FIO unwrap action with ETH transaction ID and amount. On FIO side, we call the unwrap action using push_transcation function using fio.js Code Block |
---|
const unwrapTokens = async (obt_id, fioAmount) => {
let contract = 'fio.oracle',
action = 'unwraptokens',
oraclePrivateKey = process.env.PRIVATE_KEY,
oraclePublicKey = process.env.PUBLIC_KEY,
oracleAccount = 'qbxn5zhw2ypw',
amount = fioAmount,
obtId = obt_id,
fioAddress = 'bp1@dapixdev';
const info = await (await fetch(httpEndpoint + 'v1/chain/get_info')).json();
const blockInfo = await (await fetch(httpEndpoint + 'v1/chain/get_block', { body: `{"block_num_or_id": ${info.last_irreversible_block_num}}`, method: 'POST' })).json()
const chainId = info.chain_id;
const currentDate = new Date();
const timePlusTen = currentDate.getTime() + 10000;
const timeInISOString = (new Date(timePlusTen)).toISOString();
const expiration = timeInISOString.substr(0, timeInISOString.length - 1);
const transaction = {
expiration,
ref_block_num: blockInfo.block_num & 0xffff,
ref_block_prefix: blockInfo.ref_block_prefix,
actions: [{
account: contract,
name: action,
authorization: [{
actor: oracleAccount,
permission: 'active',
}],
data: {
fio_address: fioAddress,
amount: amount,
obt_id: obtId,
actor: oracleAccount
},
}]
};
var abiMap = new Map()
var tokenRawAbi = await (await fetch(httpEndpoint + 'v1/chain/get_raw_abi', { body: `{"account_name": "fio.oracle"}`, method: 'POST' })).json()
abiMap.set('fio.oracle', tokenRawAbi)
var privateKeys = [oraclePrivateKey];
const tx = await Fio.prepareTransaction({
transaction,
chainId,
privateKeys,
abiMap,
textDecoder: new TextDecoder(),
textEncoder: new TextEncoder()
});
const pushResult = await fetch(httpEndpoint + 'v1/chain/push_transaction', {
body: JSON.stringify(tx),
method: 'POST',
});
const json = await pushResult.json()
if (json.type) {
console.log('Error: ', json);
} else if (json.error) {
console.log('Error: ', json)
} else {
console.log('Result: ', json)
}
}
|
|
fio.oracle contract actions for calls to upwraptokens function
| fio.oracle contract Actions: Parameter Validation ( min/max amount, fio address check ) Verify the actor is a registered oracle Find the fio.address inside the fionames table Search for previous votes of the same obt_id If found Search and verify actor has not voted prior copy vector and push account name to list of voted oracles to the vector of votes modify voters table with new vector
If not found
Compare number of votes with number of registered oracles Send success/fail response to the oracle
|
Responding to invalid upwraptokens transaction | Any kind of recovery? Just send a failure message to the user and note that their WFIO is burned but the transfer of FIO failed? Retry? What if 2 upwraptokens transaction succeed, but 1 oracle fails?
|
Oracle validates unwraptokens transaction | |
Ongoing monitoring of unwraptokens transactions | |
Exception handling
Error condition | Trigger | Oracle Action |
---|
Invalid FIO Address | FIO Address passed in with ERC-20 is not valid or does not exist | Oracle logs error and does no further processing of the transaction. |
| | |