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(client): reuse JSON RPC providers #2497

Merged
merged 5 commits into from
Apr 15, 2024
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
14 changes: 7 additions & 7 deletions packages/client/src/Authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { Wallet } from '@ethersproject/wallet'
import { EthereumAddress, hexToBinary, toEthereumAddress, wait, createSignature } from '@streamr/utils'
import pMemoize from 'p-memoize'
import { PrivateKeyAuthConfig, ProviderAuthConfig, StrictStreamrClientConfig } from './Config'
import { getStreamRegistryChainProviders } from './Ethereum'
import { pLimitFn } from './utils/promises'
import { RpcProviderFactory } from './RpcProviderFactory'

export const AuthenticationInjectionToken = Symbol('Authentication')

Expand All @@ -16,16 +16,16 @@ export interface Authentication {
// always in lowercase
getAddress: () => Promise<EthereumAddress>
createMessageSignature: (payload: Uint8Array) => Promise<Uint8Array>
getStreamRegistryChainSigner: () => Promise<SignerWithProvider>
getStreamRegistryChainSigner: (rpcProviderFactory: RpcProviderFactory) => Promise<SignerWithProvider>
}

export const createPrivateKeyAuthentication = (key: string, config: Pick<StrictStreamrClientConfig, 'contracts' | '_timeouts'>): Authentication => {
export const createPrivateKeyAuthentication = (key: string): Authentication => {
const address = toEthereumAddress(computeAddress(key))
return {
getAddress: async () => address,
createMessageSignature: async (payload: Uint8Array) => createSignature(payload, hexToBinary(key)),
getStreamRegistryChainSigner: async () => {
const primaryProvider = getStreamRegistryChainProviders(config)[0]
getStreamRegistryChainSigner: async (rpcProviderFactory: RpcProviderFactory) => {
const primaryProvider = rpcProviderFactory.getPrimaryProvider()
return new Wallet(key, primaryProvider)
}
}
Expand All @@ -37,7 +37,7 @@ export const createAuthentication = (config: Pick<StrictStreamrClientConfig, 'au
const normalizedPrivateKey = !privateKey.startsWith('0x')
? `0x${privateKey}`
: privateKey
return createPrivateKeyAuthentication(normalizedPrivateKey, config)
return createPrivateKeyAuthentication(normalizedPrivateKey)
} else if ((config.auth as ProviderAuthConfig)?.ethereum !== undefined) {
const ethereum = (config.auth as ProviderAuthConfig)?.ethereum
const provider = new Web3Provider(ethereum)
Expand Down Expand Up @@ -83,6 +83,6 @@ export const createAuthentication = (config: Pick<StrictStreamrClientConfig, 'au
}
}
} else {
return createPrivateKeyAuthentication(Wallet.createRandom().privateKey, config)
return createPrivateKeyAuthentication(Wallet.createRandom().privateKey)
}
}
57 changes: 0 additions & 57 deletions packages/client/src/Ethereum.ts

This file was deleted.

38 changes: 38 additions & 0 deletions packages/client/src/RpcProviderFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ConfigInjectionToken, StrictStreamrClientConfig } from './Config'
import type { Provider } from '@ethersproject/providers'
import type { ConnectionInfo } from '@ethersproject/web'
import { LoggingStaticJsonRpcProvider } from './utils/LoggingStaticJsonRpcProvider'
import { inject, Lifecycle, scoped } from 'tsyringe'

@scoped(Lifecycle.ContainerScoped)
export class RpcProviderFactory {
private readonly config: Pick<StrictStreamrClientConfig, 'contracts' | '_timeouts'>
private providers?: Provider[]

constructor(@inject(ConfigInjectionToken) config: Pick<StrictStreamrClientConfig, 'contracts' | '_timeouts'>) {
this.config = config
}

getProviders(): Provider[] {
if (this.providers === undefined) {
// eslint-disable-next-line no-underscore-dangle
const timeout = this.config._timeouts.jsonRpcTimeout
const pollInterval = this.config.contracts.pollInterval
this.providers = this.config.contracts.streamRegistryChainRPCs.rpcs.map((c: ConnectionInfo) => {
const provider = new LoggingStaticJsonRpcProvider({
...c,
timeout
})
if (pollInterval !== undefined) {
provider.pollingInterval = pollInterval
}
return provider
})
}
return this.providers
}

getPrimaryProvider(): Provider {
return this.getProviders()[0]
}
}
15 changes: 7 additions & 8 deletions packages/client/src/StreamrClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
redactConfig
} from './Config'
import { DestroySignal } from './DestroySignal'
import { generateEthereumAccount as _generateEthereumAccount, getEthersOverrides as _getEthersOverrides } from './Ethereum'
import { generateEthereumAccount as _generateEthereumAccount, getEthersOverrides as _getEthersOverrides } from './ethereumUtils'
import { Message, convertStreamMessageToMessage } from './Message'
import { MetricsPublisher } from './MetricsPublisher'
import { NetworkNodeFacade, NetworkNodeStub } from './NetworkNodeFacade'
Expand Down Expand Up @@ -49,8 +49,7 @@ import { StreamDefinition } from './types'
import { LoggerFactory } from './utils/LoggerFactory'
import { pOnce } from './utils/promises'
import { convertPeerDescriptorToNetworkPeerDescriptor, createTheGraphClient } from './utils/utils'
import { createNewInstantiateContractsFn, InstantiateERC1271ContractsToken } from './contracts/ERC1271ContractFacade'
import { ContractFactory } from './ContractFactory'
import { RpcProviderFactory } from './RpcProviderFactory'

