Skip to content

Commit f5271e3

Browse files
authored
Pass in account head to wallet/createAccount (#5619)
1 parent 4707d40 commit f5271e3

File tree

5 files changed

+117
-36
lines changed

5 files changed

+117
-36
lines changed

ironfish/src/rpc/routes/wallet/create.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe('Route wallet/create', () => {
2121
it('should create an account', async () => {
2222
await routeTest.node.wallet.createAccount('existingAccount', { setDefault: true })
2323
const createdAtHead = {
24-
hash: routeTest.node.chain.head.hash,
24+
hash: Buffer.alloc(32, 0),
2525
sequence: routeTest.node.chain.head.sequence,
2626
}
2727

ironfish/src/rpc/routes/wallet/createAccount.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
66
/* eslint-disable @typescript-eslint/no-explicit-any */
77

8+
import { blake3 } from '@napi-rs/blake-hash'
89
import { v4 as uuid } from 'uuid'
910
import { createRouteTest } from '../../../testUtilities/routeTest'
1011
import { RPC_ERROR_CODES } from '../../adapters'
@@ -20,7 +21,7 @@ describe('Route wallet/createAccount', () => {
2021
it('should create an account', async () => {
2122
await routeTest.node.wallet.createAccount('existingAccount', { setDefault: true })
2223
const createdAtHead = {
23-
hash: routeTest.node.chain.head.hash,
24+
hash: Buffer.alloc(32, 0),
2425
sequence: routeTest.node.chain.head.sequence,
2526
}
2627

@@ -118,6 +119,9 @@ describe('Route wallet/createAccount', () => {
118119
sequence: 10,
119120
},
120121
})
122+
123+
const head = await account?.getHead()
124+
expect(head).toBeNull()
121125
})
122126

123127
it('should set account createdAt to null', async () => {
@@ -142,4 +146,37 @@ describe('Route wallet/createAccount', () => {
142146
createdAt: null,
143147
})
144148
})
149+
150+
it('should set account head if passed', async () => {
151+
const name = uuid()
152+
153+
const response = await routeTest.client.wallet.createAccount({
154+
name,
155+
createdAt: null,
156+
head: {
157+
hash: blake3('test').toString('hex'),
158+
sequence: 10,
159+
},
160+
})
161+
162+
expect(response.status).toBe(200)
163+
expect(response.content).toMatchObject({
164+
name: name,
165+
publicAddress: expect.any(String),
166+
isDefaultAccount: true,
167+
})
168+
169+
const account = routeTest.node.wallet.getAccountByName(name)
170+
expect(account).toMatchObject({
171+
name: name,
172+
publicAddress: response.content.publicAddress,
173+
createdAt: null,
174+
})
175+
176+
const head = await account?.getHead()
177+
expect(head).toMatchObject({
178+
hash: blake3('test'),
179+
sequence: 10,
180+
})
181+
})
145182
})

ironfish/src/rpc/routes/wallet/createAccount.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ export type CreateAccountRequest = {
2222
name: string
2323
default?: boolean
2424
createdAt?: number | null
25+
head?: {
26+
hash: string
27+
sequence: number
28+
}
2529
}
2630

2731
export type CreateAccountResponse = {
@@ -35,6 +39,13 @@ export const CreateAccountRequestSchema: yup.ObjectSchema<CreateAccountRequest>
3539
name: yup.string().defined(),
3640
default: yup.boolean().optional(),
3741
createdAt: yup.number().optional().nullable(),
42+
head: yup
43+
.object({
44+
hash: yup.string().defined(),
45+
sequence: yup.number().defined(),
46+
})
47+
.optional()
48+
.default(undefined),
3849
})
3950
.defined()
4051

@@ -52,17 +63,17 @@ routes.register<typeof CreateAccountRequestSchema, CreateAccountResponse>(
5263
async (request, context): Promise<void> => {
5364
AssertHasRpcContext(request, context, 'wallet')
5465

55-
const createdAt =
56-
typeof request.data.createdAt === 'number'
57-
? {
58-
hash: Buffer.alloc(32, 0),
59-
sequence: request.data.createdAt,
60-
}
61-
: request.data.createdAt
66+
const head = request.data.head && {
67+
hash: Buffer.from(request.data.head.hash, 'hex'),
68+
sequence: request.data.head.sequence,
69+
}
6270

6371
let account
6472
try {
65-
account = await context.wallet.createAccount(request.data.name, { createdAt })
73+
account = await context.wallet.createAccount(request.data.name, {
74+
createdAt: request.data.createdAt,
75+
head,
76+
})
6677
} catch (e) {
6778
if (e instanceof DuplicateAccountNameError) {
6879
throw new RpcValidationError(e.message, 400, RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME)

ironfish/src/testUtilities/fixtures/account.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { FixtureGenerate, useFixture } from './fixture'
1111
export function useAccountFixture(
1212
wallet: Wallet,
1313
generate: FixtureGenerate<SpendingAccount> | string = 'test',
14-
options?: { createdAt?: HeadValue | null; setDefault?: boolean },
14+
options?: Parameters<Wallet['createAccount']>[1],
1515
): Promise<SpendingAccount> {
1616
if (typeof generate === 'string') {
1717
const name = generate
@@ -61,7 +61,7 @@ export async function useAccountAndAddFundsFixture(
6161
wallet: Wallet,
6262
chain: Blockchain,
6363
generate: FixtureGenerate<SpendingAccount> | string = 'test',
64-
options?: { createdAt?: HeadValue | null; setDefault?: boolean },
64+
options?: Parameters<Wallet['createAccount']>[1],
6565
): Promise<SpendingAccount> {
6666
const account = await useAccountFixture(wallet, generate, options)
6767
const block = await useMinerBlockFixture(chain, undefined, account)

ironfish/src/wallet/wallet.ts

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,7 +1355,7 @@ export class Wallet {
13551355

13561356
async createAccount(
13571357
name: string,
1358-
options: { createdAt?: HeadValue | null; setDefault?: boolean } = {
1358+
options: { setDefault?: boolean; createdAt?: number | null; head?: HeadValue | null } = {
13591359
setDefault: false,
13601360
},
13611361
): Promise<Account> {
@@ -1369,15 +1369,12 @@ export class Wallet {
13691369

13701370
const key = generateKey()
13711371

1372-
let createdAt: HeadValue | null = null
1373-
if (options.createdAt !== undefined) {
1374-
createdAt = options.createdAt
1375-
} else if (this.nodeClient) {
1376-
try {
1377-
createdAt = await this.getChainHead()
1378-
} catch {
1379-
this.logger.warn('Failed to fetch chain head from node client')
1380-
}
1372+
const createdAt = await this.createdAtWithDefault(options.createdAt)
1373+
let accountHead: HeadValue | null
1374+
if (options.head === undefined) {
1375+
accountHead = createdAt && (await this.accountHeadAtSequence(createdAt.sequence))
1376+
} else {
1377+
accountHead = options.head
13811378
}
13821379

13831380
const account = new Account({
@@ -1413,7 +1410,7 @@ export class Wallet {
14131410
await this.walletDb.setAccount(account, tx)
14141411
}
14151412

1416-
await account.updateHead(createdAt, tx)
1413+
await account.updateHead(accountHead, tx)
14171414
})
14181415

14191416
this.accountById.set(account.id, account)
@@ -1425,6 +1422,52 @@ export class Wallet {
14251422
return account
14261423
}
14271424

1425+
/*
1426+
* Use createdAt if provided, otherwise use the current chain head
1427+
*/
1428+
private async createdAtWithDefault(createdAt?: number | null): Promise<HeadValue | null> {
1429+
if (createdAt === null) {
1430+
return null
1431+
}
1432+
1433+
if (createdAt === undefined) {
1434+
try {
1435+
const sequence = (await this.getChainHead()).sequence
1436+
return {
1437+
sequence,
1438+
hash: Buffer.alloc(32, 0),
1439+
}
1440+
} catch {
1441+
this.logger.warn('Failed to fetch chain head from node client')
1442+
return null
1443+
}
1444+
}
1445+
1446+
return {
1447+
sequence: createdAt,
1448+
hash: Buffer.alloc(32, 0),
1449+
}
1450+
}
1451+
1452+
/*
1453+
* Try to get the block hash from the chain with createdAt sequence
1454+
* Otherwise, return null
1455+
*/
1456+
private async accountHeadAtSequence(sequence: number): Promise<HeadValue | null> {
1457+
try {
1458+
const previousBlock = await this.chainGetBlock({ sequence })
1459+
return previousBlock
1460+
? {
1461+
hash: Buffer.from(previousBlock.block.hash, 'hex'),
1462+
sequence: previousBlock.block.sequence,
1463+
}
1464+
: null
1465+
} catch {
1466+
this.logger.warn(`Failed to fetch block ${sequence} from node client`)
1467+
return null
1468+
}
1469+
}
1470+
14281471
async skipRescan(account: Account, tx?: IDatabaseTransaction): Promise<void> {
14291472
const { hash, sequence } = await this.getChainHead()
14301473
await account.updateHead({ hash, sequence }, tx)
@@ -1544,20 +1587,10 @@ export class Wallet {
15441587
}
15451588
}
15461589

1547-
if (createdAt !== null) {
1548-
const previousBlock = await this.chainGetBlock({ sequence: createdAt.sequence - 1 })
1590+
const accountHead =
1591+
createdAt && (await this.accountHeadAtSequence(createdAt.sequence - 1))
15491592

1550-
const head = previousBlock
1551-
? {
1552-
hash: Buffer.from(previousBlock.block.hash, 'hex'),
1553-
sequence: previousBlock.block.sequence,
1554-
}
1555-
: null
1556-
1557-
await account.updateHead(head, tx)
1558-
} else {
1559-
await account.updateHead(null, tx)
1560-
}
1593+
await account.updateHead(accountHead, tx)
15611594
})
15621595

15631596
this.accountById.set(account.id, account)

0 commit comments

Comments
 (0)