Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for postgres wallet type #699

Merged
merged 16 commits into from May 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
env:
TEST_AGENT_PUBLIC_DID_SEED: 000000000000000000000000Trustee9
GENESIS_TXN_PATH: network/genesis/local-genesis.txn
LD_LIBRARY_PATH: /home/runner/work/aries-framework-javascript/indy-sdk/experimental/plugins/postgres_storage/target/release # for Linux
LIB_INDY_STRG_POSTGRES: /home/runner/work/aries-framework-javascript/indy-sdk/experimental/plugins/postgres_storage/target/release # for Linux

# Make sure we're not running multiple release steps at the same time as this can give issues with determining the next npm version to release.
# Ideally we only add this to the 'release' job so it doesn't limit PR runs, but github can't guarantee the job order in that case:
Expand Down
17 changes: 16 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ RUN apt-get update -y && apt-get install -y \
apt-transport-https \
curl \
# Only needed to build indy-sdk
build-essential
build-essential \
git \
libzmq3-dev libsodium-dev pkg-config libssl-dev

# libindy
RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CE7709D068DB5E88
Expand All @@ -28,6 +30,19 @@ RUN apt-get update -y && apt-get install -y --allow-unauthenticated \
# Install yarn seperately due to `no-install-recommends` to skip nodejs install
RUN apt-get install -y --no-install-recommends yarn

# postgres plugin setup
# install rust and set up rustup
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"

# clone indy-sdk and build postgres plugin
RUN git clone https://github.com/hyperledger/indy-sdk.git
WORKDIR /indy-sdk/experimental/plugins/postgres_storage/
RUN cargo build --release

# set up library path for postgres plugin
ENV LIB_INDY_STRG_POSTGRES="/indy-sdk/experimental/plugins/postgres_storage/target/release"

FROM base as final

# AFJ specifc setup
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export interface WalletConfig {
id: string
key: string
keyDerivationMethod?: KeyDerivationMethod
storage?: {
type: string
[key: string]: unknown
}
}

export interface WalletConfigRekey {
Expand Down
62 changes: 46 additions & 16 deletions packages/core/src/wallet/IndyWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
} from '../types'
import type { Buffer } from '../utils/buffer'
import type { Wallet, DidInfo, DidConfig } from './Wallet'
import type { default as Indy } from 'indy-sdk'
import type { default as Indy, WalletStorageConfig } from 'indy-sdk'

import { Lifecycle, scoped } from 'tsyringe'

Expand Down Expand Up @@ -67,6 +67,41 @@ export class IndyWallet implements Wallet {
return this.walletConfig.id
}

private walletStorageConfig(walletConfig: WalletConfig): Indy.WalletConfig {
const walletStorageConfig: Indy.WalletConfig = {
id: walletConfig.id,
storage_type: walletConfig.storage?.type,
}

if (walletConfig.storage?.config) {
walletStorageConfig.storage_config = walletConfig.storage?.config as WalletStorageConfig
}

return walletStorageConfig
}

private walletCredentials(
walletConfig: WalletConfig,
rekey?: string,
rekeyDerivation?: KeyDerivationMethod
): Indy.OpenWalletCredentials {
const walletCredentials: Indy.OpenWalletCredentials = {
key: walletConfig.key,
key_derivation_method: walletConfig.keyDerivationMethod,
}
if (rekey) {
walletCredentials.rekey = rekey
}
if (rekeyDerivation) {
walletCredentials.rekey_derivation_method = rekeyDerivation
}
if (walletConfig.storage?.credentials) {
walletCredentials.storage_credentials = walletConfig.storage?.credentials as Record<string, unknown>
}

return walletCredentials
}

