Skip to content

Commit b2a8430

Browse files
feat: add BlsPublicKeyRegistration transaction (#4830)
* setup initial BlsPublicKeyRegistration transaction type * create BlsPublicKeyRegistration handler and index * Set bls public key on bootstrap * Fix data type * Rename index * Remove NotSupportedForSecondSignatureWalletError * Remove NotSupportedForMultiSignatureWalletError * Remove TODO * Add blsPublicKey schema * Use reference in transaction schema * Serialize * Add initial test for bls-public-key-registartion * Test throwIfCannotBeApplied * Test throwIfCannotEnterPool * onPoolEnter/Leave * Apply and revert test * Directly use indexes * Support bls key swap * Fix existing tests * Test swap scenarios * Support event emits * Enable on blsPublicKeyRegistrationEnabled * Typo * Builder tests * Fix unit tests * Add basic functional test * Setup CI * Update @arkecosystem/crypto-networks * Check bls public key * Add extra tests * Rename attribute to delegate.blsPublicKey * Update @arkecosystem/crypto-networks --------- Co-authored-by: oXtxNt9U <120286271+oXtxNt9U@users.noreply.github.com>
1 parent da69e7f commit b2a8430

File tree

37 files changed

+1688
-19
lines changed

37 files changed

+1688
-19
lines changed

.github/workflows/functional.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,61 @@ jobs:
511511
name: ${{ github.job }}-coverage
512512
path: .coverage/functional/${{ github.job }}/lcov.info
513513

514+
bls-public-key-registration-resignation:
515+
runs-on: ubuntu-latest
516+
517+
services:
518+
postgres:
519+
image: postgres:12
520+
env:
521+
POSTGRES_USER: ark
522+
POSTGRES_PASSWORD: password
523+
POSTGRES_DB: ark_unitnet
524+
ports:
525+
- 5432:5432
526+
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
527+
528+
strategy:
529+
matrix:
530+
node-version: [16.x]
531+
532+
steps:
533+
- uses: actions/checkout@v4
534+
- name: Cache node modules
535+
uses: actions/cache@v4
536+
with:
537+
path: node_modules
538+
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
539+
restore-keys: ${{ runner.os }}-node-
540+
- name: Use Node.js ${{ matrix.node-version }}
541+
uses: actions/setup-node@v1
542+
with:
543+
node-version: ${{ matrix.node-version }}
544+
545+
- name: Update system
546+
run: sudo apt-get update -y
547+
548+
- name: Install xsel & postgresql-client
549+
run: sudo apt-get install -q xsel postgresql-client
550+
551+
- name: Install and build packages
552+
run: yarn setup
553+
554+
- name: Create .core/database directory
555+
run: mkdir -p $HOME/.core/database
556+
557+
- name: Functional tests
558+
run: yarn test __tests__/functional/transaction-forging/bls-public-key-registration.test.ts --coverage --coverageDirectory .coverage/functional/${{ github.job }}
559+
env:
560+
CORE_DB_DATABASE: ark_unitnet
561+
CORE_DB_USERNAME: ark
562+
563+
- name: Archive code coverage
564+
uses: actions/upload-artifact@v4
565+
with:
566+
name: ${{ github.job }}-coverage
567+
path: .coverage/functional/${{ github.job }}/lcov.info
568+
514569
htlc-claim:
515570
runs-on: ubuntu-latest
516571

__tests__/functional/transaction-forging/__support__/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@ export const setUp = async (): Promise<Contracts.Kernel.Application> => {
5858

5959
Managers.configManager.getMilestone().aip11 = false;
6060
Managers.configManager.getMilestone().htlcEnabled = false;
61+
Managers.configManager.getMilestone().blsPublicKeyRegistrationEnabled = false;
6162

6263
await app.boot();
6364

6465
Managers.configManager.getMilestone().aip11 = true;
6566
Managers.configManager.getMilestone().htlcEnabled = true;
67+
Managers.configManager.getMilestone().blsPublicKeyRegistrationEnabled = true;
6668
});
6769

6870
return sandbox.app;
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import "@packages/core-test-framework/src/matchers";
2+
3+
import { Contracts } from "@arkecosystem/core-kernel";
4+
import { Identities } from "@arkecosystem/crypto";
5+
import secrets from "@packages/core-test-framework/src/internal/passphrases.json";
6+
import { snoozeForBlock, TransactionFactory } from "@packages/core-test-framework/src/utils";
7+
import { generateMnemonic } from "bip39";
8+
9+
import * as support from "./__support__";
10+
11+
const genesisPassphrase: string = secrets[0];
12+
13+
let app: Contracts.Kernel.Application;
14+
beforeAll(async () => (app = await support.setUp()));
15+
afterAll(async () => await support.tearDown());
16+
17+
describe("Transaction Forging - Bls Public Key Registration", () => {
18+
describe("Signed with 1 Passphase", () => {
19+
it("should broadcast, accept and forge it", async () => {
20+
// Prepare a fresh wallet for the tests
21+
const passphrase = generateMnemonic();
22+
23+
// Initial Funds
24+
const initialFunds = TransactionFactory.initialize(app)
25+
.transfer(Identities.Address.fromPassphrase(passphrase), 100 * 1e8)
26+
.withPassphrase(genesisPassphrase)
27+
.createOne();
28+
29+
await expect(initialFunds).toBeAccepted();
30+
await snoozeForBlock(1);
31+
await expect(initialFunds.id).toBeForged();
32+
33+
// Register a delegate
34+
const transactionsRegister = TransactionFactory.initialize(app)
35+
.delegateRegistration()
36+
.withPassphrase(passphrase)
37+
.createOne();
38+
39+
await expect(transactionsRegister).toBeAccepted();
40+
await snoozeForBlock(1);
41+
await expect(transactionsRegister.id).toBeForged();
42+
43+
// Register bls public key
44+
const transactionsRegisterFirst = TransactionFactory.initialize(app)
45+
.blsPublicKeyRegistration({
46+
newBlsPublicKey: "a".repeat(96),
47+
})
48+
.withPassphrase(passphrase)
49+
.createOne();
50+
51+
await expect(transactionsRegisterFirst).toBeAccepted();
52+
await snoozeForBlock(1);
53+
await expect(transactionsRegisterFirst.id).toBeForged();
54+
await expect(transactionsRegisterFirst).blsPublicKeyRegistered();
55+
56+
// Overwrite bls public key
57+
const transactionsRegisterSecond = TransactionFactory.initialize(app)
58+
.blsPublicKeyRegistration({
59+
oldBlsPublicKey: "a".repeat(96),
60+
newBlsPublicKey: "b".repeat(96),
61+
})
62+
.withPassphrase(passphrase)
63+
.createOne();
64+
65+
await expect(transactionsRegisterSecond).toBeAccepted();
66+
await snoozeForBlock(1);
67+
await expect(transactionsRegisterSecond.id).toBeForged();
68+
await expect(transactionsRegisterSecond).blsPublicKeyRegistered();
69+
});
70+
71+
it("should reject if bls key is already registered", async () => {
72+
// Prepare a fresh wallet for the tests
73+
const passphrase = generateMnemonic();
74+
75+
// Initial Funds
76+
const initialFunds = TransactionFactory.initialize(app)
77+
.transfer(Identities.Address.fromPassphrase(passphrase), 100 * 1e8)
78+
.withPassphrase(genesisPassphrase)
79+
.createOne();
80+
81+
await expect(initialFunds).toBeAccepted();
82+
await snoozeForBlock(1);
83+
await expect(initialFunds.id).toBeForged();
84+
85+
// Register a delegate
86+
const transactionsRegister = TransactionFactory.initialize(app)
87+
.delegateRegistration()
88+
.withPassphrase(passphrase)
89+
.createOne();
90+
91+
await expect(transactionsRegister).toBeAccepted();
92+
await snoozeForBlock(1);
93+
await expect(transactionsRegister.id).toBeForged();
94+
95+
// Register bls public key
96+
const transactionsRegisterFirst = TransactionFactory.initialize(app)
97+
.blsPublicKeyRegistration({
98+
newBlsPublicKey: "b".repeat(96),
99+
})
100+
.withPassphrase(passphrase)
101+
.createOne();
102+
103+
await expect(transactionsRegisterFirst).toBeRejected();
104+
await snoozeForBlock(1);
105+
await expect(transactionsRegisterFirst.id).not.toBeForged();
106+
await expect(transactionsRegisterFirst).not.blsPublicKeyRegistered();
107+
});
108+
109+
it("should reject if delegate is resiged", async () => {
110+
// Prepare a fresh wallet for the tests
111+
const passphrase = generateMnemonic();
112+
113+
// Initial Funds
114+
const initialFunds = TransactionFactory.initialize(app)
115+
.transfer(Identities.Address.fromPassphrase(passphrase), 100 * 1e8)
116+
.withPassphrase(genesisPassphrase)
117+
.createOne();
118+
119+
await expect(initialFunds).toBeAccepted();
120+
await snoozeForBlock(1);
121+
await expect(initialFunds.id).toBeForged();
122+
123+
// Register a delegate
124+
const transactionsRegister = TransactionFactory.initialize(app)
125+
.delegateRegistration()
126+
.withPassphrase(passphrase)
127+
.createOne();
128+
129+
await expect(transactionsRegister).toBeAccepted();
130+
await snoozeForBlock(1);
131+
await expect(transactionsRegister.id).toBeForged();
132+
133+
// Resign a delegate
134+
const transactionsResign = TransactionFactory.initialize(app)
135+
.delegateResignation()
136+
.withPassphrase(passphrase)
137+
.createOne();
138+
139+
await expect(transactionsResign).toBeAccepted();
140+
await snoozeForBlock(1);
141+
await expect(transactionsResign.id).toBeForged();
142+
143+
// Register bls public key
144+
const transactionsRegisterFirst = TransactionFactory.initialize(app)
145+
.blsPublicKeyRegistration({
146+
newBlsPublicKey: "f".repeat(96),
147+
})
148+
.withPassphrase(passphrase)
149+
.createOne();
150+
151+
await expect(transactionsRegisterFirst).toBeRejected();
152+
await snoozeForBlock(1);
153+
await expect(transactionsRegisterFirst.id).not.toBeForged();
154+
await expect(transactionsRegisterFirst).not.blsPublicKeyRegistered();
155+
});
156+
157+
it("should reject if not from delegate", async () => {
158+
// Prepare a fresh wallet for the tests
159+
const passphrase = generateMnemonic();
160+
161+
// Initial Funds
162+
const initialFunds = TransactionFactory.initialize(app)
163+
.transfer(Identities.Address.fromPassphrase(passphrase), 100 * 1e8)
164+
.withPassphrase(genesisPassphrase)
165+
.createOne();
166+
167+
await expect(initialFunds).toBeAccepted();
168+
await snoozeForBlock(1);
169+
await expect(initialFunds.id).toBeForged();
170+
171+
// Register bls public key
172+
const transactionsRegisterFirst = TransactionFactory.initialize(app)
173+
.blsPublicKeyRegistration({
174+
newBlsPublicKey: "g".repeat(96),
175+
})
176+
.withPassphrase(passphrase)
177+
.createOne();
178+
179+
await expect(transactionsRegisterFirst).toBeRejected();
180+
await snoozeForBlock(1);
181+
await expect(transactionsRegisterFirst.id).not.toBeForged();
182+
await expect(transactionsRegisterFirst).not.blsPublicKeyRegistered();
183+
});
184+
});
185+
186+
describe("Signed with 2 Passphases", () => {
187+
it("should broadcast, accept and forge it", async () => {
188+
// Prepare a fresh wallet for the tests
189+
const passphrase = generateMnemonic();
190+
const secondPassphrase = generateMnemonic();
191+
192+
// Initial Funds
193+
const initialFunds = TransactionFactory.initialize(app)
194+
.transfer(Identities.Address.fromPassphrase(passphrase), 100 * 1e8)
195+
.withPassphrase(genesisPassphrase)
196+
.createOne();
197+
198+
await expect(initialFunds).toBeAccepted();
199+
await snoozeForBlock(1);
200+
await expect(initialFunds.id).toBeForged();
201+
202+
// Register a second passphrase
203+
const secondSignature = TransactionFactory.initialize(app)
204+
.secondSignature(secondPassphrase)
205+
.withPassphrase(passphrase)
206+
.createOne();
207+
208+
await expect(secondSignature).toBeAccepted();
209+
await snoozeForBlock(1);
210+
await expect(secondSignature.id).toBeForged();
211+
212+
// Register a delegate
213+
const transactionsRegister = TransactionFactory.initialize(app)
214+
.delegateRegistration()
215+
.withPassphrasePair({ passphrase, secondPassphrase })
216+
.createOne();
217+
218+
await expect(transactionsRegister).toBeAccepted();
219+
await snoozeForBlock(1);
220+
await expect(transactionsRegister.id).toBeForged();
221+
222+
// Register bls public key
223+
const transactionsRegisterFirst = TransactionFactory.initialize(app)
224+
.blsPublicKeyRegistration({
225+
newBlsPublicKey: "c".repeat(96),
226+
})
227+
.withPassphrasePair({ passphrase, secondPassphrase })
228+
.createOne();
229+
230+
await expect(transactionsRegisterFirst).toBeAccepted();
231+
await snoozeForBlock(1);
232+
await expect(transactionsRegisterFirst.id).toBeForged();
233+
await expect(transactionsRegisterFirst).blsPublicKeyRegistered();
234+
235+
// Register bls public key
236+
const transactionsRegisterSecond = TransactionFactory.initialize(app)
237+
.blsPublicKeyRegistration({
238+
oldBlsPublicKey: "c".repeat(96),
239+
newBlsPublicKey: "d".repeat(96),
240+
})
241+
.withPassphrasePair({ passphrase, secondPassphrase })
242+
.createOne();
243+
244+
await expect(transactionsRegisterSecond).toBeAccepted();
245+
await snoozeForBlock(1);
246+
await expect(transactionsRegisterSecond.id).toBeForged();
247+
await expect(transactionsRegisterSecond).blsPublicKeyRegistered();
248+
});
249+
});
250+
});

