Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

latest aggregation contracts #1007

Merged
merged 19 commits into from Mar 7, 2019
Merged

latest aggregation contracts #1007

merged 19 commits into from Mar 7, 2019

Conversation

se3000
Copy link
Contributor

@se3000 se3000 commented Feb 27, 2019

Cherry-picked onto the latest master. Still has a fair amount of outstanding work to do, but probably better to get it into master sooner than have to deal with large rebases/cherry-picks.

Adds limited support for aggregating oracle answers.

Copy link
Contributor

@coventry coventry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than these comments, LGTM.

solidity/contracts/Coordinator.sol Outdated Show resolved Hide resolved
solidity/contracts/Coordinator.sol Outdated Show resolved Hide resolved
uint256 result;
uint256 oraclePayment = callback.amount.div(oracles.length);
for (uint i = 0; i < responseCount; i++) {
result = result.add(callbacks[requestId].responses[oracles[i]]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using SafeMath#add means that the oracles don't get paid if the total of their responses overflow. A malicious consumer could use this to elicit unrewarded work from oracles.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mixing the result-aggregation logic with the chainlink-business logic of paying the oracles. We will almost certainly want to make the aggregation logic pluggable, because it's very application-sensitive. Might want to make this two separate loops, even though they're iterating over the same set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would checking if the value is less than the max value for uint248 before running aggregation work for us?

For example:

require(uint256(_data) < MAX_UINT248, "Number too big");

Unfortunately we're at the stack limit in this function as well, so we wouldn't be able to implement this check until aggregation is pulled out to its own function.

withdrawableTokens[oracles[i]] = withdrawableTokens[oracles[i]].add(oraclePayment);
}
result = result.div(responseCount);
delete callbacks[requestId];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the above calculation fails, this removal of the callback will not execute. Might matter, once storage is rented rather than bought.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SafeMath's .div() operation has no assertion:

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  function div(uint256 _a, uint256 _b) internal pure returns (uint256) {
    // assert(_b > 0); // Solidity automatically throws when dividing by 0
    // uint256 c = _a / _b;
    // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold
    return _a / _b;
  }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SaveMath won't, but it looks like solidity will?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what case would aggregation run with 0 responses, which is where we would be dividing by 0?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, just pointing out that even though SaveMath has no assertion, div can still fail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So basically how I see it is the fix for this is to use SafeMath for the addition operation on line 235, incrementing the responseCount. Just checking, are we good with that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up myself on this and I think we should make this change. The responseCount variable now is a uint8 (I had assumed it was already uint256), and we're not checking that some max number of oracles is being sent in for the initiateServiceAgreement function. So, assuming we didn't hit the gas limit for a block, we could overflow with 256 oracles, which SafeMath would guard against. This would also require changing responseCount in the Callback struct to uint256.

solidity/test/Coordinator_test.js Outdated Show resolved Hide resolved
solidity/test/Coordinator_test.js Outdated Show resolved Hide resolved
solidity/test/Coordinator_test.js Show resolved Hide resolved
@thodges-gh thodges-gh force-pushed the features/aggregation-3 branch 2 times, most recently from 3afdbfa to d0ce3cf Compare February 28, 2019 15:52
thodges-gh
thodges-gh previously approved these changes Mar 4, 2019
mapping(bytes32 => ServiceAgreement) public serviceAgreements;
mapping(address => uint256) public withdrawableTokens;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional
Should this be public? Even if this doesn't have public setters, do we want people to be able to read its value?

I'm focusing on the reality that some are private, and some are public, and the inconsistency probably stems from testability. Let's make sure that's ok.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyone would be able to read the storage of a deployed contract anyway in order to see "private" values.

@@ -131,56 +111,11 @@ contract Coordinator is ChainlinkRequestInterface, CoordinatorInterface {
_amount,
_callbackAddress,
_callbackFunctionId,
now.add(5 minutes),
now.add(EXPIRY_TIME),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional
You add to now twice. Perhaps there's gas savings if you DRY it up.

uint64 memory expiry = node.add(EXPIRY_INTERVAL)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've hit the stack limit in this function. Introducing a new variable to store the expiry time won't compile.

solidity/contracts/Coordinator.sol Show resolved Hide resolved
solidity/contracts/Coordinator.sol Outdated Show resolved Hide resolved
// exit early if not all response have been received
if (oracles.length > callback.responseCount) {
return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional
Suggestion for above, below, and this whole method in general. If there are no gas implications, would it be better to invoke methods that document their purpose in the name?

if responseIncomplete(oracles) {
  return true
}

fullfillWithAverage(...)

If there are gas considerations or the indirection reduces audit-ability, ignore.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct, we're eventually going to call the averaging function specified by the requester in the service agreement. For example:

return address(this).call(_aggFuncId, _requestId, _callbackAddress, _callbackFunctionId);

However, I think this story just covers the most basic form of aggregation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Broke out a couple small helper methods. Possibly premature, given that this area is going to be refactored quite a bit, but it should also help comprehension when we get back to it.

dimroc
dimroc previously approved these changes Mar 6, 2019
@se3000 se3000 merged commit e3a973a into master Mar 7, 2019
@se3000 se3000 deleted the features/aggregation-3 branch March 13, 2019 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants