我配置了一个由三个组织组成的 Hyperledger Fabric 网络:一个组织托管三个排序者节点,另外两个组织各包含两个对等点。
为了与网络交互,我正在构建一个 NodeJS + Express 服务器,它将使用
hyperledger/fabric-gateway
模块来建立通信。出于测试目的,我在通道“ch1”上安装了由 Fabric-samples 存储库提供的 Java 版本的 asset-transfer-basic 包。
在我的expressJs服务器中,我配置了一个端点,它将尝试调用网络的“GetAllAssets”函数。
function envOrDefault(key: string, defaultValue: string): string {
return process.env[key] || defaultValue;
}
const channelName = envOrDefault('CHANNEL_NAME', 'ch1');
const chaincodeName = envOrDefault('CHAINCODE_NAME', 'basic');
// Path to crypto materials.
const cryptoPath = envOrDefault('CRYPTO_PATH', path.resolve('path', 'to', 'org1'));
// Path to admin private key directory.
const keyDirectoryPath = envOrDefault('KEY_DIRECTORY_PATH', path.resolve(cryptoPath, 'path', 'to', 'keystore'));
// Path to admin certificate.
const certPath = envOrDefault('CERT_PATH', path.resolve(cryptoPath, 'path', 'to', 'signcerts', 'cert.pem'));
// Path to peer tls certificate.
const tlsCertPath = envOrDefault('TLS_CERT_PATH', path.resolve(cryptoPath, 'peer1', 'tls-msp', 'tlscacerts', 'tlsca.pem'));
// Gateway peer endpoint.
const peerEndpoint = envOrDefault('PEER_ENDPOINT', 'localhost:7051');
// Gateway peer SSL host name override.
const peerHostAlias = envOrDefault('PEER_HOST_ALIAS', 'peer1-org1');
// MSP of the organization
const mspId = envOrDefault('MSP_ID', 'org1MSP');
// The configuration object
const configurations: Config = new Config(
keyDirectoryPath,
certPath,
tlsCertPath,
peerEndpoint,
peerHostAlias,
mspId
);
const utf8Decoder = new TextDecoder();
app.get("/gateway", async (req: Request, res: Response, next: NextFunction) => {
const client: grpc.Client = await configurations.createGrpcClient();
const identity: Identity = await configurations.createIdentity();
const signer: Signer = await configurations.createSigner();
const gateway: Gateway = connect({
client,
identity,
signer,
// Default timeouts for different gRPC calls
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds
},
endorseOptions: () => {
return { deadline: Date.now() + 15000 }; // 15 seconds
},
submitOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds
},
commitStatusOptions: () => {
return { deadline: Date.now() + 60000 }; // 1 minute
},
});
try{
let network: Network = gateway.getNetwork(channelName);
let contract: Contract = network.getContract(chaincodeName);
await getAllAssets(contract);
} finally {
gateway.close();
client.close();
}
})
configurations.createGrpcClient()
、configurations.createIdentity()
和configurations.createSigner()
定义如下:
async createGrpcClient(): Promise<grpc.Client> {
const tlsRootCert = await fs.readFile(this._peerTlsPath);
console.log("TLS Root Cert ----->");
console.log(tlsRootCert);
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
return new grpc.Client(this._peerEndpoint, tlsCredentials, {
'grpc.ssl_target_name_override': this._hostAlias,
});
}
async createGrpcClient(): Promise<grpc.Client> {
const tlsRootCert = await fs.readFile(this._peerTlsPath);
console.log("TLS Root Cert ----->");
console.log(tlsRootCert);
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
return new grpc.Client(this._peerEndpoint, tlsCredentials, {
'grpc.ssl_target_name_override': this._hostAlias,
});
}
async createIdentity(): Promise<Identity> {
const credentials: Buffer = await fs.readFile(this._certPath);
console.log("Credentials Cert ----->");
console.log(credentials);
const mspId: string = this._mspId;
return { mspId, credentials };
}
如果我直接在计算机上执行网络服务器,这将起作用,事实上,如果我尝试启动服务器并导航到
/gateway
,这将返回存储在区块链中的资产:
--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger
*** Result: [
{
appraisedValue: 300,
assetID: 'asset1',
color: 'blue',
owner: 'Tomoko',
size: 5
},
{
appraisedValue: 400,
assetID: 'asset2',
color: 'red',
owner: 'Brad',
size: 5
},
{
appraisedValue: 500,
assetID: 'asset3',
color: 'green',
owner: 'Jin Soo',
size: 10
},
{
appraisedValue: 600,
assetID: 'asset4',
color: 'yellow',
owner: 'Max',
size: 10
},
{
appraisedValue: 700,
assetID: 'asset5',
color: 'black',
owner: 'Adrian',
size: 15
},
{
appraisedValue: 700,
assetID: 'asset6',
color: 'white',
owner: 'Michel',
size: 15
}
]
不幸的是,我无法使用 docker 容器重现此行为:我配置了以下 Dockerfile:
Dockerfile:
FROM node:20-alpine
WORKDIR /usr/src/app
COPY . .
RUN npm install
RUN npm run prepare
ENV GRPC_TRACE=all
ENV GRPC_VERBOSITY=DEBUG
EXPOSE 8080
CMD [ "npm", "start" ]
然后使用
docker build . -t test
构建镜像,使用 docker run -p:8080:8080 -v /tmp/hyperledger:/tmp/hyperledger --name test test
启动,最后使用 docker network connect blockchain_fabric-ca test
连接到 Fabric 网络(通过 docker-compose 构建)。
我确定容器已添加到网络中,因为如果我使用
docker network inspect blockchain_fabric-ca
检查网络,“测试”位于活动容器列表中。
如果我在与之前相同的端点上导航,则会收到以下连接错误:
--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger
D 2023-08-10T20:03:40.623Z | channel | (1) dns:localhost:7051 createResolvingCall [0] method="/gateway.Gateway/Evaluate", deadline=2023-08-10T20:03:45.622Z
D 2023-08-10T20:03:40.624Z | resolving_call | [0] Created
D 2023-08-10T20:03:40.624Z | resolving_call | [0] Deadline: 2023-08-10T20:03:45.622Z
D 2023-08-10T20:03:40.625Z | resolving_call | [0] Deadline will be reached in 4997ms
D 2023-08-10T20:03:40.626Z | resolving_call | [0] start called
D 2023-08-10T20:03:40.627Z | dns_resolver | Looking up DNS hostname localhost
D 2023-08-10T20:03:40.628Z | resolving_load_balancer | dns:localhost:7051 IDLE -> CONNECTING
D 2023-08-10T20:03:40.628Z | connectivity_state | (1) dns:localhost:7051 IDLE -> CONNECTING
D 2023-08-10T20:03:40.628Z | channel | (1) dns:localhost:7051 callRefTimer.ref | configSelectionQueue.length=1 pickQueue.length=0
D 2023-08-10T20:03:40.628Z | resolving_call | [0] startRead called
D 2023-08-10T20:03:40.629Z | resolving_call | [0] write() called with message of length 1285
D 2023-08-10T20:03:40.629Z | resolving_call | [0] halfClose called
D 2023-08-10T20:03:40.630Z | dns_resolver | Resolved addresses for target dns:localhost:7051: [::1:7051,127.0.0.1:7051]
D 2023-08-10T20:03:40.631Z | subchannel | (2) ::1:7051 Subchannel constructed with options {
"grpc.ssl_target_name_override": "peer1-org1"
}
D 2023-08-10T20:03:40.631Z | subchannel_refcount | (2) ::1:7051 refcount 0 -> 1
D 2023-08-10T20:03:40.631Z | subchannel | (3) 127.0.0.1:7051 Subchannel constructed with options {
"grpc.ssl_target_name_override": "peer1-org1"
}
D 2023-08-10T20:03:40.631Z | subchannel_refcount | (3) 127.0.0.1:7051 refcount 0 -> 1
D 2023-08-10T20:03:40.631Z | subchannel_refcount | (2) ::1:7051 refcount 1 -> 2
D 2023-08-10T20:03:40.632Z | subchannel_refcount | (3) 127.0.0.1:7051 refcount 1 -> 2
D 2023-08-10T20:03:40.632Z | pick_first | Start connecting to subchannel with address ::1:7051
D 2023-08-10T20:03:40.632Z | pick_first | IDLE -> CONNECTING
D 2023-08-10T20:03:40.632Z | resolving_load_balancer | dns:localhost:7051 CONNECTING -> CONNECTING
D 2023-08-10T20:03:40.632Z | channel | (1) dns:localhost:7051 callRefTimer.unref | configSelectionQueue.length=1 pickQueue.length=0
D 2023-08-10T20:03:40.632Z | connectivity_state | (1) dns:localhost:7051 CONNECTING -> CONNECTING
D 2023-08-10T20:03:40.633Z | subchannel | (2) ::1:7051 IDLE -> CONNECTING
D 2023-08-10T20:03:40.634Z | transport | dns:localhost:7051 creating HTTP/2 session to ::1:7051
D 2023-08-10T20:03:40.637Z | channel | (1) dns:localhost:7051 createRetryingCall [1] method="/gateway.Gateway/Evaluate"
D 2023-08-10T20:03:40.637Z | resolving_call | [0] Created child [1]
D 2023-08-10T20:03:40.637Z | retrying_call | [1] start called
D 2023-08-10T20:03:40.637Z | channel | (1) dns:localhost:7051 createLoadBalancingCall [2] method="/gateway.Gateway/Evaluate"
D 2023-08-10T20:03:40.637Z | retrying_call | [1] Created child call [2] for attempt 1
D 2023-08-10T20:03:40.638Z | load_balancing_call | [2] start called
D 2023-08-10T20:03:40.638Z | load_balancing_call | [2] Pick called
D 2023-08-10T20:03:40.638Z | load_balancing_call | [2] Pick result: QUEUE subchannel: null status: undefined undefined
D 2023-08-10T20:03:40.638Z | channel | (1) dns:localhost:7051 callRefTimer.ref | configSelectionQueue.length=0 pickQueue.length=1
D 2023-08-10T20:03:40.638Z | retrying_call | [1] startRead called
D 2023-08-10T20:03:40.638Z | load_balancing_call | [2] startRead called
D 2023-08-10T20:03:40.639Z | retrying_call | [1] write() called with message of length 1290
D 2023-08-10T20:03:40.639Z | load_balancing_call | [2] write() called with message of length 1290
D 2023-08-10T20:03:40.639Z | retrying_call | [1] halfClose called
D 2023-08-10T20:03:40.641Z | transport | dns:localhost:7051 connection failed with error connect EADDRNOTAVAIL ::1:7051 - Local (:::0)
D 2023-08-10T20:03:40.641Z | subchannel | (2) ::1:7051 CONNECTING -> TRANSIENT_FAILURE
D 2023-08-10T20:03:40.641Z | pick_first | Start connecting to subchannel with address 127.0.0.1:7051
D 2023-08-10T20:03:40.641Z | subchannel | (3) 127.0.0.1:7051 IDLE -> CONNECTING
D 2023-08-10T20:03:40.641Z | transport | dns:localhost:7051 creating HTTP/2 session to 127.0.0.1:7051
D 2023-08-10T20:03:40.644Z | transport | dns:localhost:7051 connection failed with error connect ECONNREFUSED 127.0.0.1:7051
D 2023-08-10T20:03:40.644Z | subchannel | (3) 127.0.0.1:7051 CONNECTING -> TRANSIENT_FAILURE
D 2023-08-10T20:03:40.644Z | pick_first | CONNECTING -> TRANSIENT_FAILURE
D 2023-08-10T20:03:40.644Z | resolving_load_balancer | dns:localhost:7051 CONNECTING -> TRANSIENT_FAILURE
D 2023-08-10T20:03:40.644Z | channel | (1) dns:localhost:7051 callRefTimer.unref | configSelectionQueue.length=0 pickQueue.length=0
D 2023-08-10T20:03:40.644Z | load_balancing_call | [2] Pick called
D 2023-08-10T20:03:40.644Z | load_balancing_call | [2] Pick result: TRANSIENT_FAILURE subchannel: null status: 14 No connection established
D 2023-08-10T20:03:40.644Z | connectivity_state | (1) dns:localhost:7051 CONNECTING -> TRANSIENT_FAILURE
D 2023-08-10T20:03:40.645Z | load_balancing_call | [2] ended with status: code=14 details="No connection established"
D 2023-08-10T20:03:40.645Z | retrying_call | [1] Received status from child [2]
D 2023-08-10T20:03:40.645Z | retrying_call | [1] state=TRANSPARENT_ONLY handling status with progress PROCESSED from child [2] in state ACTIVE
D 2023-08-10T20:03:40.645Z | retrying_call | [1] ended with status: code=14 details="No connection established"
D 2023-08-10T20:03:40.645Z | resolving_call | [0] Received status
D 2023-08-10T20:03:40.646Z | resolving_call | [0] ended with status: code=14 details="No connection established"
D 2023-08-10T20:03:40.647Z | subchannel_refcount | (2) ::1:7051 refcount 2 -> 1
D 2023-08-10T20:03:40.647Z | subchannel_refcount | (3) 127.0.0.1:7051 refcount 2 -> 1
D 2023-08-10T20:03:40.647Z | connectivity_state | (1) dns:localhost:7051 TRANSIENT_FAILURE -> SHUTDOWN
D 2023-08-10T20:03:40.647Z | subchannel_refcount | (2) ::1:7051 refcount 1 -> 0
D 2023-08-10T20:03:40.647Z | subchannel_refcount | (3) 127.0.0.1:7051 refcount 1 -> 0
/usr/src/app/node_modules/@hyperledger/fabric-gateway/dist/gatewayerror.js:39
return new GatewayError({
^
GatewayError: 14 UNAVAILABLE: No connection established
at newGatewayError (/usr/src/app/node_modules/@hyperledger/fabric-gateway/dist/gatewayerror.js:39:12)
at Object.callback (/usr/src/app/node_modules/@hyperledger/fabric-gateway/dist/client.js:101:67)
at Object.onReceiveStatus (/usr/src/app/node_modules/@grpc/grpc-js/build/src/client.js:192:36)
at Object.onReceiveStatus (/usr/src/app/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:360:141)
... 2 lines matching cause stack trace ...
at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
code: 14,
details: [],
cause: Error: 14 UNAVAILABLE: No connection established
at callErrorFromStatus (/usr/src/app/node_modules/@grpc/grpc-js/build/src/call.js:31:19)
at Object.onReceiveStatus (/usr/src/app/node_modules/@grpc/grpc-js/build/src/client.js:192:76)
at Object.onReceiveStatus (/usr/src/app/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:360:141)
at Object.onReceiveStatus (/usr/src/app/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:323:181)
at /usr/src/app/node_modules/@grpc/grpc-js/build/src/resolving-call.js:99:78
at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
for call at
at Client.makeUnaryRequest (/usr/src/app/node_modules/@grpc/grpc-js/build/src/client.js:160:32)
at /usr/src/app/node_modules/@hyperledger/fabric-gateway/dist/client.js:44:110
at new Promise (<anonymous>)
at GatewayClientImpl.evaluate (/usr/src/app/node_modules/@hyperledger/fabric-gateway/dist/client.js:44:16)
at ProposalImpl.evaluate (/usr/src/app/node_modules/@hyperledger/fabric-gateway/dist/proposal.js:50:96)
at async getAllAssets (/usr/src/app/dist/app.js:86:25)
at async /usr/src/app/dist/app.js:77:9 {
code: 14,
details: 'No connection established',
metadata: Metadata { internalRepr: Map(0) {}, options: {} }
}
}
有人遇到过类似的问题吗?我想要实现的是为我的节点服务器创建一个单独的 docker-compose 文件(它将集成 MongoDB 等其他服务),以便能够与我的结构网络进行通信。 我先感谢您的宝贵帮助。
我明白了
const peerEndpoint = envOrDefault('PEER_ENDPOINT', 'localhost:7051');
。localhost
指的是容器本身,而不是主机或任何其他容器,包括那些可能链接或连接的容器。
鉴于您当前的设置,您的 Fabric 网络和 Node.js 应用程序都在单独的容器中运行。因此,会出现错误
No connection established
,因为 Node.js 应用程序正在尝试连接到 localhost
,在此上下文中指的是 Node.js 应用程序容器本身,而不是 Fabric 对等容器。
将 Node.js 容器添加到 Fabric 网络时,请确保它们位于同一网络上。然后,您可以使用另一个容器的容器名称与其进行通信。例如,如果您的对等方的容器名称是
peer0.org1.example.com
,您将使用它作为主机名,而不是 localhost
。
如果您要在本机运行应用程序和在容器中运行应用程序之间切换,请考虑使用环境变量或配置文件参数化对等端点(以及可能的其他配置项)。这将允许您根据应用程序的运行位置轻松更改设置。
一个可能的 docker 组合可能是:
version: '3'
services:
app:
image: test
container_name: test
environment:
- PEER_ENDPOINT=peer1-org1:7051
# any other environment variables you need
ports:
- 8080:8080
volumes:
- /tmp/hyperledger:/tmp/hyperledger
networks:
- fabric_net
peer1-org1:
image: hyperledger/fabric-peer:latest
# your peer's configuration, environment, volumes, etc.
networks:
- fabric_net
# other services (orderers, other peers, CAs, etc.)
networks:
fabric_net: