Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The previous requests and instances were difficult to use for multi-instruction transactions. Also, they didn't use the new Observable interface given by ByzCoin. This PR adds a v2/ directory with some soon-to-be stable version of Darc and Coin instances. Every instance can now be created as a BehaviorSubject that updates automatically whenever a new block is available. This allows to program a much more reactive programming of the user-interface: instead of polling for new values, the UI can subscribe to the BehaviorSubjects and be updated whenever something changes. In the long run, other contracts should also find their way into byzcoin/v2.
- Loading branch information
Showing
20 changed files
with
1,024 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
3.6.0 - 2020 10 16 | ||
- added a new api for the instances using rxjs observables | ||
- changed the way ByzCoinRPC.getUpdates behaves | ||
|
||
3.5.3 - 2020 09 24 | ||
- remove buffer import in log.ts |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { Log } from "../../src"; | ||
import { ByzCoinRPC, IStorage, LocalCache } from "../../src/byzcoin"; | ||
import { Darc, Rule } from "../../src/darc"; | ||
import { RosterWSConnection } from "../../src/network"; | ||
import { StatusRPC } from "../../src/status"; | ||
import { StatusRequest, StatusResponse } from "../../src/status/proto"; | ||
import { TransactionBuilder } from "../../src/v2/byzcoin"; | ||
import { CoinContract, DarcInst } from "../../src/v2/byzcoin/contracts"; | ||
import { BLOCK_INTERVAL, ROSTER, SIGNER, startConodes, stopConodes } from "../support/conondes"; | ||
|
||
export class BCTest { | ||
|
||
static async singleton(): Promise<BCTest> { | ||
if (BCTest.bct === undefined) { | ||
BCTest.bct = await BCTest.init(); | ||
} else { | ||
await new Promise((resolve) => setTimeout(resolve, 1000)); | ||
} | ||
|
||
return BCTest.bct; | ||
} | ||
private static bct: BCTest | undefined; | ||
|
||
private static async init(): Promise<BCTest> { | ||
Log.lvl = 1; | ||
const roster4 = ROSTER.slice(0, 4); | ||
|
||
let usesDocker = true; | ||
try { | ||
const ws = new RosterWSConnection(roster4, StatusRPC.serviceName); | ||
ws.setParallel(1); | ||
await ws.send(new StatusRequest(), StatusResponse); | ||
Log.warn("Using already running nodes for test!"); | ||
usesDocker = false; | ||
} catch (e) { | ||
await startConodes(); | ||
} | ||
|
||
const cache = new LocalCache(); | ||
const genesis = ByzCoinRPC.makeGenesisDarc([SIGNER], roster4, "initial"); | ||
[CoinContract.ruleFetch, CoinContract.ruleMint, CoinContract.ruleSpawn, CoinContract.ruleStore, | ||
CoinContract.ruleTransfer] | ||
.forEach((rule) => genesis.addIdentity(rule, SIGNER, Rule.OR)); | ||
const rpc = await ByzCoinRPC.newByzCoinRPC(roster4, genesis, BLOCK_INTERVAL, cache); | ||
rpc.setParallel(1); | ||
const tx = new TransactionBuilder(rpc); | ||
const genesisInst = await DarcInst.retrieve(rpc, genesis.getBaseID()); | ||
return new BCTest(cache, genesis, genesisInst, rpc, tx, usesDocker); | ||
} | ||
|
||
private constructor( | ||
public cache: IStorage, | ||
public genesis: Darc, | ||
public genesisInst: DarcInst, | ||
public rpc: ByzCoinRPC, | ||
public tx: TransactionBuilder, | ||
public usesDocker: boolean, | ||
) { | ||
} | ||
|
||
async shutdown() { | ||
if (this.usesDocker) { | ||
return stopConodes(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { Log } from "../../src"; | ||
|
||
export class HistoryObs { | ||
|
||
private readonly entries: string[] = []; | ||
|
||
constructor(private maxWait = 20) {} | ||
|
||
push(...e: string[]) { | ||
this.entries.push(...e); | ||
} | ||
|
||
async resolveInternal(newEntries: string[], complete?: boolean): Promise<void> { | ||
await expectAsync(this.expect(newEntries, true, complete)).toBeResolved(); | ||
} | ||
|
||
async resolve(...newEntries: string[]): Promise<void> { | ||
return this.resolveInternal(newEntries); | ||
} | ||
|
||
async resolveComplete(...newEntries: string[]): Promise<void> { | ||
return this.resolveInternal(newEntries, true); | ||
} | ||
|
||
async resolveAll(newEntries: string[]): Promise<void> { | ||
let found = true; | ||
while (found) { | ||
try { | ||
await this.expect(newEntries, true, false, true); | ||
} catch (e) { | ||
Log.lvl4(e); | ||
found = false; | ||
} | ||
} | ||
} | ||
|
||
async reject(newEntries: string[], complete?: boolean): Promise<void> { | ||
await expectAsync(this.expect(newEntries, false, complete)).toBeRejected(); | ||
} | ||
|
||
async expect(newEntries: string[], succeed: boolean, complete?: boolean, silent?: boolean): Promise<void> { | ||
return new Promise(async (res, rej) => { | ||
try { | ||
for (let i = 0; i < this.maxWait && this.entries.length < newEntries.length; i++) { | ||
if (!silent) { | ||
Log.lvl3("waiting", i, this.entries.length, newEntries.length); | ||
} | ||
await new Promise((resolve) => setTimeout(resolve, 200)); | ||
} | ||
if (!silent) { | ||
if (succeed) { | ||
Log.lvl2("History:", this.entries, "wanted:", newEntries); | ||
} else { | ||
Log.lvl2("Want history:", this.entries, "to fail with:", newEntries); | ||
} | ||
} | ||
if (this.entries.length < newEntries.length) { | ||
throw new Error("not enough entries"); | ||
} | ||
for (const e of newEntries) { | ||
const h = this.entries.splice(0, 1)[0]; | ||
if (e !== h) { | ||
throw new Error(`Got ${h} instead of ${e}`); | ||
} | ||
} | ||
if (complete && this.entries.length !== 0) { | ||
throw new Error(`didn't describe all history: ${this.entries}`); | ||
} | ||
res(); | ||
} catch (e) { | ||
if (succeed) { | ||
if (!silent) { | ||
Log.error(e); | ||
} | ||
} | ||
rej(e); | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"extends": "../tsconfig", | ||
"include": [ | ||
"./**/*" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import Long from "long"; | ||
|
||
import { Log } from "../../src"; | ||
import { CoinContract, CoinInst } from "../../src/v2/byzcoin/contracts"; | ||
import { BCTest } from "../helpers/bctest"; | ||
import { SIGNER } from "../support/conondes"; | ||
import { HistoryObs } from "../support/historyObs"; | ||
|
||
describe("CoinInst should", () => { | ||
const name = Buffer.alloc(32); | ||
|
||
beforeAll(async () => { | ||
name.write("coinName"); | ||
}); | ||
|
||
it("retrieve an instance from byzcoin", async () => { | ||
const {genesisInst, tx, rpc} = await BCTest.singleton(); | ||
const coinID = genesisInst.spawnCoin(tx, name); | ||
await tx.send([[SIGNER]], 10); | ||
const ci = await CoinInst.retrieve(rpc, coinID); | ||
expect(ci.getValue().name.equals(name)).toBeTruthy(); | ||
}); | ||
|
||
it("mint some coins", async () => { | ||
const {genesisInst, tx, rpc} = await BCTest.singleton(); | ||
const coinID = genesisInst.spawnCoin(tx, name); | ||
await tx.send([[SIGNER]], 10); | ||
|
||
const ci = await CoinInst.retrieve(rpc, coinID); | ||
const h = new HistoryObs(); | ||
ci.subscribe((c) => h.push(c.value.toString())); | ||
await h.resolve("0"); | ||
|
||
ci.mint(tx, Long.fromNumber(1e6)); | ||
await tx.send([[SIGNER]], 10); | ||
await h.resolve(1e6.toString()); | ||
}); | ||
|
||
it("transfer coins", async () => { | ||
const {genesisInst, tx, rpc} = await BCTest.singleton(); | ||
|
||
Log.lvl2("Spawning 2 coins"); | ||
const sourceID = genesisInst.spawnCoin(tx, name); | ||
const targetID = genesisInst.spawnCoin(tx, name); | ||
CoinContract.mint(tx, sourceID, Long.fromNumber(1e6)); | ||
CoinContract.transfer(tx, sourceID, targetID, Long.fromNumber(1e5)); | ||
await tx.send([[SIGNER]], 10); | ||
|
||
Log.lvl2("Getting coins and values"); | ||
const target = await CoinInst.retrieve(rpc, targetID); | ||
const hTarget = new HistoryObs(); | ||
target.subscribe((ci) => hTarget.push(ci.value.toString())); | ||
await hTarget.resolve(1e5.toString()); | ||
|
||
Log.lvl2("Transferring some coins from source to target"); | ||
const source = await CoinInst.retrieve(rpc, sourceID); | ||
const hSource = new HistoryObs(); | ||
source.subscribe((ci) => hSource.push(ci.value.toString())); | ||
source.mint(tx, Long.fromNumber(1e6)); | ||
source.transfer(tx, targetID, Long.fromNumber(2e5)); | ||
await tx.send([[SIGNER]], 10); | ||
await hSource.resolve(9e5.toString(), 17e5.toString()); | ||
await hTarget.resolve(3e5.toString()); | ||
|
||
Log.lvl2("Using fetch and store for transfer"); | ||
source.fetch(tx, Long.fromNumber(3e5)); | ||
target.store(tx); | ||
await tx.send([[SIGNER]], 10); | ||
await hSource.resolve(14e5.toString()); | ||
await hTarget.resolve(6e5.toString()); | ||
}, 600000); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { elementAt } from "rxjs/operators"; | ||
|
||
import { DarcInstance } from "../../src/byzcoin/contracts"; | ||
import { Darc, SignerEd25519 } from "../../src/darc"; | ||
import { DarcContract, DarcInst } from "../../src/v2/byzcoin/contracts"; | ||
|
||
import { BCTest } from "../helpers/bctest"; | ||
import { SIGNER } from "../support/conondes"; | ||
import { HistoryObs } from "../support/historyObs"; | ||
|
||
describe("DarcInst should", () => { | ||
it("retrieve an instance from byzcoin", async () => { | ||
const {genesis, rpc} = await BCTest.singleton(); | ||
const dbs = await DarcInst.retrieve(rpc, genesis.getBaseID()); | ||
expect(dbs.getValue().inst.id.equals(genesis.getBaseID())).toBeTruthy(); | ||
}); | ||
|
||
it("update when the darc is updated", async () => { | ||
const {genesis, rpc, tx} = await BCTest.singleton(); | ||
const d = Darc.createBasic([SIGNER], [SIGNER], Buffer.from("new darc")); | ||
await DarcInstance.spawn(rpc, genesis.getBaseID(), [SIGNER], d); | ||
const dbs = await DarcInst.retrieve(rpc, d.getBaseID()); | ||
expect(dbs.getValue().inst.id.equals(d.getBaseID())).toBeTruthy(); | ||
|
||
const newDarc = dbs.pipe(elementAt(1)).toPromise(); | ||
dbs.setDescription(tx, Buffer.from("new description")); | ||
await tx.send([[SIGNER]], 10); | ||
|
||
expect((await newDarc).description).toEqual(Buffer.from("new description")); | ||
}); | ||
|
||
it("update rules", async () => { | ||
const {genesis, rpc, tx} = await BCTest.singleton(); | ||
const newDarc = Darc.createBasic([SIGNER], [SIGNER], Buffer.from("darc1")); | ||
await DarcInstance.spawn(rpc, genesis.getBaseID(), [SIGNER], newDarc); | ||
const dbs = await DarcInst.retrieve(rpc, newDarc.getBaseID()); | ||
const hist = new HistoryObs(); | ||
|
||
// Create updates with the description:#signers:#evolvers | ||
dbs.subscribe((d) => { | ||
const signLen = d.rules.getRule(DarcContract.ruleSign).getIdentities().length; | ||
const evolveLen = d.rules.getRule(DarcContract.ruleEvolve).getIdentities().length; | ||
hist.push(`${d.description.toString()}:${signLen}:${evolveLen}`); | ||
}); | ||
await hist.resolve("darc1:1:1"); | ||
|
||
dbs.setDescription(tx, Buffer.from("darc2")); | ||
await tx.send([[SIGNER]]); | ||
await hist.resolve("darc2:1:1"); | ||
|
||
// Change the evolver and use it to evolve future darcs | ||
const newEvolver = SignerEd25519.random(); | ||
dbs.addToRules(tx, [DarcContract.ruleEvolve, newEvolver]); | ||
await tx.send([[SIGNER]], 10); | ||
await hist.resolve("darc2:1:2"); | ||
|
||
// Add both signer and evolver | ||
const newSigner = SignerEd25519.random(); | ||
const newEvolver2 = SignerEd25519.random(); | ||
dbs.addToRules(tx, [DarcContract.ruleSign, newSigner], [DarcContract.ruleEvolve, newEvolver2]); | ||
await tx.send([[newEvolver]], 10); | ||
await hist.resolve("darc2:2:3"); | ||
|
||
// Remove the old evolver | ||
dbs.rmFromRules(tx, [DarcContract.ruleEvolve, newEvolver]); | ||
await tx.send([[newEvolver2]], 10); | ||
await hist.resolve("darc2:2:2"); | ||
|
||
// Reset the evolver | ||
dbs.setRules(tx, [DarcContract.ruleSign, newSigner], [DarcContract.ruleEvolve, newEvolver2]); | ||
await tx.send([[newEvolver2]], 10); | ||
await hist.resolve("darc2:1:1"); | ||
|
||
// Reset the signer (first add, then set) | ||
dbs.addToRules(tx, [DarcContract.ruleSign, newSigner]); | ||
await tx.send([[newEvolver2]], 10); | ||
await hist.resolve("darc2:2:1"); | ||
|
||
dbs.setRules(tx, [DarcContract.ruleSign, newSigner]); | ||
await tx.send([[newEvolver2]], 10); | ||
await hist.resolve("darc2:1:1"); | ||
expect(dbs.getValue().rules.getRule(DarcContract.ruleEvolve).getIdentities()[0]).toBe(newEvolver2.toString()); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.