Skip to content

Commit

Permalink
refactor: remove indirect KeyAgent dependency on ObservableWallet
Browse files Browse the repository at this point in the history
1. KeyAgent depends on InputResolver, which in is implemented by a util
that depends on ObservableWallet. Since ObservableWallet depends on
KeyAgent, it makes it a circular dependency. It was worked around by
having LazyWalletUtil and setupWallet utils. This commit replaces the
KeyAgent->InputResolver dependency with an extra TxContext argument for
signTransaction method, as well as removes the obsolete utils.

2. Addresses are stored by both KeyAgent and PersonalWallet.
This commit consolidates address storage in PersonalWallet,
making KeyAgent stateless and establishing single source of truth.

Notes:
- remove createAsyncKeyAgent tests instead of refactoring them,
because AsyncKeyAgent is no longer required and should be removed.
- extract address discovery and storage from PersonalWallet into
a new AddressTracker component

BREAKING CHANGE: remove KeyAgent.knownAddresses
- remove AsyncKeyAgent.knownAddresses$
- remove LazyWalletUtil and setupWallet utils
- replace KeyAgent dependency on InputResolver with props passed to sign method
- re-purpose AddressManager to Bip32Account: addresses are now stored only by the wallet
  • Loading branch information
mkazlauskas committed Dec 6, 2023
1 parent 8a6db2a commit 8dcfbc4
Show file tree
Hide file tree
Showing 106 changed files with 2,189 additions and 2,561 deletions.
41 changes: 21 additions & 20 deletions packages/e2e/src/factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
PersonalWallet,
PollingConfig,
SingleAddressDiscovery,
setupWallet,
storage
} from '@cardano-sdk/wallet';
import {
Expand All @@ -27,6 +26,7 @@ import {
} from '@cardano-sdk/core';
import {
AsyncKeyAgent,
Bip32Account,
CommunicationType,
InMemoryKeyAgent,
KeyAgentDependencies,
Expand Down Expand Up @@ -349,24 +349,25 @@ export const getWallet = async (props: GetWalletProps) => {
const keyManagementParams = { ...envKeyParams, ...(idx === undefined ? {} : { accountIndex: idx }) };

const bip32Ed25519 = await bip32Ed25519Factory.create(env.KEY_MANAGEMENT_PARAMS.bip32Ed25519, null, logger);
const { wallet } = await setupWallet({
bip32Ed25519,
createKeyAgent: keyAgent
? () => Promise.resolve(keyAgent)
: await keyManagementFactory.create(env.KEY_MANAGEMENT_PROVIDER, keyManagementParams, logger),
createWallet: async (asyncKeyAgent: AsyncKeyAgent) =>
new PersonalWallet(
{ name, polling },
{
...providers,
addressManager: util.createBip32Ed25519AddressManager(asyncKeyAgent),
logger,
stores,
witnesser: util.createBip32Ed25519Witnesser(asyncKeyAgent)
}
),
logger
});
const asyncKeyAgent =
keyAgent ||
(await (
await keyManagementFactory.create(env.KEY_MANAGEMENT_PROVIDER, keyManagementParams, logger)
)({
bip32Ed25519,
logger
}));
const bip32Account = await Bip32Account.fromAsyncKeyAgent(asyncKeyAgent);
const wallet = new PersonalWallet(
{ name, polling },
{
...providers,
bip32Account,
logger,
stores,
witnesser: util.createBip32Ed25519Witnesser(asyncKeyAgent)
}
);

const [{ address, rewardAccount }] = await firstValueFrom(wallet.addresses$);
logger.info(`Created wallet "${wallet.name}": ${address}/${rewardAccount}`);
Expand All @@ -375,7 +376,7 @@ export const getWallet = async (props: GetWalletProps) => {
polling?.maxInterval ||
(polling?.interval && polling.interval * DEFAULT_POLLING_CONFIG.maxIntervalMultiplier) ||
DEFAULT_POLLING_CONFIG.maxInterval;
return { providers, wallet: patchInitializeTxToRespectEpochBoundary(wallet, maxInterval) };
return { bip32Account, providers, wallet: patchInitializeTxToRespectEpochBoundary(wallet, maxInterval) };
};

export type TestWallet = Awaited<ReturnType<typeof getWallet>>;
1 change: 0 additions & 1 deletion packages/e2e/src/scripts/mnemonic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { localNetworkChainId } from '../util';
},
{
bip32Ed25519: new Crypto.SodiumBip32Ed25519(),
inputResolver: { resolveInput: async () => null },
logger: console
}
);
Expand Down
67 changes: 0 additions & 67 deletions packages/e2e/src/util/StubKeyAgent.ts

This file was deleted.

31 changes: 31 additions & 0 deletions packages/e2e/src/util/createMockKeyAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Bip32PublicKeyHex, SodiumBip32Ed25519 } from '@cardano-sdk/crypto';
import { Cardano } from '@cardano-sdk/core';
import { GroupedAddress, KeyAgent, KeyAgentType } from '@cardano-sdk/key-management';

