Skip to content

Commit a298dc5

Browse files
committed
discovery: make the service registry to be used from operators
- Makes the ServiceRegistry aware of the Controller. - Introduces registerFor() and unregisterFor() that can be called by both operators and indexers to set the registration. It accepts the indexer address as first parameter. - Update the deploy config file to pass the Controller address to the ServiceRegistry.
1 parent 04aeb83 commit a298dc5

File tree

8 files changed

+170
-24
lines changed

8 files changed

+170
-24
lines changed

contracts/discovery/IServiceRegistry.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ interface IServiceRegistry {
88

99
function register(string calldata _url, string calldata _geohash) external;
1010

11+
function registerFor(
12+
address _indexer,
13+
string calldata _url,
14+
string calldata _geohash
15+
) external;
16+
1117
function unregister() external;
1218

19+
function unregisterFor(address _indexer) external;
20+
1321
function isRegistered(address _indexer) external view returns (bool);
1422
}

contracts/discovery/ServiceRegistry.sol

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
pragma solidity ^0.6.12;
22
pragma experimental ABIEncoderV2;
33

4+
import "../governance/Managed.sol";
5+
46
import "./IServiceRegistry.sol";
57

68
/**
79
* @title ServiceRegistry contract
810
* @dev This contract supports the service discovery process by allowing indexers to
911
* register their service url and any other relevant information.
1012
*/
11-
contract ServiceRegistry is IServiceRegistry {
13+
contract ServiceRegistry is Managed, IServiceRegistry {
1214
// -- State --
1315

1416
mapping(address => IndexerService) public services;
@@ -18,29 +20,88 @@ contract ServiceRegistry is IServiceRegistry {
1820
event ServiceRegistered(address indexed indexer, string url, string geohash);
1921
event ServiceUnregistered(address indexed indexer);
2022

23+
/**
24+
* @dev Check if the caller is authorized (indexer or operator)
25+
*/
26+
function _onlyAuth(address _indexer) internal view returns (bool) {
27+
return msg.sender == _indexer || staking().isOperator(msg.sender, _indexer) == true;
28+
}
29+
30+
/**
31+
* @dev Contract Constructor.
32+
* @param _controller Controller address
33+
*/
34+
constructor(address _controller) public {
35+
Managed._initialize(_controller);
36+
}
37+
2138
/**
2239
* @dev Register an indexer service
2340
* @param _url URL of the indexer service
2441
* @param _geohash Geohash of the indexer service location
2542
*/
2643
function register(string calldata _url, string calldata _geohash) external override {
27-
address indexer = msg.sender;
44+
_register(msg.sender, _url, _geohash);
45+
}
46+
47+
/**
48+
* @dev Register an indexer service
49+
* @param _indexer Address of the indexer
50+
* @param _url URL of the indexer service
51+
* @param _geohash Geohash of the indexer service location
52+
*/
53+
function registerFor(
54+
address _indexer,
55+
string calldata _url,
56+
string calldata _geohash
57+
) external override {
58+
_register(_indexer, _url, _geohash);
59+
}
60+
61+
/**
62+
* @dev Internal: Register an indexer service
63+
* @param _indexer Address of the indexer
64+
* @param _url URL of the indexer service
65+
* @param _geohash Geohash of the indexer service location
66+
*/
67+
function _register(
68+
address _indexer,
69+
string calldata _url,
70+
string calldata _geohash
71+
) internal {
72+
require(_onlyAuth(_indexer), "!auth");
2873
require(bytes(_url).length > 0, "Service must specify a URL");
2974

30-
services[indexer] = IndexerService(_url, _geohash);
75+
services[_indexer] = IndexerService(_url, _geohash);
3176

32-
emit ServiceRegistered(indexer, _url, _geohash);
77+
emit ServiceRegistered(_indexer, _url, _geohash);
3378
}
3479

3580
/**
3681
* @dev Unregister an indexer service
3782
*/
3883
function unregister() external override {
39-
address indexer = msg.sender;
40-
require(isRegistered(indexer), "Service already unregistered");
84+
_unregister(msg.sender);
85+
}
86+
87+
/**
88+
* @dev Unregister an indexer service
89+
* @param _indexer Address of the indexer
90+
*/
91+
function unregisterFor(address _indexer) external override {
92+
_unregister(_indexer);
93+
}
94+
95+
/**
96+
* @dev Unregister an indexer service
97+
* @param _indexer Address of the indexer
98+
*/
99+
function _unregister(address _indexer) internal {
100+
require(_onlyAuth(_indexer), "!auth");
101+
require(isRegistered(_indexer), "Service already unregistered");
41102

42-
delete services[indexer];
43-
emit ServiceUnregistered(indexer);
103+
delete services[_indexer];
104+
emit ServiceUnregistered(_indexer);
44105
}
45106

46107
/**

contracts/staking/IStaking.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ interface IStaking {
9090

9191
function setOperator(address _operator, bool _allowed) external;
9292

93+
function isOperator(address _operator, address _indexer) external view returns (bool);
94+
9395
// -- Staking --
9496

9597
function stake(uint256 _tokens) external;

contracts/staking/Staking.sol

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ contract Staking is StakingV1Storage, GraphUpgradeable, IStaking {
182182
* @dev Check if the caller is authorized (indexer or operator)
183183
*/
184184
function _onlyAuth(address _indexer) internal view returns (bool) {
185-
return msg.sender == _indexer || operatorAuth[_indexer][msg.sender] == true;
185+
return msg.sender == _indexer || isOperator(msg.sender, _indexer) == true;
186186
}
187187

188188
/**
@@ -524,6 +524,15 @@ contract Staking is StakingV1Storage, GraphUpgradeable, IStaking {
524524
emit SetOperator(msg.sender, _operator, _allowed);
525525
}
526526

527+
/**
528+
* @dev Return true if operator is allowed for indexer.
529+
* @param _operator Address of the operator
530+
* @param _indexer Address of the indexer
531+
*/
532+
function isOperator(address _operator, address _indexer) public override view returns (bool) {
533+
return operatorAuth[_indexer][_operator];
534+
}
535+
527536
/**
528537
* @dev Deposit tokens on the indexer stake.
529538
* @param _tokens Amount of tokens to stake

graph.config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ contracts:
2525
- fn: "setContractProxy"
2626
id: "0x45fc200c7e4544e457d3c5709bfe0d520442c30bbcbdaede89e8d4a4bbc19247" # keccak256('GraphToken')
2727
contractAddress: "${{GraphToken.address}}"
28+
ServiceRegistry:
29+
init:
30+
controller: "${{Controller.address}}"
2831
EpochManager:
2932
init:
3033
controller: "${{Controller.address}}"

test/lib/deployment.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,13 @@ export async function deployEthereumDIDRegistry(deployer: Signer): Promise<Ether
188188
>
189189
}
190190

191-
export async function deployServiceRegistry(deployer: Signer): Promise<ServiceRegistry> {
192-
return (deployContract('ServiceRegistry', deployer) as unknown) as Promise<ServiceRegistry>
191+
export async function deployServiceRegistry(
192+
deployer: Signer,
193+
controller: string,
194+
): Promise<ServiceRegistry> {
195+
return (deployContract('ServiceRegistry', deployer, controller) as unknown) as Promise<
196+
ServiceRegistry
197+
>
193198
}
194199

195200
export async function deployStaking(deployer: Signer, controller: string): Promise<Staking> {

test/lib/fixtures.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class NetworkFixture {
3333
arbitratorAddress,
3434
)
3535
const rewardsManager = await deployment.deployRewardsManager(deployer, controller.address)
36-
const serviceRegistry = await deployment.deployServiceRegistry(deployer)
36+
const serviceRegistry = await deployment.deployServiceRegistry(deployer, controller.address)
3737

3838
// Setup controller
3939
await controller.setContractProxy(utils.id('EpochManager'), epochManager.address)

test/serviceRegisty.test.ts

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import { expect } from 'chai'
22

33
import { ServiceRegistry } from '../build/typechain/contracts/ServiceRegistry'
4+
import { Staking } from '../build/typechain/contracts/Staking'
45

5-
import * as deployment from './lib/deployment'
66
import { getAccounts, Account } from './lib/testHelpers'
7+
import { NetworkFixture } from './lib/fixtures'
78

89
describe('ServiceRegistry', () => {
9-
let me: Account
10+
let governor: Account
1011
let indexer: Account
12+
let operator: Account
13+
14+
let fixture: NetworkFixture
1115

1216
let serviceRegistry: ServiceRegistry
13-
const geohash = '69y7hdrhm6mp'
17+
let staking: Staking
1418

1519
const shouldRegister = async (url: string, geohash: string) => {
1620
// Register the indexer service
@@ -26,23 +30,31 @@ describe('ServiceRegistry', () => {
2630
}
2731

2832
before(async function () {
29-
;[me, indexer] = await getAccounts()
33+
;[governor, indexer, operator] = await getAccounts()
34+
35+
fixture = new NetworkFixture()
36+
;({ serviceRegistry, staking } = await fixture.load(governor.signer, governor.signer))
3037
})
3138

3239
beforeEach(async function () {
33-
const controller = await deployment.deployController(me.signer)
34-
serviceRegistry = await deployment.deployServiceRegistry(me.signer)
40+
await fixture.setUp()
41+
})
42+
43+
afterEach(async function () {
44+
await fixture.tearDown()
3545
})
3646

3747
describe('register', function () {
48+
const url = 'https://192.168.2.1/'
49+
const geo = '69y7hdrhm6mp'
50+
3851
it('should allow registering', async function () {
39-
const url = 'https://192.168.2.1/'
40-
await shouldRegister(url, geohash)
52+
await shouldRegister(url, geo)
4153
})
4254

4355
it('should allow registering with a very long string', async function () {
4456
const url = 'https://' + 'a'.repeat(125) + '.com'
45-
await shouldRegister(url, geohash)
57+
await shouldRegister(url, geo)
4658
})
4759

4860
it('should allow updating a registration', async function () {
@@ -56,14 +68,35 @@ describe('ServiceRegistry', () => {
5668
const tx = serviceRegistry.connect(indexer.signer).register('', '')
5769
await expect(tx).revertedWith('Service must specify a URL')
5870
})
71+
72+
describe('operator', function () {
73+
it('reject register from unauthorized operator', async function () {
74+
const tx = serviceRegistry
75+
.connect(operator.signer)
76+
.registerFor(indexer.address, 'http://thegraph.com', '')
77+
await expect(tx).revertedWith('!auth')
78+
})
79+
80+
it('should register from operator', async function () {
81+
// Auth and register
82+
await staking.connect(indexer.signer).setOperator(operator.address, true)
83+
await serviceRegistry.connect(operator.signer).registerFor(indexer.address, url, geo)
84+
85+
// Updated state
86+
const indexerService = await serviceRegistry.services(indexer.address)
87+
expect(indexerService.url).eq(url)
88+
expect(indexerService.geohash).eq(geo)
89+
})
90+
})
5991
})
6092

6193
describe('unregister', function () {
62-
it('should unregister existing registration', async function () {
63-
const url = 'https://thegraph.com'
94+
const url = 'https://192.168.2.1/'
95+
const geo = '69y7hdrhm6mp'
6496

97+
it('should unregister existing registration', async function () {
6598
// Register the indexer service
66-
await serviceRegistry.connect(indexer.signer).register(url, geohash)
99+
await serviceRegistry.connect(indexer.signer).register(url, geo)
67100

68101
// Unregister the indexer service
69102
const tx = serviceRegistry.connect(indexer.signer).unregister()
@@ -74,5 +107,30 @@ describe('ServiceRegistry', () => {
74107
const tx = serviceRegistry.connect(indexer.signer).unregister()
75108
await expect(tx).revertedWith('Service already unregistered')
76109
})
110+
111+
describe('operator', function () {
112+
it('reject unregister from unauthorized operator', async function () {
113+
// Register the indexer service
114+
await serviceRegistry.connect(indexer.signer).register(url, geo)
115+
116+
// Unregister
117+
const tx = serviceRegistry.connect(operator.signer).unregisterFor(indexer.address)
118+
await expect(tx).revertedWith('!auth')
119+
})
120+
121+
it('should unregister from operator', async function () {
122+
// Register the indexer service
123+
await serviceRegistry.connect(indexer.signer).register(url, geo)
124+
125+
// Auth and unregister
126+
await staking.connect(indexer.signer).setOperator(operator.address, true)
127+
await serviceRegistry.connect(operator.signer).unregisterFor(indexer.address)
128+
129+
// Updated state
130+
const indexerService = await serviceRegistry.services(indexer.address)
131+
expect(indexerService.url).eq('')
132+
expect(indexerService.geohash).eq('')
133+
})
134+
})
77135
})
78136
})

0 commit comments

Comments
 (0)