Skip to content
This repository was archived by the owner on Jan 16, 2023. It is now read-only.

Commit 18caafe

Browse files
authored
Separate Node storage from Node Rules (#219)
* separate node storage from rules * updated version number of rules Signed-off-by: Sally MacFarlane <sally.macfarlane@consensys.net>
1 parent c84e9b8 commit 18caafe

13 files changed

+267
-78
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ ONLY use these instructions if you are doing development work on the Dapp itself
4040
This is the easiest way to get started for development with the permissioning Dapp:
4141

4242
#### Compile and migrate the contracts (Development mode) ####
43-
1. Delete your environment variables named `NODE_INGRESS_CONTRACT_ADDRESS`, `ACCOUNT_INGRESS_CONTRACT_ADDRESS`, `ACCOUNT_STORAGE_CONTRACT_ADDRESS` AND
43+
1. Delete your environment variables named `NODE_INGRESS_CONTRACT_ADDRESS`, `ACCOUNT_INGRESS_CONTRACT_ADDRESS`, `ACCOUNT_STORAGE_CONTRACT_ADDRESS`, `NODE_STORAGE_CONTRACT_ADDRESS` AND
4444
`CHAIN_ID` - you might need to restart your terminal session to have your changes applied. If you are using a `.env` file, you can comment out the variables.
4545
1. Start a terminal session and start a Truffle Ganache node running `truffle develop`. This will start a Ganache node and create a Truffle console session.
4646
1. In the truffle console, run all migrations from scratch with `migrate --reset`. Keep this terminal session open to maintain your Ganache node running.

contracts/ExposedNodeRulesList.sol

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
pragma solidity 0.5.9;
22

33
import "./NodeRulesList.sol";
4+
import "./NodeStorage.sol";
45

56

67
contract ExposedNodeRulesList is NodeRulesList {
78

8-
function _calculateKey(string calldata _enodeId, string calldata _host, uint16 _port) external pure returns(uint256) {
9+
function _setStorage(NodeStorage _storage) public {
10+
return setStorage(_storage);
11+
}
12+
13+
function _calculateKey(string calldata _enodeId, string calldata _host, uint16 _port) external view returns(uint256) {
914
return calculateKey(_enodeId, _host, _port);
1015
}
1116

contracts/NodeRules.sol

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ contract NodeRules is NodeRulesProxy, NodeRulesList {
2626
// this will be used to protect data when upgrading contracts
2727
bool private readOnlyMode = false;
2828
// version of this contract: semver like 1.2.14 represented like 001002014
29-
uint private version = 1000000;
29+
uint private version = 3000000;
3030

3131
NodeIngress private nodeIngressContract;
3232

@@ -43,7 +43,8 @@ contract NodeRules is NodeRulesProxy, NodeRulesList {
4343
_;
4444
}
4545

46-
constructor (NodeIngress _nodeIngressAddress) public {
46+
constructor (NodeIngress _nodeIngressAddress, NodeStorage _storage) public {
47+
setStorage(_storage);
4748
nodeIngressContract = _nodeIngressAddress;
4849
}
4950

@@ -134,13 +135,6 @@ contract NodeRules is NodeRulesProxy, NodeRulesList {
134135
return size();
135136
}
136137

137-
function getByIndex(uint index) external view returns (string memory enodeId, string memory host, uint16 port) {
138-
if (index >= 0 && index < size()) {
139-
enode memory item = allowlist[index];
140-
return (item.enodeId, item.host, item.port);
141-
}
142-
}
143-
144138
function triggerRulesChangeEvent(bool addsRestrictions) public {
145139
nodeIngressContract.emitRulesChangeEvent(addsRestrictions);
146140
}

contracts/NodeRulesList.sol

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pragma solidity 0.5.9;
22

3+
import "./NodeStorage.sol";
34

45

56
contract NodeRulesList {
@@ -11,48 +12,37 @@ contract NodeRulesList {
1112
uint16 port;
1213
}
1314

14-
enode[] public allowlist;
15-
mapping (uint256 => uint256) private indexOf; //1-based indexing. 0 means non-existent
15+
NodeStorage private nodeStorage;
1616

17-
function calculateKey(string memory _enodeId, string memory _host, uint16 _port) internal pure returns(uint256) {
18-
return uint256(keccak256(abi.encodePacked(_enodeId, _host, _port)));
17+
function setStorage(NodeStorage _storage) internal {
18+
nodeStorage = _storage;
19+
}
20+
21+
function upgradeVersion(address _newVersion) internal {
22+
nodeStorage.upgradeVersion(_newVersion);
1923
}
2024

2125
function size() internal view returns (uint256) {
22-
return allowlist.length;
26+
return nodeStorage.size();
2327
}
2428

2529
function exists(string memory _enodeId, string memory _host, uint16 _port) internal view returns (bool) {
26-
return indexOf[calculateKey(_enodeId, _host, _port)] != 0;
30+
return nodeStorage.exists(_enodeId, _host, _port);
2731
}
2832

2933
function add(string memory _enodeId, string memory _host, uint16 _port) internal returns (bool) {
30-
uint256 key = calculateKey(_enodeId, _host, _port);
31-
if (indexOf[key] == 0) {
32-
indexOf[key] = allowlist.push(enode(_enodeId, _host, _port));
33-
return true;
34-
}
35-
return false;
34+
return nodeStorage.add(_enodeId, _host, _port);
3635
}
3736

3837
function remove(string memory _enodeId, string memory _host, uint16 _port) internal returns (bool) {
39-
uint256 key = calculateKey(_enodeId, _host, _port);
40-
uint256 index = indexOf[key];
41-
42-
if (index > 0 && index <= allowlist.length) { //1 based indexing
43-
//move last item into index being vacated (unless we are dealing with last index)
44-
if (index != allowlist.length) {
45-
enode memory lastEnode = allowlist[allowlist.length - 1];
46-
allowlist[index - 1] = lastEnode;
47-
indexOf[calculateKey(lastEnode.enodeId, lastEnode.host, lastEnode.port)] = index;
48-
}
49-
50-
//shrink array
51-
allowlist.length -= 1; // mythx-disable-line SWC-101
52-
indexOf[key] = 0;
53-
return true;
54-
}
55-
56-
return false;
38+
return nodeStorage.remove(_enodeId, _host, _port);
39+
}
40+
41+
function calculateKey(string memory _enodeId, string memory _host, uint16 _port) public view returns(uint256) {
42+
return nodeStorage.calculateKey(_enodeId, _host, _port);
43+
}
44+
45+
function getByIndex(uint index) external view returns (string memory enodeId, string memory host, uint16 port) {
46+
return nodeStorage.getByIndex(index);
5747
}
5848
}

contracts/NodeStorage.sol

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
pragma solidity 0.5.9;
2+
3+
import "./Admin.sol";
4+
import "./NodeIngress.sol";
5+
6+
7+
contract NodeStorage {
8+
event VersionChange(
9+
address oldAddress,
10+
address newAddress
11+
);
12+
// initialize this to the deployer of this contract
13+
address private latestVersion = msg.sender;
14+
address private owner = msg.sender;
15+
16+
NodeIngress private ingressContract;
17+
18+
19+
20+
// struct size = 82 bytes
21+
struct enode {
22+
string enodeId;
23+
string ip;
24+
uint16 port;
25+
}
26+
27+
enode[] public allowlist;
28+
mapping (uint256 => uint256) private indexOf; //1-based indexing. 0 means non-existent
29+
30+
constructor (NodeIngress _ingressContract) public {
31+
ingressContract = _ingressContract;
32+
}
33+
34+
modifier onlyLatestVersion() {
35+
require(msg.sender == latestVersion, "only the latestVersion can modify the list");
36+
_;
37+
}
38+
39+
modifier onlyAdmin() {
40+
if (address(0) == address(ingressContract)) {
41+
require(msg.sender == owner, "only owner permitted since ingressContract is explicitly set to zero");
42+
} else {
43+
address adminContractAddress = ingressContract.getContractAddress(ingressContract.ADMIN_CONTRACT());
44+
45+
require(adminContractAddress != address(0), "Ingress contract must have Admin contract registered");
46+
require(Admin(adminContractAddress).isAuthorized(msg.sender), "Sender not authorized");
47+
}
48+
_;
49+
}
50+
51+
function upgradeVersion(address _newVersion) public onlyAdmin {
52+
emit VersionChange(latestVersion, _newVersion);
53+
latestVersion = _newVersion;
54+
}
55+
56+
function size() public view returns (uint256) {
57+
return allowlist.length;
58+
}
59+
60+
function exists(string memory _enodeId, string memory _ip, uint16 _port) public view returns (bool) {
61+
return indexOf[calculateKey(_enodeId, _ip, _port)] != 0;
62+
}
63+
64+
function add(string memory _enodeId, string memory _ip, uint16 _port) public returns (bool) {
65+
uint256 key = calculateKey(_enodeId, _ip, _port);
66+
if (indexOf[key] == 0) {
67+
indexOf[key] = allowlist.push(enode(_enodeId, _ip, _port));
68+
return true;
69+
}
70+
return false;
71+
}
72+
73+
function remove(string memory _enodeId, string memory _ip, uint16 _port) public returns (bool) {
74+
uint256 key = calculateKey(_enodeId, _ip, _port);
75+
uint256 index = indexOf[key];
76+
77+
if (index > 0 && index <= allowlist.length) { //1 based indexing
78+
//move last item into index being vacated (unless we are dealing with last index)
79+
if (index != allowlist.length) {
80+
enode memory lastEnode = allowlist[allowlist.length - 1];
81+
allowlist[index - 1] = lastEnode;
82+
indexOf[calculateKey(lastEnode.enodeId, lastEnode.ip, lastEnode.port)] = index;
83+
}
84+
85+
//shrink array
86+
allowlist.length -= 1; // mythx-disable-line SWC-101
87+
indexOf[key] = 0;
88+
return true;
89+
}
90+
91+
return false;
92+
}
93+
94+
function getByIndex(uint index) external view returns (string memory enodeId, string memory ip, uint16 port) {
95+
if (index >= 0 && index < size()) {
96+
enode memory item = allowlist[index];
97+
return (item.enodeId, item.ip, item.port);
98+
}
99+
}
100+
101+
function calculateKey(string memory _enodeId, string memory _ip, uint16 _port) public pure returns(uint256) {
102+
return uint256(keccak256(abi.encodePacked(_enodeId, _ip, _port)));
103+
}
104+
}

migrations/3_deploy_node_ingress_rules_contract.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
const Web3Utils = require("web3-utils");
22
const AllowlistUtils = require('../scripts/allowlist_utils');
33

4-
const NodeRules = artifacts.require("./NodeRules.sol");
4+
const Rules = artifacts.require("./NodeRules.sol");
55
const NodeIngress = artifacts.require("./NodeIngress.sol");
6+
const NodeStorage = artifacts.require("./NodeStorage.sol");
67
const Admin = artifacts.require("./Admin.sol");
78

89
const adminContractName = Web3Utils.utf8ToHex("administration");
910
const rulesContractName = Web3Utils.utf8ToHex("rules");
1011

1112
/* The address of the node ingress contract if pre deployed */
1213
let nodeIngress = process.env.NODE_INGRESS_CONTRACT_ADDRESS;
14+
/* The address of the node storage contract if pre deployed */
15+
let nodeStorage = process.env.NODE_STORAGE_CONTRACT_ADDRESS;
1316
let retainCurrentRulesContract = AllowlistUtils.getRetainNodeRulesContract();
1417

1518
async function logCurrentAllowlist(instance) {
@@ -51,14 +54,29 @@ module.exports = async(deployer, network) => {
5154
await nodeIngressInstance.setContractAddress(adminContractName, admin.address);
5255
console.log(" > Updated NodeIngress with Admin address = " + admin.address);
5356

54-
await deployer.deploy(NodeRules, nodeIngress);
55-
console.log(" > NodeRules deployed with NodeIngress.address = " + nodeIngress);
56-
let nodeRulesContract = await NodeRules.deployed();
57+
// STORAGE
58+
var storageInstance;
59+
if (! nodeStorage) {
60+
// Only deploy if we haven't been provided a pre-deployed address
61+
storageInstance = await deployer.deploy(NodeStorage, nodeIngress);
62+
console.log(" > Deployed NodeStorage contract to address = " + NodeStorage.address);
63+
nodeStorage = NodeStorage.address;
64+
} else {
65+
// is there a storage already deployed
66+
storageInstance = await NodeStorage.at(nodeStorage);
67+
console.log(">>> Using existing NodeStorage " + storageInstance.address);
68+
// TODO check that this contract is a storage contract eg call a method
69+
}
5770

71+
// rules -> storage
72+
await deployer.deploy(Rules, nodeIngress, nodeStorage);
73+
console.log(" > Rules deployed with NodeIngress.address = " + nodeIngress + "\n > and storageAddress = " + nodeStorage);
74+
console.log(" > Rules.address " + Rules.address);
75+
let nodeRulesContract = await Rules.deployed();
5876

5977

60-
await nodeIngressInstance.setContractAddress(rulesContractName, NodeRules.address);
61-
console.log(" > Updated NodeIngress contract with NodeRules address = " + NodeRules.address);
78+
await nodeIngressInstance.setContractAddress(rulesContractName, Rules.address);
79+
console.log(" > Updated NodeIngress contract with NodeRules address = " + Rules.address);
6280

6381
if(AllowlistUtils.isInitialAllowlistedNodesAvailable()) {
6482
console.log(" > Adding Initial Allowlisted eNodes ...");

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
]
114114
},
115115
"description": "Smart contracts and dapp implementing EEA spec onchain permissioning",
116-
"version": "2.0.2",
116+
"version": "2.1.0",
117117
"main": "README.md",
118118
"directories": {
119119
"test": "test"

test/test-node-ingress-proxy.js

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const NodeIngress = artifacts.require('NodeIngress.sol');
22
const NodeRules = artifacts.require('NodeRules.sol');
33
const Admin = artifacts.require('Admin.sol');
4+
const RulesStorage = artifacts.require('NodeStorage.sol');
45

56
const RULES='0x72756c6573000000000000000000000000000000000000000000000000000000';
67
const ADMIN='0x61646d696e697374726174696f6e000000000000000000000000000000000000';
@@ -15,13 +16,22 @@ contract ('NodeIngress (proxying permissioning check to rules contract)', () =>
1516
let nodeIngressContract;
1617
let nodeRulesContract;
1718
let adminContract;
19+
let storageContract;
1820

1921
beforeEach(async () => {
2022
nodeIngressContract = await NodeIngress.new();
2123
adminContract = await Admin.new();
24+
25+
// set the storage
26+
storageContract = await RulesStorage.new(nodeIngressContract.address);
27+
console.log(" >>> Storage contract deployed with address = " + storageContract.address);
2228

2329
await nodeIngressContract.setContractAddress(ADMIN, adminContract.address);
24-
nodeRulesContract = await NodeRules.new(nodeIngressContract.address);
30+
nodeRulesContract = await NodeRules.new(nodeIngressContract.address, storageContract.address);
31+
32+
// set rules as the storage owner
33+
await storageContract.upgradeVersion(nodeRulesContract.address);
34+
console.log(" >>> Set storage owner to Rules.address");
2535

2636
result = await nodeIngressContract.getContractAddress(ADMIN);
2737
assert.equal(result, adminContract.address, 'Admin contract should be reg');
@@ -56,29 +66,46 @@ contract ('NodeIngress (proxying permissioning check to rules contract)', () =>
5666
assert.equal(result, result2, "Call and proxy call did NOT return the same value");
5767
});
5868

59-
it('Should permit changing active NodeRules contract addresses', async () => {
69+
it('Should permit changing active NodeRules contract addresses WHILE keeping existing storage', async () => {
6070
let result;
61-
let result2;
62-
63-
// const icProxy = await NodeIngress.new();
64-
const rcProxy1 = await NodeRules.new(nodeIngressContract.address);
65-
const rcProxy2 = await NodeRules.new(nodeIngressContract.address);
6671

6772
// Verify that the NodeRules contract has not been registered
6873
result = await nodeIngressContract.getContractAddress(RULES);
6974
assert.equal(result, "0x0000000000000000000000000000000000000000", 'NodeRules contract should NOT already be registered');
7075

7176
// Register the initial NodeRules contract
77+
await nodeIngressContract.setContractAddress(RULES, nodeRulesContract.address);
78+
// Verify the initial rules contract has been registered
79+
result = await nodeIngressContract.getContractAddress(RULES);
80+
assert.equal(result, nodeRulesContract.address, 'Initial contract has NOT been registered correctly');
81+
82+
// Add the Enode to the NodeRules register
83+
result = await nodeRulesContract.addEnode(enode1, node1Host, node1Port);
84+
85+
// Verify that the node is permitted
86+
result = await nodeIngressContract.connectionAllowed(enode1, node1Host, node1Port);
87+
assert.equal(result, true, "Connection SHOULD be allowed after Enodes have been registered");
88+
89+
// create a NEW Rules contract
90+
const rcProxy1 = await NodeRules.new(nodeIngressContract.address, storageContract.address);
91+
// existing rules calls upgrade to change storage owner to the new one
92+
storageContract.upgradeVersion(rcProxy1.address);
93+
94+
// Register the NEW NodeRules contract
7295
await nodeIngressContract.setContractAddress(RULES, rcProxy1.address);
7396

74-
// Verify the initial rules contract has been registered
97+
// Verify the NEW rules contract has been registered
7598
result = await nodeIngressContract.getContractAddress(RULES);
76-
assert.equal(result, rcProxy1.address, 'Initial contract has NOT been registered correctly');
99+
assert.equal(result, rcProxy1.address, 'NEW Rules contract has NOT been registered correctly');
77100

78-
// Verify that the newly registered contract is the initial version
101+
// Verify that the newly registered contract is the correct version
79102
let contract = await NodeRules.at(result);
80103
result = await contract.getContractVersion();
81-
assert.equal(web3.utils.toDecimal(result), 1000000, 'Initial contract is NOT the correct version');
104+
assert.equal(web3.utils.toDecimal(result), 3000000, 'Rules contract is NOT the correct version');
105+
106+
// Verify that the node is permitted
107+
result = await nodeIngressContract.connectionAllowed(enode1, node1Host, node1Port);
108+
assert.equal(result, true, "Connection SHOULD be allowed after Enodes have been registered");
82109

83110
});
84111
});

0 commit comments

Comments
 (0)