/**
* @throws {WalletDuplicateError} if the wallet already exists
* @throws {WalletError} if another error occurs
Expand All @@ -84,11 +119,7 @@ export class IndyWallet implements Wallet {
this.logger.debug(`Creating wallet '${walletConfig.id}' using SQLite storage`)

try {
await this.indy.createWallet(
{ id: walletConfig.id },
{ key: walletConfig.key, key_derivation_method: walletConfig.keyDerivationMethod }
)

await this.indy.createWallet(this.walletStorageConfig(walletConfig), this.walletCredentials(walletConfig))
this.walletConfig = walletConfig

// We usually want to create master secret only once, therefore, we can to do so when creating a wallet.
Expand Down Expand Up @@ -139,7 +170,11 @@ export class IndyWallet implements Wallet {
throw new WalletError('Wallet rekey undefined!. Please specify the new wallet key')
}
await this._open(
{ id: walletConfig.id, key: walletConfig.key, keyDerivationMethod: walletConfig.keyDerivationMethod },
{
id: walletConfig.id,
key: walletConfig.key,
keyDerivationMethod: walletConfig.keyDerivationMethod,
},
walletConfig.rekey,
walletConfig.rekeyDerivationMethod
)
Expand All @@ -162,13 +197,8 @@ export class IndyWallet implements Wallet {

try {
this.walletHandle = await this.indy.openWallet(
{ id: walletConfig.id },
{
key: walletConfig.key,
rekey: rekey,
key_derivation_method: walletConfig.keyDerivationMethod,
rekey_derivation_method: rekeyDerivation,
}
this.walletStorageConfig(walletConfig),
this.walletCredentials(walletConfig, rekey, rekeyDerivation)
)
if (rekey) {
this.walletConfig = { ...walletConfig, key: rekey, keyDerivationMethod: rekeyDerivation }
Expand Down Expand Up @@ -224,8 +254,8 @@ export class IndyWallet implements Wallet {

try {
await this.indy.deleteWallet(
{ id: this.walletConfig.id },
{ key: this.walletConfig.key, key_derivation_method: this.walletConfig.keyDerivationMethod }
this.walletStorageConfig(this.walletConfig),
this.walletCredentials(this.walletConfig)
)
} catch (error) {
if (isIndyError(error, 'WalletNotFoundError')) {
Expand Down
38 changes: 37 additions & 1 deletion packages/core/tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { catchError, filter, map, timeout } from 'rxjs/operators'

import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport'
import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport'
import { agentDependencies } from '../../node/src'
import { agentDependencies, WalletScheme } from '../../node/src'
import {
PresentationPreview,
PresentationPreviewAttribute,
Expand Down Expand Up @@ -84,6 +84,42 @@ export function getBaseConfig(name: string, extraConfig: Partial<InitConfig> = {
return { config, agentDependencies } as const
}

export function getBasePostgresConfig(name: string, extraConfig: Partial<InitConfig> = {}) {
const config: InitConfig = {
label: `Agent: ${name}`,
walletConfig: {
id: `Wallet${name}`,
key: `Key${name}`,
storage: {
type: 'postgres_storage',
config: {
url: 'localhost:5432',
wallet_scheme: WalletScheme.DatabasePerWallet,
},
credentials: {
account: 'postgres',
password: 'postgres',
admin_account: 'postgres',
admin_password: 'postgres',
},
},
},
publicDidSeed,
autoAcceptConnections: true,
indyLedgers: [
{
id: `pool-${name}`,
isProduction: false,
genesisPath,
},
],
logger: new TestLogger(LogLevel.error, name),
...extraConfig,
}

return { config, agentDependencies } as const
}

export function getAgentConfig(name: string, extraConfig: Partial<InitConfig> = {}) {
const { config, agentDependencies } = getBaseConfig(name, extraConfig)
return new AgentConfig(config, agentDependencies)
Expand Down
98 changes: 98 additions & 0 deletions packages/core/tests/postgres.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport'
import type { IndyPostgresStorageConfig } from '../../node/src'
import type { ConnectionRecord } from '../src/modules/connections'

import { Subject } from 'rxjs'

import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport'
import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport'
import { loadPostgresPlugin, WalletScheme } from '../../node/src'
import { Agent } from '../src/agent/Agent'

import { waitForBasicMessage, getBasePostgresConfig } from './helpers'

const alicePostgresConfig = getBasePostgresConfig('AgentsAlice', {
endpoints: ['rxjs:alice'],
})
const bobPostgresConfig = getBasePostgresConfig('AgentsBob', {
endpoints: ['rxjs:bob'],
})

describe('postgres agents', () => {
let aliceAgent: Agent
let bobAgent: Agent
let aliceConnection: ConnectionRecord
let bobConnection: ConnectionRecord

afterAll(async () => {
await bobAgent.shutdown()
await bobAgent.wallet.delete()
await aliceAgent.shutdown()
await aliceAgent.wallet.delete()
})

test('make a connection between postgres agents', async () => {
const aliceMessages = new Subject<SubjectMessage>()
const bobMessages = new Subject<SubjectMessage>()

const subjectMap = {
'rxjs:alice': aliceMessages,
'rxjs:bob': bobMessages,
}

const storageConfig: IndyPostgresStorageConfig = {
type: 'postgres_storage',
config: {
url: 'localhost:5432',
wallet_scheme: WalletScheme.DatabasePerWallet,
},
credentials: {
account: 'postgres',
password: 'postgres',
admin_account: 'postgres',
admin_password: 'postgres',
},
}

// loading the postgres wallet plugin
await loadPostgresPlugin(storageConfig.config, storageConfig.credentials)

aliceAgent = new Agent(alicePostgresConfig.config, alicePostgresConfig.agentDependencies)
aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages))
aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap))
await aliceAgent.initialize()

bobAgent = new Agent(bobPostgresConfig.config, bobPostgresConfig.agentDependencies)
bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages))
bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap))
await bobAgent.initialize()

const aliceConnectionAtAliceBob = await aliceAgent.connections.createConnection()
const bobConnectionAtBobAlice = await bobAgent.connections.receiveInvitation(aliceConnectionAtAliceBob.invitation)

aliceConnection = await aliceAgent.connections.returnWhenIsConnected(aliceConnectionAtAliceBob.connectionRecord.id)
bobConnection = await bobAgent.connections.returnWhenIsConnected(bobConnectionAtBobAlice.id)

expect(aliceConnection).toBeConnectedWith(bobConnection)
expect(bobConnection).toBeConnectedWith(aliceConnection)
})

test('send a message to connection', async () => {
const message = 'hello, world'
await aliceAgent.basicMessages.sendMessage(aliceConnection.id, message)

const basicMessage = await waitForBasicMessage(bobAgent, {
content: message,
})

expect(basicMessage.content).toBe(message)
})

test('can shutdown and re-initialize the same postgres agent', async () => {
expect(aliceAgent.isInitialized).toBe(true)
await aliceAgent.shutdown()
expect(aliceAgent.isInitialized).toBe(false)
await aliceAgent.initialize()
expect(aliceAgent.isInitialized).toBe(true)
})
})
4 changes: 4 additions & 0 deletions packages/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,18 @@
"dependencies": {
"@aries-framework/core": "0.1.0",
"express": "^4.17.1",
"ffi-napi": "^4.0.3",
"indy-sdk": "^1.16.0-dev-1636",
"node-fetch": "^2.6.1",
"ref-napi": "^3.0.3",
"ws": "^7.5.3"
},
"devDependencies": {
"@types/express": "^4.17.13",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing @types/ref-napi

"@types/ffi-napi": "^4.0.5",
"@types/node": "^15.14.4",
"@types/node-fetch": "^2.5.10",
"@types/ref-napi": "^3.0.4",
"@types/ws": "^7.4.6",
"rimraf": "~3.0.2",
"typescript": "~4.3.0"
Expand Down
Loading