const accountIndex = 0;
const chainId = Cardano.ChainIds.Preview;
const extendedAccountPublicKey = Bip32PublicKeyHex(
'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
);

export const createMockKeyAgent = (deriveAddressesReturn: GroupedAddress[] = []): jest.Mocked<KeyAgent> => {
const remainingDeriveAddressesReturn = [...deriveAddressesReturn];
return {
accountIndex,
bip32Ed25519: new SodiumBip32Ed25519(),
chainId,
deriveAddress: jest.fn().mockImplementation(async () => remainingDeriveAddressesReturn.shift()),
derivePublicKey: jest.fn(),
exportRootPrivateKey: jest.fn(),
extendedAccountPublicKey,
serializableData: {
__typename: KeyAgentType.InMemory,
accountIndex,
chainId,
encryptedRootPrivateKeyBytes: [],
extendedAccountPublicKey
},
signBlob: jest.fn(),
signTransaction: jest.fn()
};
};
3 changes: 2 additions & 1 deletion packages/e2e/src/util/handle-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export const mint = async (
txMetadatum: Cardano.Metadatum,
datum?: Cardano.PlutusData
) => {
const [{ address }] = await firstValueFrom(wallet.addresses$);
const knownAddresses = await firstValueFrom(wallet.addresses$);
const [{ address }] = knownAddresses;
const { policyScript, policySigner } = await createHandlePolicy(keyAgent);

const auxiliaryData = {
Expand Down
2 changes: 1 addition & 1 deletion packages/e2e/src/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './StubKeyAgent';
export * from './createMockKeyAgent';
export * from './localNetworkChainId';
export * from './util';
export * from './handle-util';
5 changes: 3 additions & 2 deletions packages/e2e/src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ export const getTxConfirmationEpoch = async (wallet: PersonalWallet, tx: Cardano
* @param wallet The wallet
*/
export const submitCertificate = async (certificate: Cardano.Certificate, wallet: TestWallet) => {
const walletAddress = (await firstValueFrom(wallet.wallet.addresses$))[0].address;
const knownAddresses = await firstValueFrom(wallet.wallet.addresses$);
const walletAddress = knownAddresses[0].address;
const txProps: InitializeTxProps = {
certificates: [certificate],
outputs: new Set([{ address: walletAddress, value: { coins: 3_000_000n } }])
Expand Down Expand Up @@ -238,7 +239,7 @@ export const createStandaloneKeyAgent = async (
getPassphrase: async () => Buffer.from(''),
mnemonicWords: mnemonics
},
{ bip32Ed25519, inputResolver: { resolveInput: async () => null }, logger }
{ bip32Ed25519, logger }
);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AddressesModel, WalletVars } from './types';
import { Cardano } from '@cardano-sdk/core';
import { FunctionHook } from '../artillery';
import { Pool } from 'pg';
import { StubKeyAgent, getEnv, getWallet, waitForWalletStateSettle, walletVariables } from '../../../src';
import { createMockKeyAgent, getEnv, getWallet, waitForWalletStateSettle, walletVariables } from '../../../src';
import { findAddressesWithRegisteredStakeKey } from './queries';
import { logger } from '@cardano-sdk/util-dev';

Expand Down Expand Up @@ -89,7 +89,7 @@ export const walletRestoration: FunctionHook<WalletVars> = async ({ vars, _uid }

try {
// Creates Stub KeyAgent
const keyAgent = util.createAsyncKeyAgent(new StubKeyAgent(currentAddress));
const keyAgent = util.createAsyncKeyAgent(createMockKeyAgent([currentAddress]));

// Start to measure wallet restoration time
const startedAt = Date.now();
Expand Down
17 changes: 7 additions & 10 deletions packages/e2e/test/load-test-custom/wallet-init/wallet-init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import path from 'path';
dotenv.config({ path: path.join(__dirname, '../../../.env') });

import { Logger } from 'ts-log';
import { PersonalWallet, createLazyWalletUtil } from '@cardano-sdk/wallet';
import { PersonalWallet } from '@cardano-sdk/wallet';
import { bufferCount, bufferTime, from, mergeAll, tap } from 'rxjs';
import { logger } from '@cardano-sdk/util-dev';

import { Bip32Account, util } from '@cardano-sdk/key-management';
import {
MeasurementUtil,
assetProviderFactory,
Expand All @@ -26,7 +27,6 @@ import {
waitForWalletStateSettle,
walletVariables
} from '../../../src';
import { util } from '@cardano-sdk/key-management';

// Example call that creates 5000 wallets in 10 minutes:
// VIRTUAL_USERS_GENERATE_DURATION=600 VIRTUAL_USERS_COUNT=5000 yarn load-test-custom:wallet-init
Expand Down Expand Up @@ -73,29 +73,26 @@ const getKeyAgent = async (accountIndex: number) => {
logger
);
const bip32Ed25519 = await bip32Ed25519Factory.create(env.KEY_MANAGEMENT_PARAMS.bip32Ed25519, null, logger);
const walletUtil = createLazyWalletUtil();
const keyAgent = await createKeyAgent({ bip32Ed25519, inputResolver: walletUtil, logger });
return { keyAgent, walletUtil };
const keyAgent = await createKeyAgent({ bip32Ed25519, logger });
return { keyAgent };
};

const createWallet = async (accountIndex: number): Promise<PersonalWallet> => {
measurementUtil.addStartMarker(MeasureTarget.keyAgent, accountIndex);
const providers = await getProviders();
const { keyAgent, walletUtil } = await getKeyAgent(accountIndex);
const { keyAgent } = await getKeyAgent(accountIndex);
measurementUtil.addMeasureMarker(MeasureTarget.keyAgent, accountIndex);

measurementUtil.addStartMarker(MeasureTarget.wallet, accountIndex);
const wallet = new PersonalWallet(
return new PersonalWallet(
{ name: `Wallet ${accountIndex}` },
{
...providers,
addressManager: util.createBip32Ed25519AddressManager(keyAgent),
bip32Account: await Bip32Account.fromAsyncKeyAgent(keyAgent),
logger,
witnesser: util.createBip32Ed25519Witnesser(keyAgent)
}
);
walletUtil.initialize(wallet);
return wallet;
};

const initWallet = async (idx: number) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ dotenv.config({ path: path.join(__dirname, '../../../.env') });
import { Cardano } from '@cardano-sdk/core';
import { GroupedAddress, util } from '@cardano-sdk/key-management';
import { Logger } from 'ts-log';
import { MINUTE, StubKeyAgent, getEnv, getWallet, waitForWalletStateSettle, walletVariables } from '../../../src';
import { MINUTE, createMockKeyAgent, getEnv, getWallet, waitForWalletStateSettle, walletVariables } from '../../../src';
import { PersonalWallet } from '@cardano-sdk/wallet';
import { logger } from '@cardano-sdk/util-dev';
import { mapToGroupedAddress } from '../../artillery/wallet-restoration/WalletRestoration';
Expand Down Expand Up @@ -41,7 +41,7 @@ const initWallets = async (walletsNum: number, addresses: GroupedAddress[]): Pro
for (let i = 0; i < walletsNum; i++) {
currentAddress = addresses[i];
testLogger.info(' address:', currentAddress.address);
const keyAgent = util.createAsyncKeyAgent(new StubKeyAgent(currentAddress));
const keyAgent = util.createAsyncKeyAgent(createMockKeyAgent([currentAddress]));
const { wallet } = await getWallet({
env,
idx: 0,
Expand Down
16 changes: 6 additions & 10 deletions packages/e2e/test/local-network/register-pool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,15 @@ describe('local-network/register-pool', () => {

await walletReady(wallet);

const poolAddressManager = wallet.addressManager;

const poolPubKey = await poolAddressManager.derivePublicKey({
const poolPubKey = await wallet1.bip32Account.derivePublicKey({
index: 0,
role: KeyRole.External
});

const poolKeyHash = await bip32Ed25519.getPubKeyHash(poolPubKey);
const poolKeyHash = await bip32Ed25519.getPubKeyHash(poolPubKey.hex());
const poolId = Cardano.PoolId.fromKeyHash(poolKeyHash);
const poolRewardAccount = (
await poolAddressManager.deriveAddress(
await wallet1.bip32Account.deriveAddress(
{
index: 0,
type: AddressType.External
Expand Down Expand Up @@ -167,17 +165,15 @@ describe('local-network/register-pool', () => {

await walletReady(wallet);

const poolAddressManager = wallet.addressManager;

const poolPubKey = await poolAddressManager.derivePublicKey({
const poolPubKey = await wallet2.bip32Account.derivePublicKey({
index: 0,
role: KeyRole.External
});

const poolKeyHash = await bip32Ed25519.getPubKeyHash(poolPubKey);
const poolKeyHash = await bip32Ed25519.getPubKeyHash(poolPubKey.hex());
const poolId = Cardano.PoolId.fromKeyHash(poolKeyHash);
const poolRewardAccount = (
await poolAddressManager.deriveAddress(
await wallet2.bip32Account.deriveAddress(
{
index: 0,
type: AddressType.External
Expand Down
8 changes: 3 additions & 5 deletions packages/e2e/test/long-running/cache-invalidation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,15 @@ describe('cache invalidation', () => {

await walletReady(wallet);

const poolAddressManager = wallet.addressManager;

const poolPubKey = await poolAddressManager.derivePublicKey({
const poolPubKey = await wallet1.bip32Account.derivePublicKey({
index: 0,
role: KeyRole.External
});

const poolKeyHash = await bip32Ed25519.getPubKeyHash(poolPubKey);
const poolKeyHash = await bip32Ed25519.getPubKeyHash(poolPubKey.hex());
const poolId = Cardano.PoolId.fromKeyHash(poolKeyHash);
const poolRewardAccount = (
await poolAddressManager.deriveAddress(
await wallet1.bip32Account.deriveAddress(
{
index: 0,
type: AddressType.External
Expand Down
11 changes: 9 additions & 2 deletions packages/e2e/test/long-running/multisig-wallet/MultiSigWallet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import * as Crypto from '@cardano-sdk/crypto';
import { AddressType, GroupedAddress, InMemoryKeyAgent, KeyRole } from '@cardano-sdk/key-management';
import {
AccountKeyDerivationPath,
AddressType,
GroupedAddress,
InMemoryKeyAgent,
KeyRole
} from '@cardano-sdk/key-management';
import {
Cardano,
ChainHistoryProvider,
Expand All @@ -25,7 +31,7 @@ const randomPublicKey = () => Crypto.Ed25519PublicKeyHex(Array.from({ length: 64
const DUMMY_HEX_BYTES =
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';

const DERIVATION_PATH = {
const DERIVATION_PATH: AccountKeyDerivationPath = {
index: 0,
role: KeyRole.External
};
Expand Down Expand Up @@ -209,6 +215,7 @@ export class MultiSigWallet {
body: multiSigTx.getTransaction().body,
hash: multiSigTx.getTransaction().id
},
{ knownAddresses: [this.#address], txInKeyPathMap: {} },
{ additionalKeyPaths: [DERIVATION_PATH] }
);

Expand Down
Loading

0 comments on commit 8dcfbc4

Please sign in to comment.