Browse Source

Don't store Orbit Data in contract, fixes, up libs

develop
Ezerous 6 years ago
parent
commit
2337b765e6
  1. 9
      app/package.json
  2. 13
      app/src/containers/LoadingContainer.js
  3. 40
      app/src/containers/PostList.js
  4. 48
      app/src/containers/ProfileInformation.js
  5. 15
      app/src/containers/TopicContainer.js
  6. 37
      app/src/containers/TopicList.js
  7. 25
      app/src/containers/UsernameFormContainer.js
  8. 20
      app/src/redux/sagas/orbitSaga.js
  9. 3
      app/src/redux/sagas/transactionsSaga.js
  10. 29
      app/src/utils/EthereumIdentityProvider.js
  11. 95
      app/src/utils/orbitUtils.js
  12. 92
      contracts/Forum.sol

9
app/package.json

@ -10,10 +10,11 @@
"connected-react-router": "6.4.0", "connected-react-router": "6.4.0",
"drizzle": "1.4.0", "drizzle": "1.4.0",
"history": "4.9.0", "history": "4.9.0",
"ipfs": "github:ipfs/js-ipfs#e849dbcab4a313f7ffc1532a389097ee76344067", "ipfs": "0.35.0",
"lodash.isequal": "4.5.0", "lodash.isequal": "4.5.0",
"orbit-db": "0.19.9", "orbit-db": "0.21.0-rc.1",
"orbit-db-keystore": "0.1.0", "orbit-db-keystore": "0.2.1",
"orbit-db-identity-provider": "0.1.0",
"prop-types": "15.7.2", "prop-types": "15.7.2",
"react": "16.8.6", "react": "16.8.6",
"react-content-loader": "4.2.1", "react-content-loader": "4.2.1",
@ -28,7 +29,7 @@
"redux-saga": "0.16.2", "redux-saga": "0.16.2",
"semantic-ui-react": "0.87.1", "semantic-ui-react": "0.87.1",
"uuid": "3.3.2", "uuid": "3.3.2",
"web3": "1.0.0-beta.54" "web3": "1.0.0-beta.55"
}, },
"devDependencies": { "devDependencies": {
"libp2p-websocket-star-rendezvous": "0.3.0" "libp2p-websocket-star-rendezvous": "0.3.0"

13
app/src/containers/LoadingContainer.js

@ -10,16 +10,15 @@ class LoadingContainer extends Component {
render() { render() {
if (this.props.web3.status === 'failed' || !this.props.web3.networkId) { if (this.props.web3.status === 'failed' || !this.props.web3.networkId) {
return ( return (
<main className="container loading-screen"> <main className="loading-screen">
<div className="pure-g"> <div className="pure-g">
<div className="pure-u-1-1"> <div className="pure-u-1-1">
<img src={ethereum_logo} alt="ethereum_logo" className="loading-img"/> <img src={ethereum_logo} alt="ethereum_logo" className="loading-img"/>
<p><strong>This browser has no connection to the Ethereum network.</strong></p> <p><strong>This browser has no connection to the Ethereum network.</strong></p>
Please make sure that: Please make sure that:
<ul> <ul>
<li>You use MetaMask or a dedicated Ethereum browser</li> <li>MetaMask is unlocked and pointed to the correct network</li>
<li>They are pointed to the correct network</li> <li>The app has been granted the right to connect to your account</li>
<li>Your account is unlocked and the app has the rights to access it</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -33,7 +32,8 @@ class LoadingContainer extends Component {
<div> <div>
<div> <div>
<img src={ethereum_logo} alt="ethereum_logo" className="loading-img"/> <img src={ethereum_logo} alt="ethereum_logo" className="loading-img"/>
<p><strong>We can't find any Ethereum accounts!</strong>.</p> <p><strong>We can't find any Ethereum accounts!</strong></p>
<p>Please make sure that MetaMask is unlocked.</p>
</div> </div>
</div> </div>
</main> </main>
@ -76,6 +76,7 @@ class LoadingContainer extends Component {
<div> <div>
<img src={orbitdb_logo} alt="orbitdb_logo" className="loading-img"/> <img src={orbitdb_logo} alt="orbitdb_logo" className="loading-img"/>
<p><strong>Preparing OrbitDB...</strong></p> <p><strong>Preparing OrbitDB...</strong></p>
<p>Please sign the transaction in MetaMask to create the databases.</p>
</div> </div>
</div> </div>
</main> </main>
@ -86,7 +87,7 @@ class LoadingContainer extends Component {
return Children.only(this.props.children); return Children.only(this.props.children);
return( return(
<main className="container loading-screen"> <main className="loading-screen">
<div> <div>
<div> <div>
<img src={logo} alt="app_logo" className="loading-img"/> <img src={logo} alt="app_logo" className="loading-img"/>

40
app/src/containers/PostList.js

@ -5,6 +5,7 @@ import { drizzle } from '../index';
import Post from './Post'; import Post from './Post';
import PlaceholderContainer from './PlaceholderContainer'; import PlaceholderContainer from './PlaceholderContainer';
import { determineDBAddress } from '../utils/orbitUtils';
const contract = 'Forum'; const contract = 'Forum';
const getPostMethod = 'getPost'; const getPostMethod = 'getPost';
@ -16,7 +17,8 @@ class PostList extends Component {
this.getBlockchainData = this.getBlockchainData.bind(this); this.getBlockchainData = this.getBlockchainData.bind(this);
this.state = { this.state = {
dataKeys: [] dataKeys: [],
dbAddresses: []
}; };
} }
@ -29,20 +31,31 @@ class PostList extends Component {
} }
getBlockchainData() { getBlockchainData() {
const { dataKeys } = this.state; const { dataKeys, dbAddresses } = this.state;
const { drizzleStatus, postIDs } = this.props; const { drizzleStatus, postIDs, contracts } = this.props;
if (drizzleStatus.initialized) { if (drizzleStatus.initialized) {
const dataKeysShallowCopy = dataKeys.slice(); const dataKeysShallowCopy = dataKeys.slice();
let fetchingNewData = false; let fetchingNewData = false;
postIDs.forEach((postID) => { postIDs.forEach(async (postID) => {
if (!dataKeys[postID]) { if (!dataKeys[postID]) {
dataKeysShallowCopy[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall( dataKeysShallowCopy[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall(
postID, postID
); );
fetchingNewData = true; fetchingNewData = true;
} }
else if (!dbAddresses[postID]){
const fetchedPostData = contracts[contract][getPostMethod][dataKeys[postID]];
if(fetchedPostData) {
const dbAddress = await determineDBAddress('posts', fetchedPostData.value[0]);
const dbAddressesShallowCopy = dbAddresses.slice();
dbAddressesShallowCopy[postID] = dbAddress;
this.setState({
dbAddresses: dbAddressesShallowCopy
});
}
}
}); });
if (fetchingNewData) { if (fetchingNewData) {
@ -54,7 +67,7 @@ class PostList extends Component {
} }
render() { render() {
const { dataKeys } = this.state; const { dataKeys, dbAddresses } = this.state;
const { postIDs, contracts, focusOnPost, recentToTheTop } = this.props; const { postIDs, contracts, focusOnPost, recentToTheTop } = this.props;
const posts = postIDs.map((postID, index) => { const posts = postIDs.map((postID, index) => {
@ -62,13 +75,16 @@ class PostList extends Component {
if(dataKeys[postID]) if(dataKeys[postID])
fetchedPostData = contracts[contract][getPostMethod][dataKeys[postID]]; fetchedPostData = contracts[contract][getPostMethod][dataKeys[postID]];
if(fetchedPostData) { const dbAddress = dbAddresses[postID];
if(fetchedPostData && dbAddress) {
const userAddress = fetchedPostData.value[0]; //Also works as an Orbit Identity ID
const postData = { const postData = {
userAddress: fetchedPostData.value[1], userAddress,
fullOrbitAddress: `/orbitdb/${fetchedPostData.value[0]}/posts`, fullOrbitAddress: `/orbitdb/${dbAddress}/posts`,
userName: fetchedPostData.value[2], userName: fetchedPostData.value[1],
timestamp: fetchedPostData.value[3]*1000, timestamp: fetchedPostData.value[2]*1000,
topicID: fetchedPostData.value[4] topicID: fetchedPostData.value[3]
}; };
return( return(
<Post <Post

48
app/src/containers/ProfileInformation.js

@ -10,20 +10,13 @@ import ContentLoader from 'react-content-loader';
import UsernameFormContainer from './UsernameFormContainer'; import UsernameFormContainer from './UsernameFormContainer';
import { Table } from 'semantic-ui-react' import { Table } from 'semantic-ui-react'
//TODO: No array needed unless we add more calls
const callsInfo = [ const callsInfo = [
{ {
contract: 'Forum', contract: 'Forum',
method: 'getUserDateOfRegister' method: 'getUserDateOfRegister'
}, { }
contract: 'Forum', ];
method: 'getOrbitDBId'
}, {
contract: 'Forum',
method: 'getOrbitTopicsDB'
}, {
contract: 'Forum',
method: 'getOrbitPostsDB'
}];
class ProfileInformation extends Component { class ProfileInformation extends Component {
constructor(props) { constructor(props) {
@ -50,7 +43,7 @@ class ProfileInformation extends Component {
} }
getBlockchainData() { getBlockchainData() {
const { pageStatus, dateOfRegister, orbitDBId, topicsDBId, postsDBId } = this.state; const { pageStatus, dateOfRegister, topicsDBId, postsDBId } = this.state;
const { drizzleStatus, address, contracts } = this.props; const { drizzleStatus, address, contracts } = this.props;
if (pageStatus === 'initialized' if (pageStatus === 'initialized'
@ -89,37 +82,26 @@ class ProfileInformation extends Component {
}); });
} }
} }
if (orbitDBId === '') {
const transaction = contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
if (transaction) {
this.setState({
orbitDBId: transaction.value
});
}
}
if (topicsDBId === '') { if (topicsDBId === '') {
const transaction = contracts[callsInfo[2].contract][callsInfo[2].method][this.dataKey[2]]; //TODO: can be displayed using determineDBAddress
if (transaction) {
this.setState({ this.setState({
topicsDBId: transaction.value topicsDBId: "TODO"
}); });
}
} }
if (postsDBId === '') { if (postsDBId === '') {
const transaction = contracts[callsInfo[3].contract][callsInfo[3].method][this.dataKey[3]]; //TODO: can be displayed using determineDBAddress
if (transaction) {
this.setState({ this.setState({
postsDBId: transaction.value postsDBId: "TODO"
}); });
} }
} }
} }
}
render() { render() {
const { orbitDBId, topicsDBId, postsDBId, dateOfRegister } = this.state; const { topicsDBId, postsDBId, dateOfRegister } = this.state;
const { avatarUrl, username, address, numberOfTopics, numberOfPosts, self } = this.props; const { avatarUrl, username, address, numberOfTopics, numberOfPosts, self } = this.props;
return ( return (
@ -142,16 +124,6 @@ class ProfileInformation extends Component {
<Table.Cell><strong>Account address:</strong></Table.Cell> <Table.Cell><strong>Account address:</strong></Table.Cell>
<Table.Cell>{address}</Table.Cell> <Table.Cell>{address}</Table.Cell>
</Table.Row> </Table.Row>
<Table.Row>
<Table.Cell><strong>OrbitDB:</strong></Table.Cell>
<Table.Cell>{orbitDBId ? orbitDBId
: <ContentLoader height={5.8} width={300} speed={2}
primaryColor="#b2e8e6" secondaryColor="#00b5ad"
>
<rect x="0" y="0" rx="3" ry="3" width="80" height="5.5" />
</ContentLoader>
}</Table.Cell>
</Table.Row>
<Table.Row> <Table.Row>
<Table.Cell><strong>TopicsDB:</strong></Table.Cell> <Table.Cell><strong>TopicsDB:</strong></Table.Cell>
<Table.Cell>{topicsDBId ? topicsDBId <Table.Cell>{topicsDBId ? topicsDBId

15
app/src/containers/TopicContainer.js

@ -10,6 +10,7 @@ import NewPost from './NewPost';
import FloatingButton from '../components/FloatingButton'; import FloatingButton from '../components/FloatingButton';
import { setNavBarTitle } from '../redux/actions/userInterfaceActions.js'; import { setNavBarTitle } from '../redux/actions/userInterfaceActions.js';
import { determineDBAddress } from '../utils/orbitUtils';
const contract = 'Forum'; const contract = 'Forum';
const getTopicMethod = 'getTopic'; const getTopicMethod = 'getTopic';
@ -95,16 +96,16 @@ class TopicContainer extends Component {
} }
} }
async fetchTopicSubject(orbitDBAddress) { async fetchTopicSubject(userAddress) {
const { topicID } = this.state; const { topicID } = this.state;
const { contracts, user, orbitDB, setNavBarTitle } = this.props; const { user, orbitDB, setNavBarTitle } = this.props;
let orbitData; let orbitData;
if (contracts[contract][getTopicMethod][this.dataKey].value[1] if (userAddress === user.address) {
=== user.address) {
orbitData = orbitDB.topicsDB.get(topicID); orbitData = orbitDB.topicsDB.get(topicID);
} else { } else {
const fullAddress = `/orbitdb/${orbitDBAddress}/topics`; const dbAddress = await determineDBAddress('topics', userAddress);
const fullAddress = `/orbitdb/${dbAddress}/topics`;
const store = await orbitDB.orbitdb.keyvalue(fullAddress); const store = await orbitDB.orbitdb.keyvalue(fullAddress);
await store.load(); await store.load();
@ -152,7 +153,7 @@ class TopicContainer extends Component {
( (
<div> <div>
<PostList <PostList
postIDs={contracts[contract][getTopicMethod][this.dataKey].value[4]} postIDs={contracts[contract][getTopicMethod][this.dataKey].value[3]}
focusOnPost={postFocus focusOnPost={postFocus
? postFocus ? postFocus
: null} : null}
@ -162,7 +163,7 @@ class TopicContainer extends Component {
<NewPost <NewPost
topicID={topicID} topicID={topicID}
subject={topicSubject} subject={topicSubject}
postIndex={contracts[contract][getTopicMethod][this.dataKey].value[4].length} postIndex={contracts[contract][getTopicMethod][this.dataKey].value[3].length}
onCancelClick={() => { this.togglePostingState(); }} onCancelClick={() => { this.togglePostingState(); }}
onPostCreated={() => { this.postCreated(); }} onPostCreated={() => { this.postCreated(); }}
/> />

37
app/src/containers/TopicList.js

@ -5,6 +5,7 @@ import { drizzle } from '../index';
import Topic from './Topic'; import Topic from './Topic';
import PlaceholderContainer from './PlaceholderContainer'; import PlaceholderContainer from './PlaceholderContainer';
import { determineDBAddress } from '../utils/orbitUtils';
const contract = 'Forum'; const contract = 'Forum';
const getTopicMethod = 'getTopic'; const getTopicMethod = 'getTopic';
@ -16,7 +17,8 @@ class TopicList extends Component {
this.getBlockchainData = this.getBlockchainData.bind(this); this.getBlockchainData = this.getBlockchainData.bind(this);
this.state = { this.state = {
dataKeys: [] dataKeys: [],
dbAddresses: []
}; };
} }
@ -29,19 +31,30 @@ class TopicList extends Component {
} }
getBlockchainData() { getBlockchainData() {
const { dataKeys } = this.state; const { dataKeys, dbAddresses } = this.state;
const { drizzleStatus, topicIDs } = this.props; const { drizzleStatus, topicIDs, contracts } = this.props;
if (drizzleStatus.initialized) { if (drizzleStatus.initialized) {
const dataKeysShallowCopy = dataKeys.slice(); const dataKeysShallowCopy = dataKeys.slice();
let fetchingNewData = false; let fetchingNewData = false;
topicIDs.forEach((topicID) => { topicIDs.forEach(async (topicID) => {
if (!dataKeys[topicID]) { if (!dataKeys[topicID]) {
dataKeysShallowCopy[topicID] = drizzle.contracts[contract].methods[getTopicMethod] dataKeysShallowCopy[topicID] = drizzle.contracts[contract].methods[getTopicMethod]
.cacheCall(topicID); .cacheCall(topicID);
fetchingNewData = true; fetchingNewData = true;
} }
else if (!dbAddresses[topicID]){
const fetchedTopicData = contracts[contract][getTopicMethod][dataKeys[topicID]];
if(fetchedTopicData) {
const dbAddress = await determineDBAddress('topics', fetchedTopicData.value[0]);
const dbAddressesShallowCopy = dbAddresses.slice();
dbAddressesShallowCopy[topicID] = dbAddress;
this.setState({
dbAddresses: dbAddressesShallowCopy
});
}
}
}); });
if (fetchingNewData) { if (fetchingNewData) {
@ -53,7 +66,7 @@ class TopicList extends Component {
} }
render() { render() {
const { dataKeys } = this.state; const { dataKeys, dbAddresses } = this.state;
const { topicIDs, contracts } = this.props; const { topicIDs, contracts } = this.props;
const topics = topicIDs.map(topicID => { const topics = topicIDs.map(topicID => {
@ -61,13 +74,15 @@ class TopicList extends Component {
if(dataKeys[topicID]) if(dataKeys[topicID])
fetchedTopicData = contracts[contract][getTopicMethod][dataKeys[topicID]]; fetchedTopicData = contracts[contract][getTopicMethod][dataKeys[topicID]];
if(fetchedTopicData) { const dbAddress = dbAddresses[topicID];
if(fetchedTopicData && dbAddress) {
const userAddress = fetchedTopicData.value[0]; //Also works as an Orbit Identity ID
const topicData = { const topicData = {
userAddress: fetchedTopicData.value[1], userAddress,
fullOrbitAddress: `/orbitdb/${fetchedTopicData.value[0]}/topics`, fullOrbitAddress: `/orbitdb/${dbAddress}/topics`,
userName: fetchedTopicData.value[2], userName: fetchedTopicData.value[1],
timestamp: fetchedTopicData.value[3]*1000, timestamp: fetchedTopicData.value[2]*1000,
numberOfReplies: fetchedTopicData.value[4].length numberOfReplies: fetchedTopicData.value[3].length
}; };
return( return(
<Topic <Topic

25
app/src/containers/UsernameFormContainer.js

@ -79,33 +79,12 @@ class UsernameFormContainer extends Component {
this.setState({ this.setState({
signingUp: true signingUp: true
}); });
const { const { orbitdb,topicsDB,postsDB } = await createDatabases();
identityId,
identityPublicKey,
identityPrivateKey,
orbitdb,
orbitPublicKey,
orbitPrivateKey,
topicsDB,
postsDB
} = await createDatabases();
dispatch( dispatch(
updateDatabases(DATABASES_CREATED, orbitdb, topicsDB, postsDB), updateDatabases(DATABASES_CREATED, orbitdb, topicsDB, postsDB),
); );
this.stackId = drizzle.contracts[contract].methods[signUpMethod].cacheSend( this.stackId = drizzle.contracts[contract].methods[signUpMethod].cacheSend(
...[ ...[usernameInput], { from: account }
usernameInput,
identityId,
identityPublicKey,
identityPrivateKey,
orbitdb.id,
orbitPublicKey,
orbitPrivateKey,
topicsDB,
postsDB
], {
from: account
},
); );
} }
this.setState({ this.setState({

20
app/src/redux/sagas/orbitSaga.js

@ -2,7 +2,7 @@ import { all, call, put, select, take, takeEvery, takeLatest } from 'redux-saga/
import isEqual from 'lodash.isequal'; import isEqual from 'lodash.isequal';
import { forumContract, getCurrentAccount } from './web3UtilsSaga'; import { forumContract, getCurrentAccount } from './web3UtilsSaga';
import { import {
createTempDatabases, createDatabases,
loadDatabases, loadDatabases,
orbitSagaOpen orbitSagaOpen
} from '../../utils/orbitUtils'; } from '../../utils/orbitUtils';
@ -31,26 +31,12 @@ function* getOrbitDBInfo() {
address: account address: account
}); });
if (callResult) { if (callResult) {
const txObj2 = yield call(forumContract.methods.getOrbitIdentityInfo, yield call(loadDatabases);
...[account]);
const orbitIdentityInfo = yield call(txObj2.call, {
address: account
});
const txObj3 = yield call(forumContract.methods.getOrbitDBInfo,
...[account]);
const orbitDBInfo = yield call(txObj3.call, {
address: account
});
yield call(loadDatabases, orbitIdentityInfo[0], orbitIdentityInfo[1],
orbitIdentityInfo[2],
orbitDBInfo[0], orbitDBInfo[1], orbitDBInfo[2], orbitDBInfo[3],
orbitDBInfo[4]);
} else { } else {
const orbit = yield select(state => state.orbit); const orbit = yield select(state => state.orbit);
if(!orbit.ready){ if(!orbit.ready){
const { orbitdb, topicsDB, postsDB } = yield call(createTempDatabases); const { orbitdb, topicsDB, postsDB } = yield call(createDatabases);
yield put(updateDatabases(DATABASES_CREATED, orbitdb, topicsDB, postsDB )); yield put(updateDatabases(DATABASES_CREATED, orbitdb, topicsDB, postsDB ));
console.debug("Created temporary databases.");
} }
} }
latestAccount = account; latestAccount = account;

3
app/src/redux/sagas/transactionsSaga.js

@ -10,9 +10,8 @@ const transactionsHistory = Object.create(null);
function* initTransaction(action) { function* initTransaction(action) {
const dataKey = drizzle.contracts[action.transactionDescriptor.contract] const dataKey = drizzle.contracts[action.transactionDescriptor.contract]
.methods[action.transactionDescriptor.method].cacheSend( .methods[action.transactionDescriptor.method].cacheSend(
...(action.transactionDescriptor.params), ...(action.transactionDescriptor.params)
); );
transactionsHistory[dataKey] = action; transactionsHistory[dataKey] = action;
transactionsHistory[dataKey].state = 'initialized'; transactionsHistory[dataKey].state = 'initialized';
} }

29
app/src/utils/EthereumIdentityProvider.js

@ -0,0 +1,29 @@
import { web3 } from '../redux/sagas/web3UtilsSaga';
class EthereumIdentityProvider {
constructor () {
this.web3 = web3;
}
// Returns the type of the identity provider
static get type () { return 'ethereum' }
// Returns the signer's id
async getId () {
return (await this.web3.eth.getAccounts())[0];
}
// Returns a signature of pubkeysignature
async signIdentity (data) {
const address = await this.getId();
return await this.web3.eth.personal.sign(data,address,""); //Password not required for MetaMask
}
static async verifyIdentity (identity) {
// Verify that identity was signed by the ID
return web3.eth.accounts.recover(identity.publicKey + identity.signatures.id,
identity.signatures.publicKey) === identity.id;
}
}
export default EthereumIdentityProvider;

95
app/src/utils/orbitUtils.js

@ -1,12 +1,13 @@
import OrbitDB from 'orbit-db'; import OrbitDB from 'orbit-db';
import Keystore from 'orbit-db-keystore'; import Identities from 'orbit-db-identity-provider';
import path from 'path';
import IPFS from 'ipfs'; import IPFS from 'ipfs';
import store from '../redux/store'; import store from '../redux/store';
import { DATABASES_LOADED, IPFS_INITIALIZED, updateDatabases } from '../redux/actions/orbitActions'; import { DATABASES_LOADED, IPFS_INITIALIZED, updateDatabases } from '../redux/actions/orbitActions';
import ipfsOptions from '../config/ipfsOptions'; import ipfsOptions from '../config/ipfsOptions';
import EthereumIdentityProvider from './EthereumIdentityProvider';
function initIPFS() { function initIPFS() {
Identities.addIdentityProvider(EthereumIdentityProvider);
const ipfs = new IPFS(ipfsOptions); const ipfs = new IPFS(ipfsOptions);
ipfs.on('error', (error) => console.error(`IPFS error: ${error}`)); ipfs.on('error', (error) => console.error(`IPFS error: ${error}`));
ipfs.on('ready', async () => { ipfs.on('ready', async () => {
@ -21,63 +22,23 @@ function initIPFS() {
}); });
} }
async function createTempDatabases() {
console.debug('Creating temporary databases...');
const ipfs = getIPFS();
const orbitdb = new OrbitDB(ipfs);
const topicsDB = await orbitdb.keyvalue('topics');
const postsDB = await orbitdb.keyvalue('posts');
return { orbitdb, topicsDB, postsDB };
}
async function createDatabases() { async function createDatabases() {
console.debug('Creating databases...'); console.debug('Creating databases...');
const ipfs = getIPFS(); const databases = await createDBs();
const orbitdb = new OrbitDB(ipfs); console.debug('Databases created successfully.');
const topicsDB = await orbitdb.keyvalue('topics'); return databases;
const postsDB = await orbitdb.keyvalue('posts');
const orbitKey = orbitdb.keystore.getKey(orbitdb.id);
return {
identityId: 'Tempus',
identityPublicKey: 'edax',
identityPrivateKey: 'rerum',
orbitdb: orbitdb,
orbitPublicKey: orbitKey.getPublic('hex'),
orbitPrivateKey: orbitKey.getPrivate('hex'),
topicsDB: topicsDB.address.root,
postsDB: postsDB.address.root
};
} }
async function loadDatabases(identityId, identityPublicKey, identityPrivateKey, async function loadDatabases() {
orbitId, orbitPublicKey, orbitPrivateKey, console.debug('Loading databases...');
topicsDBId, postsDBId) { const { orbitdb, topicsDB, postsDB } = await createDBs();
const directory = './orbitdb';
const keystore = Keystore.create(path.join(directory, orbitId, '/keystore'));
keystore._storage.setItem(orbitId, JSON.stringify({
publicKey: orbitPublicKey,
privateKey: orbitPrivateKey
}));
const ipfs = getIPFS();
const orbitdb = new OrbitDB(ipfs, directory,
{
peerId: orbitId, keystore
});
const topicsDB = await orbitdb.keyvalue(`/orbitdb/${topicsDBId}/topics`)
.catch((error) => console.error(`TopicsDB init error: ${error}`));
const postsDB = await orbitdb.keyvalue(`/orbitdb/${postsDBId}/posts`)
.catch((error) => console.error(`PostsDB init error: ${error}`));
await topicsDB.load().catch((error) => console.error(`TopicsDB loading error: ${error}`)); await topicsDB.load().catch((error) => console.error(`TopicsDB loading error: ${error}`));
await postsDB.load().catch((error) => console.error(`PostsDB loading error: ${error}`)); await postsDB.load().catch((error) => console.error(`PostsDB loading error: ${error}`));
//It is possible that we lack our own data and need to replicate them from somewhere else //It is possible that we lack our own data and need to replicate them from somewhere else
topicsDB.events.on('replicate', (address) => { topicsDB.events.on('replicate', (address) => {
console.log(`TopicsDB Replicating (${address}).`); console.log(`TopicsDB replicating (${address}).`);
}); });
topicsDB.events.on('replicated', (address) => { topicsDB.events.on('replicated', (address) => {
console.log(`TopicsDB replicated (${address}).`); console.log(`TopicsDB replicated (${address}).`);
@ -89,14 +50,26 @@ async function loadDatabases(identityId, identityPublicKey, identityPrivateKey,
console.log(`PostsDB replicated (${address}).`); console.log(`PostsDB replicated (${address}).`);
}); });
console.debug('Orbit databases loaded successfully.'); console.debug('Databases loaded successfully.');
store.dispatch(updateDatabases(DATABASES_LOADED, orbitdb, topicsDB, postsDB)); store.dispatch(updateDatabases(DATABASES_LOADED, orbitdb, topicsDB, postsDB));
} }
async function determineDBAddress(name, identityId){
return (await getOrbitDB().determineAddress(name, 'keyvalue', {
accessController: {
write: [identityId]}
}
)).root;
}
function getIPFS() { function getIPFS() {
return store.getState().orbit.ipfs; return store.getState().orbit.ipfs;
} }
function getOrbitDB() {
return store.getState().orbit.orbitdb;
}
async function orbitSagaPut(db, key, value) { async function orbitSagaPut(db, key, value) {
await db.put(key, value).catch((error) => console.error(`Orbit put error: ${error}`)); await db.put(key, value).catch((error) => console.error(`Orbit put error: ${error}`));
} }
@ -114,4 +87,24 @@ async function orbitSagaOpen(orbitdb, address) {
return store; return store;
} }
export { initIPFS, createTempDatabases, createDatabases, loadDatabases, orbitSagaPut, orbitSagaOpen }; async function createDBs(){
const ipfs = getIPFS();
const identity = await Identities.createIdentity({type: 'ethereum'});
const orbitdb = await OrbitDB.createInstance(ipfs, {identity});
const topicsDB = await orbitdb.keyvalue('topics')
.catch((error) => console.error(`TopicsDB init error: ${error}`));
const postsDB = await orbitdb.keyvalue('posts')
.catch((error) => console.error(`PostsDB init error: ${error}`));
return { orbitdb, topicsDB, postsDB };
}
export {
initIPFS,
createDatabases,
loadDatabases,
orbitSagaPut,
orbitSagaOpen,
determineDBAddress
};

92
contracts/Forum.sol

@ -5,7 +5,6 @@ contract Forum {
//----------------------------------------USER---------------------------------------- //----------------------------------------USER----------------------------------------
struct User { struct User {
string username; // TODO: set an upper bound instead of arbitrary string string username; // TODO: set an upper bound instead of arbitrary string
OrbitDB orbitdb;
uint[] topicIDs; // IDs of the topics the user created uint[] topicIDs; // IDs of the topics the user created
uint[] postIDs; // IDs of the posts the user created uint[] postIDs; // IDs of the posts the user created
uint timestamp; uint timestamp;
@ -18,15 +17,10 @@ contract Forum {
event UserSignedUp(string username, address userAddress); event UserSignedUp(string username, address userAddress);
event UsernameUpdated(string newName, string oldName,address userAddress); event UsernameUpdated(string newName, string oldName,address userAddress);
function signUp(string memory username, string memory orbitIdentityId, function signUp(string memory username) public returns (bool) {
string memory orbitIdentityPublicKey, string memory orbitIdentityPrivateKey,
string memory orbitId, string memory orbitPublicKey, string memory orbitPrivateKey,
string memory orbitTopicsDB, string memory orbitPostsDB) public returns (bool) {
require (!hasUserSignedUp(msg.sender), "User has already signed up."); require (!hasUserSignedUp(msg.sender), "User has already signed up.");
require(!isUserNameTaken(username), "Username is already taken."); require(!isUserNameTaken(username), "Username is already taken.");
users[msg.sender] = User(username, users[msg.sender] = User(username,
OrbitDB(orbitIdentityId, orbitIdentityPublicKey, orbitIdentityPrivateKey,
orbitId, orbitPublicKey, orbitPrivateKey, orbitTopicsDB, orbitPostsDB),
new uint[](0), new uint[](0), block.timestamp, true); new uint[](0), new uint[](0), block.timestamp, true);
userAddresses[username] = msg.sender; userAddresses[username] = msg.sender;
emit UserSignedUp(username, msg.sender); emit UserSignedUp(username, msg.sender);
@ -78,82 +72,6 @@ contract Forum {
return users[userAddress].timestamp; return users[userAddress].timestamp;
} }
//----------------------------------------OrbitDB----------------------------------------
// TODO: set upper bounds to strings (instead of being of arbitrary length)
// TODO: not sure if topicsDB//postsDB are actually needed
struct OrbitDB {
string identityId;
string identityPublicKey;
string identityPrivateKey;
string orbitId;
string orbitPublicKey;
string orbitPrivateKey;
string topicsDB;
string postsDB;
}
function getOrbitIdentityId(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.identityId;
}
function getOrbitIdentityPublicKey(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.identityPublicKey;
}
function getOrbitIdentityPrivateKey(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.identityPrivateKey;
}
function getOrbitDBId(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.orbitId;
}
function getOrbitPublicKey(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.orbitPublicKey;
}
//TODO: encrypt using Metamask in the future
function getOrbitPrivateKey(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.orbitPrivateKey;
}
function getOrbitTopicsDB(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.topicsDB;
}
function getOrbitPostsDB(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.postsDB;
}
function getOrbitIdentityInfo(address userAddress) public view returns (string memory, string memory, string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return (
users[userAddress].orbitdb.identityId,
users[userAddress].orbitdb.identityPublicKey,
users[userAddress].orbitdb.identityPrivateKey
);
}
function getOrbitDBInfo(address userAddress) public view returns (string memory, string memory,
string memory, string memory, string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return (
users[userAddress].orbitdb.orbitId,
users[userAddress].orbitdb.orbitPublicKey,
users[userAddress].orbitdb.orbitPrivateKey,
users[userAddress].orbitdb.topicsDB,
users[userAddress].orbitdb.postsDB
);
}
//----------------------------------------POSTING---------------------------------------- //----------------------------------------POSTING----------------------------------------
struct Topic { struct Topic {
@ -211,10 +129,10 @@ contract Forum {
return numTopics; return numTopics;
} }
function getTopic(uint topicID) public view returns (string memory, address, string memory, uint, uint[] memory) { function getTopic(uint topicID) public view returns (address, string memory, uint, uint[] memory) {
//require(hasUserSignedUp(msg.sender)); needed? //require(hasUserSignedUp(msg.sender)); needed?
require(topicID<numTopics); require(topicID<numTopics);
return (getOrbitTopicsDB(topics[topicID].author), return (
topics[topicID].author, topics[topicID].author,
users[topics[topicID].author].username, users[topics[topicID].author].username,
topics[topicID].timestamp, topics[topicID].timestamp,
@ -227,10 +145,10 @@ contract Forum {
return topics[topicID].postIDs; return topics[topicID].postIDs;
} }
function getPost(uint postID) public view returns (string memory, address, string memory, uint, uint) { function getPost(uint postID) public view returns (address, string memory, uint, uint) {
//require(hasUserSignedUp(msg.sender)); needed? //require(hasUserSignedUp(msg.sender)); needed?
require(postID<numPosts); require(postID<numPosts);
return (getOrbitPostsDB(posts[postID].author), return (
posts[postID].author, posts[postID].author,
users[posts[postID].author].username, users[posts[postID].author].username,
posts[postID].timestamp, posts[postID].timestamp,

Loading…
Cancel
Save