Skip to content

Commit

Permalink
feat(core): add support for postgres wallet type (#699)
Browse files Browse the repository at this point in the history
Signed-off-by: Sai Ranjit Tummalapalli <[email protected]>

Co-authored-by: Timo Glastra <[email protected]>
  • Loading branch information
Sai Ranjit Tummalapalli and TimoGlastra authored May 10, 2022
1 parent 4146778 commit 83ff0f3
Show file tree
Hide file tree
Showing 10 changed files with 392 additions and 21 deletions.
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",
"@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

0 comments on commit 83ff0f3

Please sign in to comment.