diff --git a/app/package.json b/app/package.json index cb0aa4c..fa888c5 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,6 @@ "drizzle": "1.4.0", "history": "4.9.0", "ipfs": "0.35.0", - "level": "5.0.1", "lodash.isequal": "4.5.0", "orbit-db": "0.21.0-rc.1", "orbit-db-keystore": "0.2.1", diff --git a/app/src/containers/LoadingContainer.js b/app/src/containers/LoadingContainer.js index c9cddbe..eb7bcf2 100644 --- a/app/src/containers/LoadingContainer.js +++ b/app/src/containers/LoadingContainer.js @@ -17,9 +17,8 @@ class LoadingContainer extends Component {

This browser has no connection to the Ethereum network.

Please make sure that: @@ -34,6 +33,7 @@ class LoadingContainer extends Component {
ethereum_logo

We can't find any Ethereum accounts!

+

Please make sure that MetaMask is unlocked.

@@ -76,6 +76,7 @@ class LoadingContainer extends Component {
orbitdb_logo

Preparing OrbitDB...

+

Please sign the transaction in MetaMask to create the databases.

diff --git a/app/src/containers/PostList.js b/app/src/containers/PostList.js index 6de283f..c2de481 100644 --- a/app/src/containers/PostList.js +++ b/app/src/containers/PostList.js @@ -5,6 +5,7 @@ import { drizzle } from '../index'; import Post from './Post'; import PlaceholderContainer from './PlaceholderContainer'; +import { determineDBAddress } from '../utils/orbitUtils'; const contract = 'Forum'; const getPostMethod = 'getPost'; @@ -16,7 +17,8 @@ class PostList extends Component { this.getBlockchainData = this.getBlockchainData.bind(this); this.state = { - dataKeys: [] + dataKeys: [], + dbAddresses: [] }; } @@ -29,20 +31,31 @@ class PostList extends Component { } getBlockchainData() { - const { dataKeys } = this.state; - const { drizzleStatus, postIDs } = this.props; + const { dataKeys, dbAddresses } = this.state; + const { drizzleStatus, postIDs, contracts } = this.props; if (drizzleStatus.initialized) { const dataKeysShallowCopy = dataKeys.slice(); let fetchingNewData = false; - postIDs.forEach((postID) => { + postIDs.forEach(async (postID) => { if (!dataKeys[postID]) { dataKeysShallowCopy[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall( - postID, + postID ); 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) { @@ -54,7 +67,7 @@ class PostList extends Component { } render() { - const { dataKeys } = this.state; + const { dataKeys, dbAddresses } = this.state; const { postIDs, contracts, focusOnPost, recentToTheTop } = this.props; const posts = postIDs.map((postID, index) => { @@ -62,13 +75,16 @@ class PostList extends Component { if(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 = { - userAddress: fetchedPostData.value[1], - fullOrbitAddress: `/orbitdb/${fetchedPostData.value[0]}/posts`, - userName: fetchedPostData.value[2], - timestamp: fetchedPostData.value[3]*1000, - topicID: fetchedPostData.value[4] + userAddress, + fullOrbitAddress: `/orbitdb/${dbAddress}/posts`, + userName: fetchedPostData.value[1], + timestamp: fetchedPostData.value[2]*1000, + topicID: fetchedPostData.value[3] }; return( Account address: {address} - - OrbitDB: - {orbitDBId ? orbitDBId - : - - - } - TopicsDB: {topicsDBId ? topicsDBId diff --git a/app/src/containers/TopicContainer.js b/app/src/containers/TopicContainer.js index 52fc3bf..5c672d0 100644 --- a/app/src/containers/TopicContainer.js +++ b/app/src/containers/TopicContainer.js @@ -10,6 +10,7 @@ import NewPost from './NewPost'; import FloatingButton from '../components/FloatingButton'; import { setNavBarTitle } from '../redux/actions/userInterfaceActions.js'; +import { determineDBAddress } from '../utils/orbitUtils'; const contract = 'Forum'; const getTopicMethod = 'getTopic'; @@ -95,16 +96,16 @@ class TopicContainer extends Component { } } - async fetchTopicSubject(orbitDBAddress) { + async fetchTopicSubject(userAddress) { const { topicID } = this.state; - const { contracts, user, orbitDB, setNavBarTitle } = this.props; + const { user, orbitDB, setNavBarTitle } = this.props; let orbitData; - if (contracts[contract][getTopicMethod][this.dataKey].value[1] - === user.address) { + if (userAddress === user.address) { orbitData = orbitDB.topicsDB.get(topicID); } else { - const fullAddress = `/orbitdb/${orbitDBAddress}/topics`; + const dbAddress = await determineDBAddress('topics', userAddress); + const fullAddress = `/orbitdb/${dbAddress}/topics`; const store = await orbitDB.orbitdb.keyvalue(fullAddress); await store.load(); @@ -152,7 +153,7 @@ class TopicContainer extends Component { (
{ this.togglePostingState(); }} onPostCreated={() => { this.postCreated(); }} /> diff --git a/app/src/containers/TopicList.js b/app/src/containers/TopicList.js index dbab22a..0db0cd8 100644 --- a/app/src/containers/TopicList.js +++ b/app/src/containers/TopicList.js @@ -5,6 +5,7 @@ import { drizzle } from '../index'; import Topic from './Topic'; import PlaceholderContainer from './PlaceholderContainer'; +import { determineDBAddress } from '../utils/orbitUtils'; const contract = 'Forum'; const getTopicMethod = 'getTopic'; @@ -16,7 +17,8 @@ class TopicList extends Component { this.getBlockchainData = this.getBlockchainData.bind(this); this.state = { - dataKeys: [] + dataKeys: [], + dbAddresses: [] }; } @@ -29,19 +31,30 @@ class TopicList extends Component { } getBlockchainData() { - const { dataKeys } = this.state; - const { drizzleStatus, topicIDs } = this.props; + const { dataKeys, dbAddresses } = this.state; + const { drizzleStatus, topicIDs, contracts } = this.props; if (drizzleStatus.initialized) { const dataKeysShallowCopy = dataKeys.slice(); let fetchingNewData = false; - topicIDs.forEach((topicID) => { + topicIDs.forEach(async (topicID) => { if (!dataKeys[topicID]) { dataKeysShallowCopy[topicID] = drizzle.contracts[contract].methods[getTopicMethod] .cacheCall(topicID); 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) { @@ -53,7 +66,7 @@ class TopicList extends Component { } render() { - const { dataKeys } = this.state; + const { dataKeys, dbAddresses } = this.state; const { topicIDs, contracts } = this.props; const topics = topicIDs.map(topicID => { @@ -61,13 +74,15 @@ class TopicList extends Component { if(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 = { - userAddress: fetchedTopicData.value[1], - fullOrbitAddress: `/orbitdb/${fetchedTopicData.value[0]}/topics`, - userName: fetchedTopicData.value[2], - timestamp: fetchedTopicData.value[3]*1000, - numberOfReplies: fetchedTopicData.value[4].length + userAddress, + fullOrbitAddress: `/orbitdb/${dbAddress}/topics`, + userName: fetchedTopicData.value[1], + timestamp: fetchedTopicData.value[2]*1000, + numberOfReplies: fetchedTopicData.value[3].length }; return( state.orbit); 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 )); - console.debug("Created temporary databases."); } } latestAccount = account; diff --git a/app/src/redux/sagas/transactionsSaga.js b/app/src/redux/sagas/transactionsSaga.js index c7e8373..b69cb72 100644 --- a/app/src/redux/sagas/transactionsSaga.js +++ b/app/src/redux/sagas/transactionsSaga.js @@ -4,17 +4,14 @@ import { drizzle } from '../../index'; import { orbitSagaPut } from '../../utils/orbitUtils'; import { WEB3_UTILS_SAGA_INITIALIZED } from '../actions/web3UtilsActions'; import { CONTRACT_EVENT_FIRED } from './eventSaga'; -import { getCurrentAccount } from './web3UtilsSaga'; const transactionsHistory = Object.create(null); function* initTransaction(action) { - action.transactionDescriptor.params.from = getCurrentAccount(); const dataKey = drizzle.contracts[action.transactionDescriptor.contract] .methods[action.transactionDescriptor.method].cacheSend( ...(action.transactionDescriptor.params) ); - transactionsHistory[dataKey] = action; transactionsHistory[dataKey].state = 'initialized'; } diff --git a/app/src/utils/levelUtils.js b/app/src/utils/levelUtils.js deleted file mode 100644 index b36df30..0000000 --- a/app/src/utils/levelUtils.js +++ /dev/null @@ -1,20 +0,0 @@ -import level from 'level' - -const db = level('./orbitdb/identity/identitykeys'); - -async function getPrivateKey(id){ - try{ - const keyPair = await db.get(id); - return JSON.parse(keyPair).privateKey; - } catch (err) { - if (err && err.notFound) - console.error("LevelDB: Private Key not found!"); - throw err; - } -} - -async function setKeyPair(id, publicKey, privateKey){ - await db.put(id,JSON.stringify({publicKey: publicKey, privateKey: privateKey})); -} - -export { getPrivateKey, setKeyPair } diff --git a/app/src/utils/orbitUtils.js b/app/src/utils/orbitUtils.js index 8b78736..91a2ef7 100644 --- a/app/src/utils/orbitUtils.js +++ b/app/src/utils/orbitUtils.js @@ -2,10 +2,9 @@ import OrbitDB from 'orbit-db'; import Identities from 'orbit-db-identity-provider'; import IPFS from 'ipfs'; 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 EthereumIdentityProvider from './EthereumIdentityProvider'; -import { getPrivateKey, setKeyPair } from './levelUtils'; function initIPFS() { Identities.addIdentityProvider(EthereumIdentityProvider); @@ -23,63 +22,17 @@ function initIPFS() { }); } -async function createTempDatabases() { - console.debug('Creating temporary databases...'); - const ipfs = getIPFS(); - const identity = await Identities.createIdentity({type: 'ethereum'}); - const orbitdb = await OrbitDB.createInstance(ipfs, {identity}); - console.dir(orbitdb) - const topicsDB = await orbitdb.keyvalue('topics'); - const postsDB = await orbitdb.keyvalue('posts'); - return { orbitdb, topicsDB, postsDB }; -} - async function createDatabases() { console.debug('Creating databases...'); - const ipfs = getIPFS(); - const identity = await Identities.createIdentity({type: 'ethereum'}); - const orbitdb = await OrbitDB.createInstance(ipfs, {identity}); - const options = { - // Give write access to ourselves - accessController: { - write: ['*'] - } - }; - const topicsDB = await orbitdb.keyvalue('topics', options); - const postsDB = await orbitdb.keyvalue('posts', options); - const privateKey = await getPrivateKey(identity.id); - - return { - identityId: identity.id, - identityPublicKey: identity.publicKey, - identityPrivateKey: privateKey, - orbitdb: orbitdb, - orbitPublicKey: "eeeee", - orbitPrivateKey: "fffffff", - topicsDB: topicsDB.address.root, - postsDB: postsDB.address.root - }; + const databases = await createDBs(); + console.debug('Databases created successfully.'); + return databases; } -async function loadDatabases(identityId, identityPublicKey, identityPrivateKey, - orbitId, orbitPublicKey, orbitPrivateKey, - topicsDBId, postsDBId) { +async function loadDatabases() { console.debug('Loading databases...'); - const ipfs = getIPFS(); - await setKeyPair(identityId, identityPublicKey, identityPrivateKey); - const identity = await Identities.createIdentity({type: 'ethereum' }); - const orbitdb = await OrbitDB.createInstance(ipfs, {identity}); - - console.dir(orbitdb) + const { orbitdb, topicsDB, postsDB } = await createDBs(); - const topicsDB = await orbitdb.keyvalue('topics') - .catch((error) => console.error(`TopicsDB init error: ${error}`)); - - console.dir(topicsDB) - - const postsDB = await orbitdb.keyvalue('posts') - .catch((error) => console.error(`PostsDB init error: ${error}`)); - console.dir(topicsDB) await topicsDB.load().catch((error) => console.error(`TopicsDB loading error: ${error}`)); await postsDB.load().catch((error) => console.error(`PostsDB loading error: ${error}`)); @@ -101,10 +54,22 @@ async function loadDatabases(identityId, identityPublicKey, identityPrivateKey, 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() { return store.getState().orbit.ipfs; } +function getOrbitDB() { + return store.getState().orbit.orbitdb; +} + async function orbitSagaPut(db, key, value) { await db.put(key, value).catch((error) => console.error(`Orbit put error: ${error}`)); } @@ -122,4 +87,24 @@ async function orbitSagaOpen(orbitdb, address) { 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 +}; diff --git a/contracts/Forum.sol b/contracts/Forum.sol index e6dcba2..a49cfb0 100644 --- a/contracts/Forum.sol +++ b/contracts/Forum.sol @@ -5,7 +5,6 @@ contract Forum { //----------------------------------------USER---------------------------------------- struct User { string username; // TODO: set an upper bound instead of arbitrary string - OrbitDB orbitdb; uint[] topicIDs; // IDs of the topics the user created uint[] postIDs; // IDs of the posts the user created uint timestamp; @@ -18,15 +17,10 @@ contract Forum { event UserSignedUp(string username, address userAddress); event UsernameUpdated(string newName, string oldName,address userAddress); - function signUp(string memory username, string memory orbitIdentityId, - string memory orbitIdentityPublicKey, string memory orbitIdentityPrivateKey, - string memory orbitId, string memory orbitPublicKey, string memory orbitPrivateKey, - string memory orbitTopicsDB, string memory orbitPostsDB) public returns (bool) { + function signUp(string memory username) public returns (bool) { require (!hasUserSignedUp(msg.sender), "User has already signed up."); require(!isUserNameTaken(username), "Username is already taken."); users[msg.sender] = User(username, - OrbitDB(orbitIdentityId, orbitIdentityPublicKey, orbitIdentityPrivateKey, - orbitId, orbitPublicKey, orbitPrivateKey, orbitTopicsDB, orbitPostsDB), new uint[](0), new uint[](0), block.timestamp, true); userAddresses[username] = msg.sender; emit UserSignedUp(username, msg.sender); @@ -78,82 +72,6 @@ contract Forum { 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---------------------------------------- struct Topic { @@ -211,10 +129,10 @@ contract Forum { 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(topicID