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

Refactor create community #2305

Merged
merged 10 commits into from
Apr 4, 2024
Merged

Refactor create community #2305

merged 10 commits into from
Apr 4, 2024

Conversation

leblowl
Copy link
Collaborator

@leblowl leblowl commented Feb 19, 2024

This PR migrates the Community model to the backend so that the backend is now the source of truth for Community data. In order to accomplish this, for existing communities (existing Quiet install), we move (migrate) the data from the frontend to the backend and then make sure all operations that previously affected the frontend Community state are done on the backend. For new communities (new Quiet install), some of the logic for creating/joining has been moved to the backend and the event-flow is simplified. Whenever the Community model is updated on the backend, the backend now emits a COMMUNITY_UPDATED event so that the frontend can synchronize its Community state.

Solution for: #2329

Pull Request Checklist

  • I have linked this PR to a related GitHub issue.
  • I have added a description of the change (and Github issue number, if any) to the root CHANGELOG.md.

(Optional) Mobile checklist

Please ensure you completed the following checks if you did any changes to the mobile package:

  • I have run e2e tests for mobile
  • I have updated base screenshots for visual regression tests

@leblowl leblowl force-pushed the refactor-create-community branch 6 times, most recently from 156fe39 to 5d86954 Compare February 23, 2024 23:11
@leblowl leblowl force-pushed the refactor-create-community branch 14 times, most recently from a8db4ee to 5bd37ad Compare March 8, 2024 01:04
} else {
this.logger('Nothing to migrate')
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Since we are moving the Community model to the backend, new communities created after this code is released will have the Community model in the backend, however existing communities that upgrade Quiet need to have their community data migrated - that's what this function is for.

await this.localDbService.put(LocalDBKeys.PSK, pskBase64)
this.logger('Generated Libp2p PSK')
this.serverIoProvider.io.emit(SocketActionTypes.LIBP2P_PSK_STORED, { psk: pskBase64 })
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We store the psk in the Community object, so there is no more need for the PSK key in LevelDB. We also return the entire Community object from createCommunity so there is no more need for the LIBP2P_PSK_STORED event.

} catch (e) {
this.logger.error('Failed to register owner certificate')
return
}
Copy link
Collaborator Author

@leblowl leblowl Mar 8, 2024

Choose a reason for hiding this comment

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

We register the owner certificate in createCommunity since that's how it was done before in sagas. We previously called REGISTER_OWNER_CERTIFICATE and then when that completed, we called CREATED_COMMUNITY. Now we do it all together. However, we may want to decouple the owner cert from the community in future work.

public async launchCommunity(payload: InitCommunityPayload) {
this.logger('Launching community: peers:', payload.peers)
this.communityState = ServiceState.LAUNCHING
public async joinCommunity(payload: InitCommunityPayload): Promise<Community | undefined> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

joinCommunity is a new function so that we can focus on validation and creating a Community object, while launchCommunity is still used but it's only for launching an already-validated community.

emitError(this.serverIoProvider.io, payload)
})
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not needed anymore since we call to the registration service directly and await on its return

...community,
ownerOrbitDbIdentity: meta.ownerOrbitDbIdentity,
})
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Update Community object on backend when setting CommunityMetadata. This is the first time that ownerOrbitDbIdentity is available. In the future, we can do this all when creating the community and remove this SET_COMMUNITY_METADATA event.

async (args: RegisterOwnerCertificatePayload) => {
await this.registrationService.registerOwnerCertificate(args)
}
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No longer required - we do this in createCommunity

@@ -609,8 +724,8 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
this.storageService.on(StorageEvents.MESSAGE_MEDIA_UPDATED, (payload: FileMetadata) => {
this.serverIoProvider.io.emit(SocketActionTypes.MESSAGE_MEDIA_UPDATED, payload)
})
this.storageService.on(StorageEvents.UPDATE_PEERS_LIST, (payload: StorePeerListPayload) => {
this.serverIoProvider.io.emit(SocketActionTypes.PEER_LIST, payload)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Instead of sending an event for a single piece of data (e.g. peerList), we send a general event (COMMUNITY_UPDATED) anytime the backend updates the Community model so that the frontend can replicate the state.

await this.localDbService.setCommunity(updatedCommunity)

this.serverIoProvider.io.emit(SocketActionTypes.COMMUNITY_UPDATED, updatedCommunity)
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

When another peer receives the CommunityMetadata from OrbitDB, they update their Community object in LevelDB and then notify the frontend via COMMUNITY_UPDATED.

COMMUNITIES = 'communities',
// ID of current community
CURRENT_COMMUNITY_ID = 'currentCommunityId',
// Record of peer details
PEERS = 'peers',
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Backend LevelDB state mirrors the way things have been organized in Redux

message: ErrorMessages.REGISTRATION_FAILED,
community: payload.communityId,
})
throw new Error('Failed to register owner certificate')
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removing events since we can call this directly and await it

}

