Skip to content

Commit

Permalink
packages/core: add write-write conflict test
Browse files Browse the repository at this point in the history
  • Loading branch information
joeltg committed Dec 11, 2024
1 parent 6c2da72 commit 6af7a3e
Showing 1 changed file with 76 additions and 4 deletions.
80 changes: 76 additions & 4 deletions packages/core/test/conflicts.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import test, { ExecutionContext } from "ava"
import * as cbor from "@ipld/dag-cbor"
import { Wallet } from "ethers"

import { SIWESigner } from "@canvas-js/chain-ethereum"
import { Canvas } from "@canvas-js/core"
import { getRecordId } from "@canvas-js/core/utils"
import { assert } from "@canvas-js/utils"

const init = async (t: ExecutionContext) => {
const signer = new SIWESigner()
const signer = new SIWESigner({ signer: Wallet.createRandom() })
const app = await Canvas.initialize({
contract: {
models: {
Expand All @@ -23,12 +24,23 @@ const init = async (t: ExecutionContext) => {
},
async addMember(db, roomId, did) {
const room = await db.get("rooms", roomId)
assert(room !== null && room.admin_did === this.did)
assert(room !== null, "room does not exist")
assert(this.did === room.admin_did, "not authorized to add members")
await db.set("memberships", { id: `${roomId}/${did}` })
},
async removeMember(db, roomId, did) {
const room = await db.get("rooms", roomId)
assert(room !== null, "room does not exist")
assert(did !== room.admin_did, "cannot remove the room admin")
assert(this.did === did || this.did === room.admin_did, "not authorized to remove other members")
await db.delete("memberships", `${roomId}/${did}`)
},
async createPost(db, roomId, content) {
const postId = [this.did, this.id].join("/")
await db.get("memberships", `${roomId}/${this.did}`).then(assert)
await db
.get("memberships", `${roomId}/${this.did}`)
.then((membership) => assert(membership !== null, "not a member"))

await db.set("posts", { id: postId, room_id: roomId, content })
},
},
Expand All @@ -42,7 +54,7 @@ const init = async (t: ExecutionContext) => {
return { app, signer }
}

test("create a record", async (t) => {
test("create a room and post a message", async (t) => {
const { app } = await init(t)

const createRoom = await app.actions.createRoom()
Expand Down Expand Up @@ -108,3 +120,63 @@ test("create a record", async (t) => {
].sort((a, b) => (a.id < b.id ? -1 : 1)),
)
})

test("create a room and add two members concurrently", async (t) => {
const { app: app1, signer: signer1 } = await init(t)
const { app: app2, signer: signer2 } = await init(t)
const { app: app3, signer: signer3 } = await init(t)
const did1 = await signer1.getDid()
const did2 = await signer2.getDid()
const did3 = await signer3.getDid()
t.log("signer1 did:", did1)
t.log("signer2 did:", did2)
t.log("signer3 did:", did3)

const createRoom = await app1.actions.createRoom()
t.log(`applied createRoom ${createRoom.id}`)
const roomId = createRoom.result
const adminDid = createRoom.message.payload.did

await app1.messageLog.serve((snapshot) => app2.messageLog.sync(snapshot))
await app1.messageLog.serve((snapshot) => app3.messageLog.sync(snapshot))

await app2.as(signer1).addMember(roomId, await signer2.getDid())
await app3.as(signer1).addMember(roomId, await signer3.getDid())

await app2.actions.createPost(roomId, "hello")
await app3.actions.createPost(roomId, "world")

t.pass()
})

test("create a room and add the same member twice concurrently (write-write conflict)", async (t) => {
const alice = new SIWESigner({ signer: Wallet.createRandom() })
const aliceDid = await alice.getDid()

const { app: app1, signer: signer1 } = await init(t)
const { app: app2, signer: signer2 } = await init(t)
const { app: app3, signer: signer3 } = await init(t)
const did1 = await signer1.getDid()
const did2 = await signer2.getDid()
const did3 = await signer3.getDid()
t.log("signer1 did:", did1)
t.log("signer2 did:", did2)
t.log("signer3 did:", did3)

const createRoom = await app1.actions.createRoom()
t.log(`applied createRoom ${createRoom.id}`)
const roomId = createRoom.result
const adminDid = createRoom.message.payload.did

await app1.messageLog.serve((snapshot) => app2.messageLog.sync(snapshot))
await app1.messageLog.serve((snapshot) => app3.messageLog.sync(snapshot))

await app2.as(signer1).addMember(roomId, aliceDid)
await app3.as(signer1).addMember(roomId, aliceDid)

// okay now we have conflicting writes to the memberships table
await app2.messageLog.serve((snapshot) => app3.messageLog.sync(snapshot))
await app3.messageLog.serve((snapshot) => app2.messageLog.sync(snapshot))

t.pass()
})

0 comments on commit 6af7a3e

Please sign in to comment.