Skip to content

Commit 6dd58ec

Browse files
Joonmo Yangmergify[bot]
authored andcommitted
Add test cases for double proposal
1 parent c8d5d4e commit 6dd58ec

File tree

4 files changed

+119
-17
lines changed

4 files changed

+119
-17
lines changed

test/src/e2e.dynval/doubleVote.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,28 @@ describe("Double vote detection", function() {
5252
mock = new Mock("0.0.0.0", aliceNode.port, aliceNode.sdk.networkId);
5353
});
5454

55+
it("Should report if double proposal is detected", async function() {
56+
const termWaiter = setTermTestTimeout(this, { terms: 1 });
57+
58+
const aliceNode = nodes[0];
59+
60+
// Start sending double votes for all the votes
61+
await mock.establishWithoutSync();
62+
mock.startDoubleProposal(betty.privateKey);
63+
await termWaiter.waitForTermPeriods(0.5, 0);
64+
65+
// Stop double voting and check if alice is banned
66+
mock.stopDoubleProposal();
67+
await termWaiter.waitNodeUntilTerm(aliceNode, {
68+
target: 2,
69+
termPeriods: 1
70+
});
71+
const banned = await stake.getBanned(aliceNode.sdk);
72+
expect(banned.map(b => b.toString())).to.include(
73+
betty.platformAddress.toString()
74+
);
75+
});
76+
5577
it("Should report if double vote for prevote is detected", async function() {
5678
const termWaiter = setTermTestTimeout(this, { terms: 1 });
5779

test/src/helper/mock/index.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,18 @@
1616
import { blake256, getPublicFromPrivate, H160, H256, recoverSchnorr, SchnorrSignature, signSchnorr, U256, U64 } from "codechain-primitives";
1717
import { SignedTransaction } from "codechain-sdk/lib/core/SignedTransaction";
1818
import * as RLP from "rlp";
19+
import { readUIntRLP } from "../rlp";
1920
import { BlockSyncMessage, Emitter, IBodiesq, IHeadersq, MessageType, ResponseMessage } from "./blockSyncMessage";
2021
import { Header } from "./cHeader";
2122
import { P2pLayer } from "./p2pLayer";
22-
import { ConsensusMessage, Emitter as TendermintEmitter, Step as TendermintStep, StepState, TendermintMessage } from "./tendermintMessage";
23+
import {
24+
ConsensusMessage,
25+
Emitter as TendermintEmitter,
26+
ProposalBlock,
27+
Step as TendermintStep,
28+
StepState,
29+
TendermintMessage
30+
} from "./tendermintMessage";
2331
import { TransactionSyncMessage } from "./transactionSyncMessage";
2432

2533

@@ -438,6 +446,74 @@ export class Mock {
438446
TendermintEmitter.removeAllListeners("stepstate");
439447
}
440448

449+
public startDoubleProposal(priv: string) {
450+
const pub = getPublicFromPrivate(priv);
451+
TendermintEmitter.on("proposalblock", (message: ProposalBlock) => {
452+
const digest = (on: ConsensusMessage["messages"][number]["on"]) =>
453+
blake256(
454+
RLP.encode([
455+
[
456+
new U64(on.step.height).toEncodeObject(),
457+
new U64(on.step.view).toEncodeObject(),
458+
new U64(on.step.step).toEncodeObject(),
459+
],
460+
on.blockHash == null ? [] : [on.blockHash.toEncodeObject()],
461+
])
462+
);
463+
464+
const signature: SchnorrSignature = {
465+
r: message.signature.slice(0, 64),
466+
s: message.signature.slice(64),
467+
};
468+
469+
const block: any = RLP.decode(message.message);
470+
const oldOn: Parameters<typeof digest>[0] = {
471+
step: {
472+
height: readUIntRLP(block[0][5]),
473+
view: message.view,
474+
step: TendermintStep.Propose,
475+
},
476+
blockHash: new H256(blake256(RLP.encode(block[0]))),
477+
};
478+
const recovered = recoverSchnorr(digest(oldOn), signature);
479+
if (recovered === pub) {
480+
const newHeader = [
481+
...block[0].slice(0, 6),
482+
new U64(readUIntRLP(block[0][6]) + 1).toEncodeObject(), // timestamp
483+
...block[0].slice(7),
484+
];
485+
const newDigest = digest({
486+
...oldOn,
487+
blockHash: new H256(blake256(RLP.encode(newHeader))),
488+
});
489+
const newSignature = signSchnorr(newDigest, priv);
490+
491+
this.sendTendermintMessage(new TendermintMessage({
492+
type: "proposalblock",
493+
view: message.view,
494+
message: RLP.encode([newHeader, block[1]]),
495+
signature: newSignature.r + newSignature.s,
496+
}));
497+
}
498+
});
499+
TendermintEmitter.on("stepstate", (message: StepState) => {
500+
if (message.voteStep.step === TendermintStep.Propose) {
501+
setTimeout(() => {
502+
this.sendTendermintMessage(new TendermintMessage({
503+
type: "requestproposal",
504+
height: message.voteStep.height,
505+
view: message.voteStep.view,
506+
}));
507+
}, 200)
508+
}
509+
});
510+
}
511+
512+
public stopDoubleProposal() {
513+
TendermintEmitter.removeAllListeners("proposalblock");
514+
TendermintEmitter.removeAllListeners("stepstate");
515+
}
516+
441517
private async waitForBlockSyncMessage(type: MessageType): Promise<{}> {
442518
return new Promise((resolve, reject) => {
443519
switch (type) {

test/src/helper/mock/tendermintMessage.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import { H256, U64 } from "codechain-primitives";
1717
import { EventEmitter } from "events";
1818
import { compressSync, uncompressSync } from "snappy";
1919

20+
import { readOptionalRlp, readUIntRLP } from "../rlp";
21+
2022
const RLP = require("rlp");
2123

2224
export const Emitter = new EventEmitter();
@@ -83,22 +85,6 @@ export interface RequestProposal {
8385

8486
type MessageBody = ConsensusMessage | ProposalBlock | StepState | RequestMessage | RequestProposal;
8587

86-
function readOptionalRlp<T>(bytes: [] | [Buffer], decoder: (b: Buffer) => T) {
87-
if (bytes.length === 0) {
88-
return null;
89-
} else {
90-
return decoder(bytes[0]);
91-
}
92-
}
93-
94-
function readUIntRLP(bytes: Buffer) {
95-
if (bytes.length === 0) {
96-
return 0;
97-
} else {
98-
return bytes.readUIntBE(0, bytes.length);
99-
}
100-
}
101-
10288
export class TendermintMessage {
10389

10490
public static fromBytes(bytes: Buffer): TendermintMessage {

test/src/helper/rlp.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export function readOptionalRlp<T>(
2+
bytes: [] | [Buffer],
3+
decoder: (b: Buffer) => T
4+
) {
5+
if (bytes.length === 0) {
6+
return null;
7+
} else {
8+
return decoder(bytes[0]);
9+
}
10+
}
11+
12+
export function readUIntRLP(bytes: Buffer) {
13+
if (bytes.length === 0) {
14+
return 0;
15+
} else {
16+
return bytes.readUIntBE(0, bytes.length);
17+
}
18+
}

0 commit comments

Comments
 (0)