__tests__/unit/core-api/__support__/app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export const initApp = (): Application => {
106106
app.bind(Identifiers.TransactionHandler).to(Two.HtlcLockTransactionHandler);
107107
app.bind(Identifiers.TransactionHandler).to(Two.HtlcClaimTransactionHandler);
108108
app.bind(Identifiers.TransactionHandler).to(Two.HtlcRefundTransactionHandler);
109+
app.bind(Identifiers.TransactionHandler).to(Two.BlsPublicKeyRegistrationTransactionHandler);
109110

110111
app.bind(Identifiers.TransactionHandlerProvider).to(TransactionHandlerProvider).inSingletonScope();
111112
app.bind(Identifiers.TransactionHandlerRegistry).to(TransactionHandlerRegistry).inSingletonScope();

__tests__/unit/core-api/controllers/transactions.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ describe("TransactionsController", () => {
316316
it("should return registered schemas", async () => {
317317
const response = (await controller.schemas(undefined, undefined)) as ItemResponse;
318318

319-
const coreTransactionHandlersCount = 11;
319+
const coreTransactionHandlersCount = 12;
320320
expect(Object.keys(response.data["1"]).length).toBe(coreTransactionHandlersCount);
321321
});
322322
});

__tests__/unit/core-state/wallets/wallet-repository-copy-on-write.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Contracts } from "@packages/core-kernel";
44
import { Wallet, WalletRepository, WalletRepositoryCopyOnWrite } from "@packages/core-state/src/wallets";
55
import {
66
addressesIndexer,
7+
blsPublicKeysIndexer,
78
ipfsIndexer,
89
locksIndexer,
910
publicKeysIndexer,
@@ -36,14 +37,15 @@ describe("Wallet Repository Copy On Write", () => {
3637
});
3738

3839
it("should be able to look up indexers", () => {
39-
const expected = ["addresses", "publicKeys", "usernames", "resignations", "locks", "ipfs"];
40+
const expected = ["addresses", "publicKeys", "usernames", "resignations", "locks", "ipfs", "blsPublicKeys"];
4041
expect(walletRepoCopyOnWrite.getIndexNames()).toEqual(expected);
4142
expect(walletRepoCopyOnWrite.getIndex("addresses").indexer).toEqual(addressesIndexer);
4243
expect(walletRepoCopyOnWrite.getIndex("publicKeys").indexer).toEqual(publicKeysIndexer);
4344
expect(walletRepoCopyOnWrite.getIndex("usernames").indexer).toEqual(usernamesIndexer);
4445
expect(walletRepoCopyOnWrite.getIndex("resignations").indexer).toEqual(resignationsIndexer);
4546
expect(walletRepoCopyOnWrite.getIndex("locks").indexer).toEqual(locksIndexer);
4647
expect(walletRepoCopyOnWrite.getIndex("ipfs").indexer).toEqual(ipfsIndexer);
48+
expect(walletRepoCopyOnWrite.getIndex("blsPublicKeys").indexer).toEqual(blsPublicKeysIndexer);
4749
});
4850

4951
it("should find wallets by address", () => {

__tests__/unit/core-state/wallets/wallet-repository.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { Wallet, WalletRepository } from "@packages/core-state/src/wallets";
1212
import {
1313
addressesIndexer,
14+
blsPublicKeysIndexer,
1415
ipfsIndexer,
1516
locksIndexer,
1617
publicKeysIndexer,
@@ -75,6 +76,7 @@ describe("Wallet Repository", () => {
7576
"resignations",
7677
"locks",
7778
"ipfs",
79+
"blsPublicKeys",
7880
"businesses",
7981
"bridgechains",
8082
];
@@ -85,6 +87,7 @@ describe("Wallet Repository", () => {
8587
expect(walletRepo.getIndex("resignations").indexer).toEqual(resignationsIndexer);
8688
expect(walletRepo.getIndex("locks").indexer).toEqual(locksIndexer);
8789
expect(walletRepo.getIndex("ipfs").indexer).toEqual(ipfsIndexer);
90+
expect(walletRepo.getIndex("blsPublicKeys").indexer).toEqual(blsPublicKeysIndexer);
8891
expect(() => walletRepo.getIndex("iDontExist")).toThrow();
8992
});
9093

0 commit comments

Comments
 (0)