Skip to content

Commit 148f021

Browse files
authored
Merge branch 'develop' into updatepool
2 parents 866494a + d693ed3 commit 148f021

28 files changed

+2133
-1088
lines changed

README.md

Lines changed: 2 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,10 @@
1-
# Oracle Core
2-
3-
![](images/oracle-core.png)
1+
# Oracle Core v2.0
42

53
The oracle core is the off-chain component that oracles who are part of an oracle pool run. This oracle core provides a HTTP API interface for reading the current protocol state & another for submitting datapoints. Once a datapoint is submited, the oracle core will automatically generate the required tx and post it as well as any other actions required for the protocol to run. This thereby allows the oracle to participate in the oracle pool protocol without any extra effort for the oracle operator.
64

75
The oracle core requires that the user has access to a full node wallet in order to create txs & perform UTXO-set scanning. Furthermore each oracle core is designed to work with only a single oracle pool. If an operator runs several oracles in several oracle pools then a single full node can be used, but several instances of oracle cores must be run (and set with different api ports).
86

9-
A `Connector` must also be used with the oracle core in order to acquire data to submit to the pool. Each connector sources data from the expected sources, potentially applies functions to said data, and then submits the data to the oracle core via HTTP API during the `Live Epoch` stage in the oracle pool protocol. All oracles for a given pool are expected to use the exact same connector, thereby making it simple to onboard and get started.
10-
11-
The current oracle core is built to run the protocol specified in the [Deviation Checking Oracle Pool Spec](/docs/specs/v0.2/Deviation-Checking-Oracle-Pool-Spec.md).
12-
13-
Other documents can also be found explaining how various parts of the oracle core work in the [docs folder](docs).
14-
15-
16-
17-
# Building & Running An Oracle
18-
The majority of oracle operators will only need to focus on setting up their own oracle core to work with an [already bootstrapped oracle pool](#Bootstrapping-An-Oracle-Pool). This section will explain how to do so for oracles using the ERG-USD connector. The steps are exactly the same for other connectors, but simply require using that connector's prepare script.
19-
20-
It is assumed that you are running this oracle on Linux, and have the following prerequisites:
21-
- Access to an [Ergo Node](https://github.com/ergoplatform/ergo) v3.3.0+ with an unlocked wallet.
22-
- A recent stable version of the [Rust Compiler](https://www.rust-lang.org/tools/install) installed.
23-
- The Linux CLI tool `screen` and the `libssl-dev` package on Ubuntu (aka `openssl-devel` on Fedora, and potentially slightly different on other distros) installed.
24-
25-
1. Clone this repository via:
26-
```sh
27-
git clone [email protected]:ergoplatform/oracle-core.git
28-
```
29-
2. Enter into the connector's script folder:
30-
```sh
31-
cd oracle-core/scripts/erg-usd-oracle
32-
```
33-
3. Run the prepare script which will automatically compile the oracle core and the connector for you:
34-
```sh
35-
sh prepare-erg-usd-oracle.sh
36-
```
37-
4. Enter into the newly created `oracle-core-deployed` folder:
38-
```sh
39-
cd ../../erg-usd-oracle-deployed & ls
40-
```
41-
5. Edit your `oracle-config.yaml` with your Ergo Node information, your oracle address (address that was used for bootstrapping the pool & is inside of your Ergo Node wallet), and other relevant pool information. (Optionally acquire a pre-configured `oracle-config.yaml` from the user who bootstrapped the oracle pool and simply fill in your node info/oracle address)
42-
6. Ensure your Ergo Node is running (and matches the info you input in the config) and has it's wallet unlocked (with some Ergs in the wallet to pay for tx fees).
43-
7. Launch your oracle by running `run-oracle.sh`:
44-
```sh
45-
sh run-oracle.sh
46-
```
47-
8. A `screen` instance will be created which launches both the oracle core and the connector. (Press `Ctrl+a - n` to go between the core & the connector screens).
48-
9. If your node is running and properly configured, the oracle core will inform you that it has successfully registered the required UTXO-set scans:
49-
```sh
50-
UTXO-Set Scans Have Been Successfully Registered With The Ergo Node
51-
```
52-
10. Press enter to confirm that the scans have been registered. Your oracle core is now properly set up and waiting for the UTXO-set scans to be triggered in order to read the state of the oracle pool on-chain to then perform actions/txs.
53-
11. Rescan the blockchain history by either using the `/wallet/rescan` GET endpoint of your node, or by deleting `.ergo/wallet/registry` in your Ergo Node folder. Either option triggers a rescan after the blockchain progresses into the next block.
54-
12. Once the node has finished rescanning (which can be checked via the `/wallet/status` endpoint and comparing the `walletHeight` value to the current blockheight), the oracle core & connector will automatically issue transactions and move the protocol forward.
55-
13. Congrats, you can now detach from the screen instance if you wish via `Ctrl+a d`. (And reattach via `screen -r`) Your oracle core/connector will run automatically going forward.
56-
57-
58-
# Bootstrapping An Oracle Pool
59-
In order for an oracle pool to run, it must be first created/bootstrapped on-chain. This is the bootstrap process that is required before oracle operators can run their oracle core and have the pool function on-chain.
60-
61-
Check out the [Oracle Pool Bootstrap folder](oracle-pool-bootstrap) for detailed instructions about how to bootstrap an oracle pool using the CLI tool or manually.
62-
63-
64-
# Writing A New Connector
65-
If you are looking to create a new Oracle Pool for a new datapoint, you need to write a new Connector. This process has been greatly simplified thanks to [`Connector Lib`](connectors/connector-lib).
7+
The current oracle core is built to run the protocol specified in the [EIP-0023 PR](https://github.com/ergoplatform/eips/pull/41).
668

67-
Now within 15-20 lines of Rust code, you can easily create your own Connector that plugs right in to the Oracle Core.
689

69-
If you would like to integrate your pool with the Ergo Explorer we have also created [`Frontend Connector Lib`](connectors/frontend-connector-lib). This library builds off of `Connector Lib` and automatically provides + runs an API server which produces all of the data required for the frontend.
7010

71-
Building a Frontend Connector provides a single endpoint which summarizes the majority of relevant data about your Oracle Pool, and as such can also be useful if you intend to create your own custom website/frontend for showing off what is going on in your pool.

bootstrap_config_example.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
# Example bootstrap config file corresponding to the non-advaced `bootstrap` command.
12
---
2-
refresh_contract_parameters:
3+
oracle_pool_parameter_values: # These values are the defaults described in EIP-0023
34
epoch_length: 30
45
buffer: 4
56
total_oracles: 15

core/src/actions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ fn execute_publish_datapoint_action(action: PublishDataPointAction) -> Result<()
6767
Ok(())
6868
}
6969

70-
impl OraclePool {
70+
impl<'a> OraclePool<'a> {
7171
// /// Generates and submits the "Collect Funds" action tx
7272
// pub fn action_collect_funds(&self) -> Result<String, StageError> {
7373
// let mut req = json::parse(BASIC_TRANSACTION_SEND_REQUEST).unwrap();

core/src/api.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,16 @@ pub fn start_get_api(repost_receiver: Receiver<bool>) {
3939
// Basic information about the oracle pool
4040
app.get("/poolInfo", move |context| {
4141
let parameters = &ORACLE_CONFIG;
42-
let num_of_oracles = datapoint_stage.number_of_boxes().unwrap_or(10);
42+
let num_of_oracles = datapoint_stage.stage.number_of_boxes().unwrap_or(10);
4343

4444
let response_json = json! ({
4545
"number_of_oracles": num_of_oracles,
46-
"datapoint_address": datapoint_stage.contract_address,
47-
"live_epoch_length": parameters.epoch_length,
48-
"deviation_range": parameters.max_deviation_percent,
49-
"consensus_num": parameters.min_data_points,
50-
"oracle_pool_nft_id": parameters.oracle_pool_nft,
51-
"oracle_pool_participant_token_id": parameters.oracle_pool_participant_token_id,
46+
"datapoint_address": datapoint_stage.stage.contract_address,
47+
"live_epoch_length": parameters.refresh_contract_parameters.epoch_length,
48+
"deviation_range": parameters.refresh_contract_parameters.max_deviation_percent,
49+
"consensus_num": parameters.refresh_contract_parameters.min_data_points,
50+
"oracle_pool_nft_id": parameters.token_ids.pool_nft_token_id,
51+
"oracle_pool_participant_token_id": parameters.token_ids.oracle_token_id,
5252

5353
});
5454

core/src/box_kind/ballot_box.rs

Lines changed: 104 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,54 @@
1-
use crate::contracts::ballot::{BallotContract, BallotContractError};
1+
use std::convert::TryInto;
2+
3+
use crate::{
4+
contracts::ballot::{BallotContract, BallotContractError},
5+
oracle_config::{BallotBoxWrapperParameters, CastBallotBoxVoteParameters},
6+
};
27
use ergo_lib::{
38
chain::ergo_box::box_builder::{ErgoBoxCandidateBuilder, ErgoBoxCandidateBuilderError},
49
ergo_chain_types::{Digest32, EcPoint},
510
ergotree_ir::{
611
chain::{
12+
address::{Address, AddressEncoder, AddressEncoderError},
713
ergo_box::{box_value::BoxValue, ErgoBox, ErgoBoxCandidate, NonMandatoryRegisterId},
814
token::{Token, TokenId},
915
},
10-
mir::constant::TryExtractInto,
16+
mir::constant::{TryExtractFromError, TryExtractInto},
17+
serialization::SigmaSerializationError,
1118
sigma_protocol::sigma_boolean::ProveDlog,
1219
},
1320
};
14-
use std::convert::TryFrom;
21+
use log::warn;
1522
use thiserror::Error;
1623

1724
#[derive(Debug, Error)]
1825
pub enum BallotBoxError {
1926
#[error("ballot box: no ballot token found")]
2027
NoBallotToken,
28+
#[error("ballot box: unknown ballot token id in `TOKENS(0)`")]
29+
UnknownBallotTokenId,
2130
#[error("ballot box: no reward token id in R7 register")]
2231
NoRewardTokenIdInR7,
2332
#[error("ballot box: no reward token quantity in R8 register")]
2433
NoRewardTokenQuantityInR8,
2534
#[error("ballot box: no group element in R4 register")]
2635
NoGroupElementInR4,
36+
#[error("ballot box: unexpected group element in R4 register")]
37+
UnexpectedGroupElementInR4,
2738
#[error("ballot box: no update box creation height in R5 register")]
2839
NoUpdateBoxCreationHeightInR5,
2940
#[error("ballot box: no pool box address hash in R6 register")]
3041
NoPoolBoxAddressInR6,
3142
#[error("ballot box: contract error {0:?}")]
3243
BallotContract(#[from] BallotContractError),
44+
#[error("ballot box: AddressEncoder error {0}")]
45+
AddressEncoder(#[from] AddressEncoderError),
46+
#[error("ballot box: TryExtractFrom error {0:?}")]
47+
TryExtractFrom(#[from] TryExtractFromError),
48+
#[error("ballot box: SigmaSerializationError {0:?}")]
49+
SigmaSerialization(#[from] SigmaSerializationError),
50+
#[error("ballot box: vote expected to be already cast, but hasn't")]
51+
ExpectedVoteCast,
3352
}
3453

3554
pub trait BallotBox {
@@ -46,59 +65,37 @@ pub struct BallotBoxWrapper {
4665
contract: BallotContract,
4766
}
4867

49-
impl BallotBox for BallotBoxWrapper {
50-
fn contract(&self) -> &BallotContract {
51-
&self.contract
52-
}
53-
54-
fn ballot_token(&self) -> Token {
55-
self.ergo_box
56-
.tokens
68+
impl BallotBoxWrapper {
69+
pub fn new(ergo_box: ErgoBox, inputs: BallotBoxWrapperInputs) -> Result<Self, BallotBoxError> {
70+
let CastBallotBoxVoteParameters {
71+
reward_token_id,
72+
reward_token_quantity,
73+
pool_box_address_hash,
74+
} = inputs
75+
.parameters
76+
.vote_parameters
5777
.as_ref()
58-
.unwrap()
59-
.get(0)
60-
.unwrap()
61-
.clone()
62-
}
63-
64-
fn min_storage_rent(&self) -> u64 {
65-
self.contract.min_storage_rent()
66-
}
67-
68-
fn ballot_token_owner(&self) -> ProveDlog {
69-
self.ergo_box
70-
.get_register(NonMandatoryRegisterId::R4.into())
71-
.unwrap()
72-
.try_extract_into::<EcPoint>()
73-
.unwrap()
74-
.into()
75-
}
76-
77-
fn get_box(&self) -> &ErgoBox {
78-
&self.ergo_box
79-
}
80-
}
81-
82-
impl TryFrom<ErgoBox> for BallotBoxWrapper {
83-
type Error = BallotBoxError;
84-
85-
fn try_from(ergo_box: ErgoBox) -> Result<Self, Self::Error> {
86-
let _ballot_token_id = ergo_box
78+
.ok_or(BallotBoxError::ExpectedVoteCast)?;
79+
let ballot_token_id = &ergo_box
8780
.tokens
8881
.as_ref()
8982
.ok_or(BallotBoxError::NoBallotToken)?
9083
.get(0)
9184
.ok_or(BallotBoxError::NoBallotToken)?
92-
.token_id
93-
.clone();
85+
.token_id;
86+
if *ballot_token_id != *inputs.ballot_token_id {
87+
return Err(BallotBoxError::UnknownBallotTokenId);
88+
}
9489

95-
if ergo_box
90+
let ec = ergo_box
9691
.get_register(NonMandatoryRegisterId::R4.into())
9792
.ok_or(BallotBoxError::NoGroupElementInR4)?
98-
.try_extract_into::<EcPoint>()
99-
.is_err()
100-
{
101-
return Err(BallotBoxError::NoGroupElementInR4);
93+
.try_extract_into::<EcPoint>()?;
94+
let prefix = inputs.parameters.contract_parameters.p2s.network();
95+
let config_from_address = AddressEncoder::new(prefix)
96+
.parse_address_from_str(&inputs.parameters.ballot_token_owner_address)?;
97+
if config_from_address != Address::P2Pk(ProveDlog::from(ec)) {
98+
return Err(BallotBoxError::UnexpectedGroupElementInR4);
10299
}
103100

104101
if ergo_box
@@ -110,38 +107,82 @@ impl TryFrom<ErgoBox> for BallotBoxWrapper {
110107
return Err(BallotBoxError::NoUpdateBoxCreationHeightInR5);
111108
}
112109

113-
if ergo_box
110+
let register_pool_box_address_hash = ergo_box
114111
.get_register(NonMandatoryRegisterId::R6.into())
115112
.ok_or(BallotBoxError::NoPoolBoxAddressInR6)?
116-
.try_extract_into::<Digest32>()
117-
.is_err()
118-
{
119-
return Err(BallotBoxError::NoPoolBoxAddressInR6);
113+
.try_extract_into::<Digest32>()?;
114+
let pb: Digest32 = base16::decode(pool_box_address_hash)
115+
.unwrap()
116+
.try_into()
117+
.unwrap();
118+
if pb != register_pool_box_address_hash {
119+
warn!("Pool box address in R6 register differs to config. Could be due to vote.");
120120
}
121121

122-
if ergo_box
122+
let register_reward_token_id = ergo_box
123123
.get_register(NonMandatoryRegisterId::R7.into())
124124
.ok_or(BallotBoxError::NoRewardTokenIdInR7)?
125-
.try_extract_into::<TokenId>()
126-
.is_err()
127-
{
128-
return Err(BallotBoxError::NoRewardTokenIdInR7);
125+
.try_extract_into::<TokenId>()?;
126+
if register_reward_token_id != *reward_token_id {
127+
warn!("Reward token id in R7 register differs to config. Could be due to vote.");
129128
}
130129

131-
if ergo_box
130+
let register_reward_token_quantity = ergo_box
132131
.get_register(NonMandatoryRegisterId::R8.into())
133132
.ok_or(BallotBoxError::NoRewardTokenQuantityInR8)?
134-
.try_extract_into::<i64>()
135-
.is_err()
136-
{
137-
return Err(BallotBoxError::NoRewardTokenQuantityInR8);
133+
.try_extract_into::<i32>()? as u32;
134+
135+
if register_reward_token_quantity != *reward_token_quantity {
136+
warn!("Reward token quantity in R8 register differs to config. Could be due to vote.");
138137
}
139138

140-
let contract = BallotContract::from_ergo_tree(ergo_box.ergo_tree.clone())?;
139+
let contract = BallotContract::from_ergo_tree(ergo_box.ergo_tree.clone(), inputs.into())?;
141140
Ok(Self { ergo_box, contract })
142141
}
143142
}
144143

144+
#[derive(Clone, Copy, Debug)]
145+
pub struct BallotBoxWrapperInputs<'a> {
146+
pub parameters: &'a BallotBoxWrapperParameters,
147+
/// Ballot token is expected to reside in `tokens(0)` of the ballot box.
148+
pub ballot_token_id: &'a TokenId,
149+
/// This token id appears as a constant in the ballot contract.
150+
pub update_nft_token_id: &'a TokenId,
151+
}
152+
153+
impl BallotBox for BallotBoxWrapper {
154+
fn contract(&self) -> &BallotContract {
155+
&self.contract
156+
}
157+
158+
fn ballot_token(&self) -> Token {
159+
self.ergo_box
160+
.tokens
161+
.as_ref()
162+
.unwrap()
163+
.get(0)
164+
.unwrap()
165+
.clone()
166+
}
167+
168+
fn min_storage_rent(&self) -> u64 {
169+
self.contract.min_storage_rent()
170+
}
171+
172+
fn ballot_token_owner(&self) -> ProveDlog {
173+
self.ergo_box
174+
.get_register(NonMandatoryRegisterId::R4.into())
175+
.unwrap()
176+
.try_extract_into::<EcPoint>()
177+
.unwrap()
178+
.into()
179+
}
180+
181+
fn get_box(&self) -> &ErgoBox {
182+
&self.ergo_box
183+
}
184+
}
185+
145186
#[allow(clippy::too_many_arguments)]
146187
pub fn make_local_ballot_box_candidate(
147188
contract: &BallotContract,

0 commit comments

Comments
 (0)