Skip to content

Commit

Permalink
Refactor publish (#4)
Browse files Browse the repository at this point in the history
* Refactor publish

* update

* update deployment
  • Loading branch information
jiguantong authored Oct 9, 2024
1 parent 00d40b4 commit 6e4fa44
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 66 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
## Addresses

### On Sepolia
XAPI: `0xC32BDE0002D71fD9a6C64eED65bdFD6e72F4Deba`
XAPI Consumer Example: `0xc6F64212F06F34e3d25bCb8D2C498387c46Fe7cb`
XAPI: `0xcedBd5b942c37870D172fEF4FC1C568C0aB8c038`
XAPI Consumer Example: `0xcd3627925b1396104126Fa400715A01C09703D66`

### On Near testnet
ORMP Aggregator: `ormpaggregator.guantong.testnet`
Expand Down
4 changes: 2 additions & 2 deletions aggregator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"clear": "near contract call-function as-transaction ormpaggregator.guantong.testnet _clear_state json-args {} prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' sign-as ormpaggregator.guantong.testnet network-config testnet sign-with-legacy-keychain send",
"test": "$npm_execpath run build && ava -- ./build/ormp_aggregator.wasm",
"publish-test": "near contract call-function as-transaction ormpaggregator.guantong.testnet publish json-args {} prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as ormpaggregator.guantong.testnet network-config testnet sign-with-legacy-keychain send",
"publish-external": "near contract call-function as-transaction ormpaggregator.guantong.testnet publish_external json-args '{\"request_id\": \"6277101735386680763835789423207666416102355444464034512855\"}' prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as guantong.testnet network-config testnet sign-with-legacy-keychain send",
"report": "near contract call-function as-transaction ormpaggregator.guantong.testnet report json-args '{\"request_id\":\"6277101735386680763835789423207666416102355444464034512855\",\"nonce\":\"1\",\"answers\":[{\"data_source_name\":\"test-source\",\"result\":\"test-result\"}],\"reporter_required\":{\"quorum\":1,\"threshold\":1},\"reward_address\":\"0x9f123456\"}' prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as guantong.testnet network-config testnet sign-with-legacy-keychain send",
"publish-external": "near contract call-function as-transaction ormpaggregator.guantong.testnet publish_external json-args '{\"request_id\": \"6277101735386680763835789423207666416102355444464034512856\",\"nonce\":\"3\",\"gas_limit\":\"1\",\"max_fee_per_gas\":\"1\",\"max_priority_fee_per_gas\":\"1\"}' prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as guantong.testnet network-config testnet sign-with-legacy-keychain send",
"report": "near contract call-function as-transaction ormpaggregator.guantong.testnet report json-args '{\"request_id\":\"6277101735386680763835789423207666416102355444464034512856\",\"answers\":[{\"data_source_name\":\"test-source\",\"result\":\"test-result\"}],\"reward_address\":\"0x9f123456\"}' prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as guantong.testnet network-config testnet sign-with-legacy-keychain send",
"ava": "ava"
},
"dependencies": {
Expand Down
86 changes: 44 additions & 42 deletions aggregator/src/abstract/aggregator.abstract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,10 @@ export enum RequestMethod {
export class PublishChainConfig {
chain_id: ChainId;
xapi_address: string;
gas_limit: string;
max_fee_per_gas: string;
max_priority_fee_per_gas: string;

constructor({ chain_id, xapi_address, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { chain_id: ChainId, xapi_address: string, gas_limit: string, max_fee_per_gas: string, max_priority_fee_per_gas: string }) {
constructor({ chain_id, xapi_address }: { chain_id: ChainId, xapi_address: string }) {
this.chain_id = chain_id;
this.xapi_address = xapi_address;
this.gas_limit = gas_limit;
this.max_fee_per_gas = max_fee_per_gas;
this.max_priority_fee_per_gas = max_priority_fee_per_gas;
}
}

Expand Down Expand Up @@ -94,11 +88,20 @@ class PublishData {
chain_config: PublishChainConfig;
signature: string;

constructor({ request_id, response, chain_config, signature }: { request_id: RequestId, response: Response, chain_config: PublishChainConfig, signature: string }) {
nonce: string;
gas_limit: string;
max_fee_per_gas: string;
max_priority_fee_per_gas: string;

constructor({ request_id, response, chain_config, signature, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId, response: Response, chain_config: PublishChainConfig, signature: string, nonce: string, gas_limit: string, max_fee_per_gas: string, max_priority_fee_per_gas: string }) {
this.request_id = request_id;
this.response = response;
this.chain_config = chain_config;
this.signature = signature;
this.nonce = nonce;
this.gas_limit = gas_limit;
this.max_fee_per_gas = max_fee_per_gas;
this.max_priority_fee_per_gas = max_priority_fee_per_gas;
}
}

Expand All @@ -125,6 +128,7 @@ export class ReporterRequired {

export class Response {
request_id: RequestId;
chain_id: ChainId;
valid_reporters: AccountId[];
// EVM address to distribute rewards
reporter_reward_addresses: string[];
Expand All @@ -136,8 +140,6 @@ export class Response {

// 👇 These values should be aggregated from reporter's answer
result: string;
nonce: string;
chain_id: ChainId;

constructor(request_id: RequestId) {
this.request_id = request_id;
Expand All @@ -150,19 +152,12 @@ export class Report {
request_id: RequestId;
reporter: AccountId;
timestamp: Timestamp;
chain_id: ChainId;
// Evm address to withdraw rewards on target chain
reward_address: string;
// Because cross-chain transactions may fail, we need to rely on the reporter to report nonce instead of maintaining the self-increment.
nonce: string;
reporter_required: ReporterRequired;
answers: Answer[];
constructor({ request_id, chain_id, nonce, answers, reporter_required, reward_address }: { request_id: RequestId, chain_id: ChainId, nonce: string, answers: Answer[], reporter_required: ReporterRequired, reward_address: string }) {
constructor({ request_id, answers, reward_address }: { request_id: RequestId, answers: Answer[], reward_address: string }) {
this.request_id = request_id;
this.chain_id = chain_id;
this.nonce = nonce;
this.answers = answers;
this.reporter_required = reporter_required;
this.reward_address = reward_address;
this.timestamp = near.blockTimestamp().toString();
this.reporter = near.signerAccountId();
Expand Down Expand Up @@ -277,9 +272,6 @@ export abstract class Aggregator extends ContractBase {
_set_publish_chain_config(publish_chain_config: PublishChainConfig): void {
this._assert_operator();
assert(publish_chain_config.chain_id != null, "chain_id can't be null.");
assert(publish_chain_config.gas_limit != null, "gas_limit can't be null.");
assert(publish_chain_config.max_fee_per_gas != null, "max_fee_per_gas can't be null.");
assert(publish_chain_config.max_priority_fee_per_gas != null, "max_priority_fee_per_gas can't be null.");
assert(publish_chain_config.xapi_address != null, "xapi_address can't be null.");
this.publish_chain_config_lookup.set(publish_chain_config.chain_id.toString(), publish_chain_config);
}
Expand Down Expand Up @@ -324,21 +316,15 @@ export abstract class Aggregator extends ContractBase {
return this.response_lookup.get(request_id);
}

abstract report({ request_id, nonce, answers, reporter_required, reward_address }: { request_id: RequestId, nonce: string, answers: Answer[], reporter_required: ReporterRequired, reward_address: string }): NearPromise;
_report({ request_id, nonce, answers, reporter_required, reward_address }: { request_id: RequestId, nonce: string, answers: Answer[], reporter_required: ReporterRequired, reward_address: string }): NearPromise {
abstract report({ request_id, answers, reward_address }: { request_id: RequestId, answers: Answer[], reward_address: string }): NearPromise;
_report({ request_id, answers, reward_address }: { request_id: RequestId, answers: Answer[], reward_address: string }): NearPromise {
assert(request_id != null, "request_id is null");
assert(nonce != null, "nonce is null");
assert(answers != null && answers.length > 0, "answers is empty");
assert(reward_address != null, "reward_address is null");

const _chain_id = (BigInt(request_id) >> BigInt(192)).toString();

const __report = new Report({
request_id,
chain_id: _chain_id,
nonce,
answers,
reporter_required,
reward_address
});

Expand Down Expand Up @@ -368,6 +354,8 @@ export abstract class Aggregator extends ContractBase {
if (_response.status == RequestStatus[RequestStatus.FETCHING] && BigInt(_response.started_at) + BigInt(this.timeout) < near.blockTimestamp()) {
_response.status = RequestStatus[RequestStatus.TIMEOUT];
new TimeoutEvent(_response).emit();
this.response_lookup.set(request_id, _response);
return;
}

// Only fetching request can accept reports.
Expand Down Expand Up @@ -462,13 +450,26 @@ export abstract class Aggregator extends ContractBase {
}
}

abstract publish_external({ request_id }: { request_id: RequestId }): NearPromise;
_publish({ request_id, promise_index }: { request_id: RequestId, promise_index: number }): NearPromise {
abstract publish_external({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId, nonce: string, gas_limit: string, max_fee_per_gas: string, max_priority_fee_per_gas: string }): NearPromise;
_publish({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId, nonce: string, gas_limit: string, max_fee_per_gas: string, max_priority_fee_per_gas: string }): NearPromise {
assert(nonce != null, "nonce can't be null.");
assert(gas_limit != null, "gas_limit can't be null.");
assert(max_fee_per_gas != null, "max_fee_per_gas can't be null.");
assert(max_priority_fee_per_gas != null, "max_priority_fee_per_gas can't be null.");

const _response = this.response_lookup.get(request_id);
assert(_response != null, `Response for ${request_id} does not exist`);
assert(_response.status == RequestStatus[RequestStatus.AGGREGATED] || _response.status == RequestStatus[RequestStatus.PUBLISHED], `Response status is ${_response.status}, can't be published`);

assert(_response.status == RequestStatus[RequestStatus.AGGREGATED], `Response status is ${_response.status}, can't be published`);
// Update timeout status if necessary.
if (BigInt(_response.started_at) + BigInt(this.timeout) < near.blockTimestamp()) {
_response.status = RequestStatus[RequestStatus.TIMEOUT];
new TimeoutEvent(_response).emit();
this.response_lookup.set(request_id, _response);
return;
}

_response.chain_id = (BigInt(request_id) >> BigInt(192)).toString();
const _chain_config = this.publish_chain_config_lookup.get(_response.chain_id.toString());
assert(_chain_config != null, `Chain config for ${_response.chain_id} does not exist`);

Expand All @@ -491,10 +492,10 @@ export abstract class Aggregator extends ContractBase {

const payload = ethereumTransaction({
chainId: BigInt(_response.chain_id),
nonce: BigInt(_response.nonce),
maxPriorityFeePerGas: BigInt(_chain_config.max_priority_fee_per_gas),
maxFeePerGas: BigInt(_chain_config.max_fee_per_gas),
gasLimit: BigInt(_chain_config.gas_limit),
nonce: BigInt(nonce),
maxPriorityFeePerGas: BigInt(max_priority_fee_per_gas),
maxFeePerGas: BigInt(max_fee_per_gas),
gasLimit: BigInt(gas_limit),
to: _chain_config.xapi_address,
value: BigInt(0),
data: function_call_data_bytes,
Expand All @@ -521,7 +522,7 @@ export abstract class Aggregator extends ContractBase {
NearPromise.new(near.currentAccountId())
.functionCall(
"publish_callback",
JSON.stringify({ request_id: request_id, promise_index: promise_index }),
JSON.stringify({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }),
BigInt(0),
// Beware of the 300T cap with mpc gas
BigInt(ONE_TERA_GAS * BigInt(25))
Expand All @@ -530,16 +531,17 @@ export abstract class Aggregator extends ContractBase {
return promise.asReturn();
}

abstract publish_callback({ request_id, promise_index }: { request_id: RequestId, promise_index: number }): void
_publish_callback({ request_id, promise_index }: { request_id: RequestId, promise_index: number }): void {
const _result = this._promise_result({ promise_index: promise_index });
near.log(`publish_callback ${request_id}, ${_result.success}, ${_result.result}, promise_index: ${promise_index}`);
abstract publish_callback({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId, nonce: string, gas_limit: string, max_fee_per_gas: string, max_priority_fee_per_gas: string }): void
_publish_callback({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId, nonce: string, gas_limit: string, max_fee_per_gas: string, max_priority_fee_per_gas: string }): void {
const _result = this._promise_result({ promise_index: 0 });
near.log(`publish_callback ${request_id}, ${_result.success}, ${_result.result}`);
const _response = this.response_lookup.get(request_id);
if (_result.success) {
_response.status = RequestStatus[RequestStatus.PUBLISHED];
const _chain_config = this.publish_chain_config_lookup.get(_response.chain_id.toString());
new PublishEvent(new PublishData({
request_id, response: _response, chain_config: _chain_config, signature: _result.result
request_id, response: _response, chain_config: _chain_config, signature: _result.result,
nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas
})).emit();
this.response_lookup.set(request_id, _response);
}
Expand Down
27 changes: 14 additions & 13 deletions aggregator/src/ormp.aggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class OrmpAggregator extends Aggregator {
}

_aggregate({ request_id, top_staked }: { request_id: RequestId, top_staked: Staked[] }): boolean {
// filter invalid reporter
// 1. Filter invalid reports via top staked reporters
const _reporters = this.report_lookup.get(request_id).map(r => r.reporter);
const _valid_reporters = _reporters.filter(reporter =>
top_staked.some(staked => staked.account_id === reporter)
Expand All @@ -42,35 +42,36 @@ class OrmpAggregator extends Aggregator {

const _each_reporter_report = [];
const _each_reporter_result = new Map<string, string>();

// 2. Aggregate from multi datasources of one report.
_valid_reports.forEach(report => {
const _result = this._aggregate_answer(report.answers.map(r => r.result)).result;
_each_reporter_result.set(report.reporter, _result);
_each_reporter_report.push(`${_result} | ${report.nonce} | ${report.chain_id}`);
_each_reporter_report.push(_result);
});

// 3. Aggregate from multi reports
const most_common_report = this._aggregate_answer(_each_reporter_report);
near.log("most_common_report: ", most_common_report);

const [result, nonce, chain_id] = most_common_report.result.split(' | ');
const result = most_common_report.result;
assert(_valid_reporters.length >= this.reporter_required.quorum, `Quorum: required ${this.reporter_required.quorum}, but got ${_valid_reporters.length}`);
assert(most_common_report.count >= this.reporter_required.threshold, `Threshold: required ${this.reporter_required.threshold}, but got ${most_common_report.count}`)

const _response = this.response_lookup.get(request_id);

_response.valid_reporters = [];
_response.reporter_reward_addresses = []
// filter most common reporter
// 4. Filter most common reporter, only most common report will be rewarded
for (const _report of _valid_reports) {
const key = `${_each_reporter_result.get(_report.reporter)} | ${_report.nonce} | ${_report.chain_id}`;
const key = _each_reporter_result.get(_report.reporter);
if (key === most_common_report.result) {
_response.reporter_reward_addresses.push(_report.reward_address);
_response.valid_reporters.push(_report.reporter);
}
}

_response.result = result;
_response.nonce = nonce;
_response.chain_id = chain_id;
this.response_lookup.set(request_id, _response);
return true;
}
Expand Down Expand Up @@ -103,13 +104,13 @@ class OrmpAggregator extends Aggregator {
/// Calls

@call({})
publish_external({ request_id }: { request_id: RequestId; }): NearPromise {
return super._publish({ request_id, promise_index: 0 });
publish_external({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId; nonce: string; gas_limit: string; max_fee_per_gas: string; max_priority_fee_per_gas: string; }): NearPromise {
return super._publish({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas });
}

@call({ privateFunction: true })
publish_callback({ request_id, promise_index }: { request_id: RequestId; promise_index: number; }): void {
super._publish_callback({ request_id, promise_index });
publish_callback({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId; nonce: string; gas_limit: string; max_fee_per_gas: string; max_priority_fee_per_gas: string; }): void {
super._publish_callback({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas });
}

@call({ privateFunction: true })
Expand All @@ -118,8 +119,8 @@ class OrmpAggregator extends Aggregator {
}

@call({ payableFunction: true })
report({ request_id, nonce, answers, reporter_required, reward_address }: { request_id: RequestId; nonce: string; answers: Answer[]; reporter_required: ReporterRequired; reward_address: string }): NearPromise {
return super._report({ request_id, nonce, answers, reporter_required, reward_address });
report({ request_id, answers, reward_address }: { request_id: RequestId; answers: Answer[]; reward_address: string; }): NearPromise {
return super._report({ request_id, answers, reward_address });
}

@call({ payableFunction: true })
Expand Down
3 changes: 1 addition & 2 deletions xapi-consumer/contracts/ConsumerExample.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ contract ConsumerExample is IXAPIConsumer{

function makeRequest(string memory aggregator) external payable {
string memory requestData = "{'hello':'world'}";
ReporterRequired memory reporterRequired = ReporterRequired(1, 1);
uint256 requestId = xapi.makeRequest{value: msg.value}(requestData, this.fulfillCallback.selector, aggregator, reporterRequired);
uint256 requestId = xapi.makeRequest{value: msg.value}(requestData, this.fulfillCallback.selector, aggregator);
emit RequestMade(requestId, requestData);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"ConsumerExampleModule#ConsumerExample": "0xc6F64212F06F34e3d25bCb8D2C498387c46Fe7cb"
"ConsumerExampleModule#ConsumerExample": "0xcd3627925b1396104126Fa400715A01C09703D66"
}
2 changes: 1 addition & 1 deletion xapi-consumer/ignition/modules/ConsumerExample.deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
module.exports = buildModule("ConsumerExampleModule", (m) => {
const deployer = m.getAccount(0);

const consumer = m.contract("ConsumerExample", ["0xC32BDE0002D71fD9a6C64eED65bdFD6e72F4Deba"], {
const consumer = m.contract("ConsumerExample", ["0xcedBd5b942c37870D172fEF4FC1C568C0aB8c038"], {
from: deployer
});

Expand Down
2 changes: 1 addition & 1 deletion xapi-consumer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"build": "npx hardhat compile",
"deploy": "npx hardhat ignition deploy ./ignition/modules/ConsumerExample.deploy.js --network sepolia",
"console": "npx hardhat console --network sepolia",
"verify": "npx hardhat verify --network sepolia 0xc6F64212F06F34e3d25bCb8D2C498387c46Fe7cb '0xC32BDE0002D71fD9a6C64eED65bdFD6e72F4Deba'"
"verify": "npx hardhat verify --network sepolia 0xcd3627925b1396104126Fa400715A01C09703D66 '0xcedBd5b942c37870D172fEF4FC1C568C0aB8c038'"
},
"dependencies": {
"xapi": "file:../xapi"
Expand Down
2 changes: 1 addition & 1 deletion xapi/contracts/XAPI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ contract XAPI is IXAPI, Ownable2Step {
for (uint256 i = 0; i < response.reporters.length; i++) {
rewards[response.reporters[i]] += aggregatorConfig.perReporterFee;
}
rewards[aggregatorConfig.fulfillAddress] += aggregatorConfig.publishFee;
rewards[aggregatorConfig.rewardAddress] += aggregatorConfig.publishFee;

(bool success,) =
request.callbackContract.call(abi.encodeWithSelector(request.callbackFunction, requestId, response));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"XAPIModule#XAPI": "0xcedBd5b942c37870D172fEF4FC1C568C0aB8c038"
}
Loading

0 comments on commit 6e4fa44

Please sign in to comment.