// TODO: this type only exists to enable tsdoc to generate proper documentation
export type SubscribeOptions = StreamDefinition & ExtraSubscribeOptions
Expand Down Expand Up @@ -85,6 +84,7 @@ export class StreamrClient {
private readonly subscriber: Subscriber
private readonly resends: Resends
private readonly node: NetworkNodeFacade
private readonly rpcProviderFactory: RpcProviderFactory
private readonly streamRegistry: StreamRegistry
private readonly streamStorageRegistry: StreamStorageRegistry
private readonly storageNodeRegistry: StorageNodeRegistry
Expand All @@ -108,9 +108,7 @@ export class StreamrClient {
const container = parentContainer.createChildContainer()
container.register(AuthenticationInjectionToken, { useValue: authentication })
container.register(ConfigInjectionToken, { useValue: strictConfig })
container.register(InstantiateERC1271ContractsToken, {
useValue: createNewInstantiateContractsFn(container.resolve<ContractFactory>(ContractFactory), strictConfig)
})

// eslint-disable-next-line max-len
container.register(TheGraphClient, { useValue: createTheGraphClient(container.resolve<StreamrClientEventEmitter>(StreamrClientEventEmitter), strictConfig) })
this.id = strictConfig.id
Expand All @@ -120,6 +118,7 @@ export class StreamrClient {
this.subscriber = container.resolve<Subscriber>(Subscriber)
this.resends = container.resolve<Resends>(Resends)
this.node = container.resolve<NetworkNodeFacade>(NetworkNodeFacade)
this.rpcProviderFactory = container.resolve(RpcProviderFactory)
this.streamRegistry = container.resolve<StreamRegistry>(StreamRegistry)
this.streamStorageRegistry = container.resolve<StreamStorageRegistry>(StreamStorageRegistry)
this.storageNodeRegistry = container.resolve<StorageNodeRegistry>(StorageNodeRegistry)
Expand Down Expand Up @@ -572,7 +571,7 @@ export class StreamrClient {
* Gets the Signer associated with the current {@link StreamrClient} instance.
*/
getSigner(): Promise<SignerWithProvider> {
return this.authentication.getStreamRegistryChainSigner()
return this.authentication.getStreamRegistryChainSigner(this.rpcProviderFactory)
}

/**
Expand Down Expand Up @@ -694,7 +693,7 @@ export class StreamrClient {
* transactions via ethers library.
*/
getEthersOverrides(): Overrides {
return _getEthersOverrides(this.config)
return _getEthersOverrides(this.rpcProviderFactory, this.config)
}

// --------------------------------------------------------------------------------------------
Expand Down
33 changes: 12 additions & 21 deletions packages/client/src/contracts/ERC1271ContractFacade.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { ContractFactory } from '../ContractFactory'
import { getStreamRegistryChainProviders } from '../Ethereum'
import { Provider } from '@ethersproject/providers'
import { BrandedString, EthereumAddress, MapWithTtl, toEthereumAddress } from '@streamr/utils'
import ERC1271ContractArtifact from '../ethereumArtifacts/IERC1271Abi.json'
import type { IERC1271 as ERC1271Contract } from '../ethereumArtifacts/IERC1271'
import { StrictStreamrClientConfig } from '../Config'
import { queryAllReadonlyContracts } from '../utils/contract'
import { Mapping } from '../utils/Mapping'
import { inject, Lifecycle, scoped } from 'tsyringe'
import { Lifecycle, scoped } from 'tsyringe'
import { recoverAddress, hash } from '@streamr/utils'
import { RpcProviderFactory } from '../RpcProviderFactory'

export const SUCCESS_MAGIC_VALUE = '0x1626ba7e' // Magic value for success as defined by ERC-1271

Expand All @@ -20,32 +19,24 @@ function formCacheKey(contractAddress: EthereumAddress, clientWalletAddress: Eth
return `${contractAddress}_${clientWalletAddress}` as CacheKey
}

export const InstantiateERC1271ContractsToken = Symbol('InstantiateERC1271ContractsToken')

export function createNewInstantiateContractsFn(
contractFactory: ContractFactory,
config: Pick<StrictStreamrClientConfig, 'contracts' | '_timeouts'>
): (address: EthereumAddress) => ERC1271Contract[] {
return (address) => getStreamRegistryChainProviders(config).map((provider: Provider) => {
return contractFactory.createReadContract(
address,
ERC1271ContractArtifact,
provider,
'erc1271Contract'
) as ERC1271Contract
})
}

@scoped(Lifecycle.ContainerScoped)
export class ERC1271ContractFacade {
private readonly contractsByAddress: Mapping<[EthereumAddress], ERC1271Contract[]>
private readonly publisherCache = new MapWithTtl<CacheKey, boolean>(() => CACHE_TTL)

constructor(
@inject(InstantiateERC1271ContractsToken) instantiateContracts: (address: EthereumAddress) => ERC1271Contract[],
contractFactory: ContractFactory,
rpcProviderFactory: RpcProviderFactory
) {
this.contractsByAddress = new Mapping<[EthereumAddress], ERC1271Contract[]>(async (address) => {
return instantiateContracts(address)
return rpcProviderFactory.getProviders().map((provider: Provider) => {
return contractFactory.createReadContract(
address,
ERC1271ContractArtifact,
provider,
'erc1271Contract'
) as ERC1271Contract
})
})
}

Expand Down
12 changes: 8 additions & 4 deletions packages/client/src/contracts/StorageNodeRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { Lifecycle, inject, scoped } from 'tsyringe'
import { Authentication, AuthenticationInjectionToken } from '../Authentication'
import { ConfigInjectionToken, StrictStreamrClientConfig } from '../Config'
import { ContractFactory } from '../ContractFactory'
import { getStreamRegistryChainProviders, getEthersOverrides } from '../Ethereum'
import { getEthersOverrides } from '../ethereumUtils'
import { StreamrClientError } from '../StreamrClientError'
import type { NodeRegistry as NodeRegistryContract } from '../ethereumArtifacts/NodeRegistry'
import NodeRegistryArtifact from '../ethereumArtifacts/NodeRegistryAbi.json'
import { queryAllReadonlyContracts, waitForTx } from '../utils/contract'
import { RpcProviderFactory } from '../RpcProviderFactory'

export interface StorageNodeMetadata {
urls: string[]
Expand All @@ -23,18 +24,21 @@ export class StorageNodeRegistry {
private nodeRegistryContract?: NodeRegistryContract
private readonly nodeRegistryContractsReadonly: NodeRegistryContract[]
private readonly contractFactory: ContractFactory
private readonly rpcProviderFactory: RpcProviderFactory
private readonly config: Pick<StrictStreamrClientConfig, 'contracts' | '_timeouts'>
private readonly authentication: Authentication

constructor(
contractFactory: ContractFactory,
rpcProviderFactory: RpcProviderFactory,
@inject(ConfigInjectionToken) config: Pick<StrictStreamrClientConfig, 'contracts' | '_timeouts'>,
@inject(AuthenticationInjectionToken) authentication: Authentication,
) {
this.contractFactory = contractFactory
this.rpcProviderFactory = rpcProviderFactory
this.config = config
this.authentication = authentication
this.nodeRegistryContractsReadonly = getStreamRegistryChainProviders(config).map((provider: Provider) => {
this.nodeRegistryContractsReadonly = rpcProviderFactory.getProviders().map((provider: Provider) => {
return this.contractFactory.createReadContract(
toEthereumAddress(this.config.contracts.storageNodeRegistryChainAddress),
NodeRegistryArtifact,
Expand All @@ -46,7 +50,7 @@ export class StorageNodeRegistry {

private async connectToContract() {
if (!this.nodeRegistryContract) {
const chainSigner = await this.authentication.getStreamRegistryChainSigner()
const chainSigner = await this.authentication.getStreamRegistryChainSigner(this.rpcProviderFactory)
this.nodeRegistryContract = this.contractFactory.createWriteContract<NodeRegistryContract>(
toEthereumAddress(this.config.contracts.storageNodeRegistryChainAddress),
NodeRegistryArtifact,
Expand All @@ -58,7 +62,7 @@ export class StorageNodeRegistry {

async setStorageNodeMetadata(metadata: StorageNodeMetadata | undefined): Promise<void> {
await this.connectToContract()
const ethersOverrides = getEthersOverrides(this.config)
const ethersOverrides = getEthersOverrides(this.rpcProviderFactory, this.config)
if (metadata !== undefined) {
await waitForTx(this.nodeRegistryContract!.createOrUpdateNodeSelf(JSON.stringify(metadata), ethersOverrides))
} else {
Expand Down
Loading
Loading