yield* apply(socket, socket.emit, applyEmitParams(SocketActionTypes.LOAD_MIGRATION_DATA, data))
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Saga for sending frontend data to the backend in order to migrate existing communities when upgrading Quiet.

// community creation from CSR/certificate creation and create the community
// first and then add the owner's CSR and issue their certificate.
yield* put(identityActions.saveUserCsr())
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I renamed savedOwnerCertificate.saga.ts to createCommunity.saga.ts and modified it a bit. We generate the owner's certificate when creating the community now and just call updateCommunityData with the returned Community object.

if (psk) {
console.log('create network saga: saving PSK')
yield* put(communitiesActions.savePSK(psk))
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No need for this anymore, otherwise just reorganizing things a bit.

ownerCertificate: action.payload.ownerCertificate,
})
)
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Now when CommunityMetadata is received from OrbitDB, we update the Community on the backend and then emit a COMMUNITY_UPDATED event. This isn't needed anymore.

}

yield* put(communitiesActions.updateCommunityData(payload))
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Now when CommunityMetadata is received from OrbitDB, we update the Community on the backend and then emit a COMMUNITY_UPDATED event. This isn't needed anymore.


if (meta) {
yield* put(communitiesActions.saveCommunityMetadata(meta))
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is also replaced by a COMMUNITY_UPDATED event. Going forward, we can remove this saga entirely.

}

yield* apply(socket, socket.emit, applyEmitParams(SocketActionTypes.CREATE_COMMUNITY, payload))
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was renamed to createCommunity.saga.ts

// all the backend services up and running and then save the
// CSR, register the owner's certificate and set community
// metadata.
emit(identityActions.saveUserCsr())
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Since we are using an acknowledgement for the CREATE_COMMUNITY event, this was moved into createCommunitySaga

@@ -11,8 +11,6 @@ export interface Community {
rootCa?: string
peerList?: string[]
onionAddress?: string
privateKey?: string
port?: number
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Apparently unused

peerId: PeerId
hiddenService: HiddenService
certs?: Certificates
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also apparently unused

@leblowl leblowl force-pushed the refactor-create-community branch from 454011d to bc0510b Compare March 8, 2024 04:57
@leblowl leblowl marked this pull request as ready for review March 8, 2024 04:57
@leblowl leblowl requested review from EmiM and siepra March 8, 2024 05:19

// Only require migration data for existing communities. We can tell because
// they are using the deprecated COMMUNITY key in LevelDB. This is related
// to a specific migration. Perhaps we want a more general purpose migration
Copy link
Contributor

Choose a reason for hiding this comment

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

That's definitely a good idea especially that leveldb will be storing more data from now on. We can get inspiration from Django migrations files or any other migration mechanism.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yea I was thinking about something like that

await this.tor.destroyHiddenService(hiddenService.onionAddress.split('.')[0])

// TODO: Do we want to create the PeerId here? It doesn't necessarily have
Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose it was added here because it's needed to create full peer's p2p address in order for peer to join the network. So it's kind of related. But we don't have to do that here.

const communityName = getCertFieldValue(rootCaCert, CertFieldsTypes.commonName)

if (!communityName) {
this.logger.error(`Could not retrieve ${CertFieldsTypes.commonName} from CommunityMetadata.rootCa`)
Copy link
Contributor

Choose a reason for hiding this comment

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

If rootCa doesn't have commonName there may be something wrong with rootCa. Are we sure that we still want to update community metadata in this case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This code runs after community metadata has been updated. I think I mostly copied it from sagas. I think we can definitely improve things. My feeling is we should probably validate the rootCA somewhere when it's first created and then trust it from then on.

async (payload: CommunityMetadata, callback: (response?: CommunityMetadata) => void) => {
const meta = await this.storageService?.updateCommunityMetadata(payload)
async (payload: CommunityMetadata, callback: (response: CommunityMetadata | undefined) => void) => {
const meta = await this.storageService.updateCommunityMetadata(payload)
Copy link
Contributor

Choose a reason for hiding this comment

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

I see that you added throwing exception in updateCommunityMetadata. Maybe we should catch it here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Where is the exception? I think I'm missing it.


jest.setTimeout(20_000)

describe('General channel', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it deleted?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this is fully tested in community.create and community.join tests

@leblowl leblowl force-pushed the refactor-create-community branch from bc0510b to 2227744 Compare March 27, 2024 18:55
@leblowl leblowl merged commit 29c1c1b into develop Apr 4, 2024
26 of 31 checks passed
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.

2 participants