org.hyperledger.fabric.shim.impl.InvocationStubImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fabric-chaincode-shim Show documentation
Show all versions of fabric-chaincode-shim Show documentation
Hyperledger Fabric Java Chaincode Shim
/*
* Copyright 2019 IBM All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.shim.impl;
import static java.util.stream.Collectors.toList;
import static org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage.Type.COMPLETED;
import static org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage.Type.GET_HISTORY_FOR_KEY;
import static org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage.Type.GET_PRIVATE_DATA_HASH;
import static org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage.Type.GET_QUERY_RESULT;
import static org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage.Type.GET_STATE_BY_RANGE;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.hyperledger.fabric.protos.common.Common;
import org.hyperledger.fabric.protos.common.Common.ChannelHeader;
import org.hyperledger.fabric.protos.common.Common.Header;
import org.hyperledger.fabric.protos.common.Common.HeaderType;
import org.hyperledger.fabric.protos.common.Common.SignatureHeader;
import org.hyperledger.fabric.protos.ledger.queryresult.KvQueryResult;
import org.hyperledger.fabric.protos.ledger.queryresult.KvQueryResult.KV;
import org.hyperledger.fabric.protos.peer.Chaincode.ChaincodeID;
import org.hyperledger.fabric.protos.peer.Chaincode.ChaincodeInput;
import org.hyperledger.fabric.protos.peer.Chaincode.ChaincodeSpec;
import org.hyperledger.fabric.protos.peer.ChaincodeEventPackage.ChaincodeEvent;
import org.hyperledger.fabric.protos.peer.ChaincodeShim;
import org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage;
import org.hyperledger.fabric.protos.peer.ChaincodeShim.GetQueryResult;
import org.hyperledger.fabric.protos.peer.ChaincodeShim.GetState;
import org.hyperledger.fabric.protos.peer.ChaincodeShim.GetStateByRange;
import org.hyperledger.fabric.protos.peer.ChaincodeShim.QueryResultBytes;
import org.hyperledger.fabric.protos.peer.ChaincodeShim.StateMetadataResult;
import org.hyperledger.fabric.protos.peer.ProposalPackage.ChaincodeProposalPayload;
import org.hyperledger.fabric.protos.peer.ProposalPackage.Proposal;
import org.hyperledger.fabric.protos.peer.ProposalPackage.SignedProposal;
import org.hyperledger.fabric.protos.peer.ProposalResponsePackage;
import org.hyperledger.fabric.protos.peer.TransactionPackage;
import org.hyperledger.fabric.shim.Chaincode;
import org.hyperledger.fabric.shim.Chaincode.Response;
import org.hyperledger.fabric.shim.ChaincodeStub;
import org.hyperledger.fabric.shim.ledger.CompositeKey;
import org.hyperledger.fabric.shim.ledger.KeyModification;
import org.hyperledger.fabric.shim.ledger.KeyValue;
import org.hyperledger.fabric.shim.ledger.QueryResultsIterator;
import org.hyperledger.fabric.shim.ledger.QueryResultsIteratorWithMetadata;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Timestamp;
class InvocationStubImpl implements ChaincodeStub {
private static final String UNSPECIFIED_KEY = new String(Character.toChars(0x000001));
private static final Logger LOGGER = Logger.getLogger(InvocationStubImpl.class.getName());
public static final String MAX_UNICODE_RUNE = "\udbff\udfff";
private final String channelId;
private final String txId;
private final ChaincodeInvocationTask handler;
private final List args;
private final SignedProposal signedProposal;
private final Instant txTimestamp;
private final ByteString creator;
private final Map transientMap;
private final byte[] binding;
private ChaincodeEvent event;
/**
*
* @param message
* @param handler
* @throws InvalidProtocolBufferException
*/
InvocationStubImpl(final ChaincodeMessage message, final ChaincodeInvocationTask handler) throws InvalidProtocolBufferException {
this.channelId = message.getChannelId();
this.txId = message.getTxid();
this.handler = handler;
final ChaincodeInput input = ChaincodeInput.parseFrom(message.getPayload());
this.args = Collections.unmodifiableList(input.getArgsList());
this.signedProposal = message.getProposal();
if (this.signedProposal == null || this.signedProposal.getProposalBytes().isEmpty()) {
this.creator = null;
this.txTimestamp = null;
this.transientMap = Collections.emptyMap();
this.binding = null;
} else {
try {
final Proposal proposal = Proposal.parseFrom(signedProposal.getProposalBytes());
final Header header = Header.parseFrom(proposal.getHeader());
final ChannelHeader channelHeader = ChannelHeader.parseFrom(header.getChannelHeader());
validateProposalType(channelHeader);
final SignatureHeader signatureHeader = SignatureHeader.parseFrom(header.getSignatureHeader());
final ChaincodeProposalPayload chaincodeProposalPayload = ChaincodeProposalPayload.parseFrom(proposal.getPayload());
final Timestamp timestamp = channelHeader.getTimestamp();
this.txTimestamp = Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos());
this.creator = signatureHeader.getCreator();
this.transientMap = chaincodeProposalPayload.getTransientMapMap();
this.binding = computeBinding(channelHeader, signatureHeader);
} catch (InvalidProtocolBufferException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
private byte[] computeBinding(final ChannelHeader channelHeader, final SignatureHeader signatureHeader) throws NoSuchAlgorithmException {
final MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(signatureHeader.getNonce().asReadOnlyByteBuffer());
messageDigest.update(this.creator.asReadOnlyByteBuffer());
final ByteBuffer epochBytes = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN).putLong(channelHeader.getEpoch());
epochBytes.flip();
messageDigest.update(epochBytes);
return messageDigest.digest();
}
private void validateProposalType(final ChannelHeader channelHeader) {
switch (Common.HeaderType.forNumber(channelHeader.getType())) {
case ENDORSER_TRANSACTION:
case CONFIG:
return;
default:
throw new RuntimeException(String.format("Unexpected transaction type: %s", HeaderType.forNumber(channelHeader.getType())));
}
}
@Override
public List getArgs() {
return args.stream().map(x -> x.toByteArray()).collect(Collectors.toList());
}
@Override
public List getStringArgs() {
return args.stream().map(x -> x.toStringUtf8()).collect(Collectors.toList());
}
@Override
public String getFunction() {
return getStringArgs().size() > 0 ? getStringArgs().get(0) : null;
}
@Override
public List getParameters() {
return getStringArgs().stream().skip(1).collect(toList());
}
@Override
public void setEvent(final String name, final byte[] payload) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("event name can not be nil string");
}
if (payload != null) {
this.event = ChaincodeEvent.newBuilder().setEventName(name).setPayload(ByteString.copyFrom(payload)).build();
} else {
this.event = ChaincodeEvent.newBuilder().setEventName(name).build();
}
}
@Override
public ChaincodeEvent getEvent() {
return event;
}
@Override
public String getChannelId() {
return channelId;
}
@Override
public String getTxId() {
return txId;
}
@Override
public byte[] getState(final String key) {
return this.handler.invoke(ChaincodeMessageFactory.newGetStateEventMessage(channelId, txId, "", key)).toByteArray();
}
@Override
public byte[] getStateValidationParameter(final String key) {
final ByteString payload = handler.invoke(ChaincodeMessageFactory.newGetStateMetadataEventMessage(channelId, txId, "", key));
try {
final StateMetadataResult stateMetadataResult = StateMetadataResult.parseFrom(payload);
final Map stateMetadataMap = new HashMap<>();
stateMetadataResult.getEntriesList().forEach(entry -> stateMetadataMap.put(entry.getMetakey(), entry.getValue()));
if (stateMetadataMap.containsKey(TransactionPackage.MetaDataKeys.VALIDATION_PARAMETER.toString())) {
return stateMetadataMap.get(TransactionPackage.MetaDataKeys.VALIDATION_PARAMETER.toString()).toByteArray();
}
} catch (final InvalidProtocolBufferException e) {
LOGGER.severe(String.format("[%-8.8s] unmarshalling error", txId));
throw new RuntimeException("Error unmarshalling StateMetadataResult.", e);
}
return null;
}
@Override
public void putState(final String key, final byte[] value) {
validateKey(key);
this.handler.invoke(ChaincodeMessageFactory.newPutStateEventMessage(channelId, txId, "", key, ByteString.copyFrom(value)));
}
@Override
public void setStateValidationParameter(final String key, final byte[] value) {
validateKey(key);
final ChaincodeMessage msg = ChaincodeMessageFactory.newPutStateMetadataEventMessage(channelId, txId, "", key,
TransactionPackage.MetaDataKeys.VALIDATION_PARAMETER.toString(), ByteString.copyFrom(value));
this.handler.invoke(msg);
}
@Override
public void delState(final String key) {
final ChaincodeMessage msg = ChaincodeMessageFactory.newDeleteStateEventMessage(channelId, txId, "", key);
this.handler.invoke(msg);
}
@Override
public QueryResultsIterator getStateByRange(final String startKey, final String endKey) {
String start = startKey;
String end = endKey;
if (startKey == null || startKey.isEmpty()) {
start = UNSPECIFIED_KEY;
}
if (endKey == null || endKey.isEmpty()) {
end = UNSPECIFIED_KEY;
}
CompositeKey.validateSimpleKeys(start, end);
return executeGetStateByRange("", start, end);
}
private QueryResultsIterator executeGetStateByRange(final String collection, final String startKey, final String endKey) {
final ByteString requestPayload = GetStateByRange.newBuilder().setCollection(collection).setStartKey(startKey).setEndKey(endKey).build().toByteString();
final ChaincodeMessage requestMessage = ChaincodeMessageFactory.newEventMessage(GET_STATE_BY_RANGE, channelId, txId, requestPayload);
final ByteString response = handler.invoke(requestMessage);
return new QueryResultsIteratorImpl(this.handler, channelId, txId, response, queryResultBytesToKv.andThen(KeyValueImpl::new));
}
private final Function queryResultBytesToKv = new Function() {
@Override
public KV apply(final QueryResultBytes queryResultBytes) {
try {
return KV.parseFrom(queryResultBytes.getResultBytes());
} catch (final InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
}
};
@Override
public QueryResultsIteratorWithMetadata getStateByRangeWithPagination(final String startKey,
final String endKey, final int pageSize, final String bookmark) {
String start = startKey;
String end = endKey;
if (startKey == null || startKey.isEmpty()) {
start = UNSPECIFIED_KEY;
}
if (endKey == null || endKey.isEmpty()) {
end = UNSPECIFIED_KEY;
}
CompositeKey.validateSimpleKeys(start, end);
final ChaincodeShim.QueryMetadata queryMetadata = ChaincodeShim.QueryMetadata.newBuilder().setBookmark(bookmark).setPageSize(pageSize).build();
return executeGetStateByRangeWithMetadata("", start, end, queryMetadata.toByteString());
}
private QueryResultsIteratorWithMetadataImpl executeGetStateByRangeWithMetadata(final String collection, final String startKey,
final String endKey, final ByteString metadata) {
final ByteString payload = GetStateByRange.newBuilder().setCollection(collection).setStartKey(startKey).setEndKey(endKey).setMetadata(metadata).build()
.toByteString();
final ChaincodeMessage requestMessage = ChaincodeMessageFactory.newEventMessage(GET_STATE_BY_RANGE, channelId, txId,
payload);
final ByteString response = this.handler.invoke(requestMessage);
return new QueryResultsIteratorWithMetadataImpl<>(this.handler, getChannelId(), getTxId(), response, queryResultBytesToKv.andThen(KeyValueImpl::new));
}
@Override
public QueryResultsIterator getStateByPartialCompositeKey(final String compositeKey) {
CompositeKey key;
if (compositeKey.startsWith(CompositeKey.NAMESPACE)) {
key = CompositeKey.parseCompositeKey(compositeKey);
} else {
key = new CompositeKey(compositeKey);
}
return getStateByPartialCompositeKey(key);
}
@Override
public QueryResultsIterator getStateByPartialCompositeKey(final String objectType, final String... attributes) {
return getStateByPartialCompositeKey(new CompositeKey(objectType, attributes));
}
@Override
public QueryResultsIterator getStateByPartialCompositeKey(final CompositeKey compositeKey) {
String cKeyAsString;
if (compositeKey == null) {
cKeyAsString = new CompositeKey(UNSPECIFIED_KEY).toString();
} else {
cKeyAsString = compositeKey.toString();
}
return executeGetStateByRange("", cKeyAsString, cKeyAsString + MAX_UNICODE_RUNE);
}
@Override
public QueryResultsIteratorWithMetadata getStateByPartialCompositeKeyWithPagination(final CompositeKey compositeKey, final int pageSize,
final String bookmark) {
String cKeyAsString;
if (compositeKey == null) {
cKeyAsString = new CompositeKey(UNSPECIFIED_KEY).toString();
} else {
cKeyAsString = compositeKey.toString();
}
final ChaincodeShim.QueryMetadata queryMetadata = ChaincodeShim.QueryMetadata.newBuilder().setBookmark(bookmark).setPageSize(pageSize).build();
return executeGetStateByRangeWithMetadata("", cKeyAsString, cKeyAsString + MAX_UNICODE_RUNE, queryMetadata.toByteString());
}
@Override
public CompositeKey createCompositeKey(final String objectType, final String... attributes) {
return new CompositeKey(objectType, attributes);
}
@Override
public CompositeKey splitCompositeKey(final String compositeKey) {
return CompositeKey.parseCompositeKey(compositeKey);
}
@Override
public QueryResultsIterator getQueryResult(final String query) {
final ByteString requestPayload = GetQueryResult.newBuilder().setCollection("").setQuery(query).build().toByteString();
final ChaincodeMessage requestMessage = ChaincodeMessageFactory.newEventMessage(GET_QUERY_RESULT, channelId, txId, requestPayload);
final ByteString response = handler.invoke(requestMessage);
return new QueryResultsIteratorImpl(this.handler, channelId, txId, response, queryResultBytesToKv.andThen(KeyValueImpl::new));
}
@Override
public QueryResultsIteratorWithMetadata getQueryResultWithPagination(final String query, final int pageSize, final String bookmark) {
final ByteString queryMetadataPayload = ChaincodeShim.QueryMetadata.newBuilder().setBookmark(bookmark).setPageSize(pageSize).build().toByteString();
final ByteString requestPayload = GetQueryResult.newBuilder().setCollection("").setQuery(query).setMetadata(queryMetadataPayload).build()
.toByteString();
final ChaincodeMessage requestMessage = ChaincodeMessageFactory.newEventMessage(GET_QUERY_RESULT, channelId, txId, requestPayload);
final ByteString response = handler.invoke(requestMessage);
return new QueryResultsIteratorWithMetadataImpl(this.handler, channelId, txId, response, queryResultBytesToKv.andThen(KeyValueImpl::new));
}
@Override
public QueryResultsIterator getHistoryForKey(final String key) {
final ByteString requestPayload = GetQueryResult.newBuilder().setCollection("").setQuery(key).build().toByteString();
final ChaincodeMessage requestMessage = ChaincodeMessageFactory.newEventMessage(GET_HISTORY_FOR_KEY, channelId, txId, requestPayload);
final ByteString response = handler.invoke(requestMessage);
return new QueryResultsIteratorImpl(this.handler, channelId, txId, response,
queryResultBytesToKeyModification.andThen(KeyModificationImpl::new));
}
private final Function queryResultBytesToKeyModification =
new Function() {
@Override
public KvQueryResult.KeyModification apply(final QueryResultBytes queryResultBytes) {
try {
return KvQueryResult.KeyModification.parseFrom(queryResultBytes.getResultBytes());
} catch (final InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
}
};
@Override
public byte[] getPrivateData(final String collection, final String key) {
validateCollection(collection);
return this.handler.invoke(ChaincodeMessageFactory.newGetStateEventMessage(channelId, txId, collection, key)).toByteArray();
}
@Override
public byte[] getPrivateDataHash(final String collection, final String key) {
validateCollection(collection);
final ByteString requestPayload = GetState.newBuilder().setCollection(collection).setKey(key).build().toByteString();
final ChaincodeMessage requestMessage = ChaincodeMessageFactory.newEventMessage(GET_PRIVATE_DATA_HASH, channelId, txId, requestPayload);
return handler.invoke(requestMessage).toByteArray();
}
@Override
public byte[] getPrivateDataValidationParameter(final String collection, final String key) {
validateCollection(collection);
final ByteString payload = handler.invoke(ChaincodeMessageFactory.newGetStateMetadataEventMessage(channelId, txId, collection, key));
try {
final StateMetadataResult stateMetadataResult = StateMetadataResult.parseFrom(payload);
final Map stateMetadataMap = new HashMap<>();
stateMetadataResult.getEntriesList().forEach(entry -> stateMetadataMap.put(entry.getMetakey(), entry.getValue()));
if (stateMetadataMap.containsKey(TransactionPackage.MetaDataKeys.VALIDATION_PARAMETER.toString())) {
return stateMetadataMap.get(TransactionPackage.MetaDataKeys.VALIDATION_PARAMETER.toString()).toByteArray();
}
} catch (final InvalidProtocolBufferException e) {
LOGGER.severe(String.format("[%-8.8s] unmarshalling error", txId));
throw new RuntimeException("Error unmarshalling StateMetadataResult.", e);
}
return null;
}
@Override
public void putPrivateData(final String collection, final String key, final byte[] value) {
validateKey(key);
validateCollection(collection);
this.handler.invoke(ChaincodeMessageFactory.newPutStateEventMessage(channelId, txId, collection, key, ByteString.copyFrom(value)));
}
@Override
public void setPrivateDataValidationParameter(final String collection, final String key, final byte[] value) {
validateKey(key);
validateCollection(collection);
final ChaincodeMessage msg = ChaincodeMessageFactory.newPutStateMetadataEventMessage(channelId, txId, collection, key,
TransactionPackage.MetaDataKeys.VALIDATION_PARAMETER.toString(), ByteString.copyFrom(value));
this.handler.invoke(msg);
}
@Override
public void delPrivateData(final String collection, final String key) {
validateCollection(collection);
final ChaincodeMessage msg = ChaincodeMessageFactory.newDeleteStateEventMessage(channelId, txId, collection, key);
this.handler.invoke(msg);
}
@Override
public QueryResultsIterator getPrivateDataByRange(final String collection, final String startKey, final String endKey) {
String start = startKey;
String end = endKey;
validateCollection(collection);
if (startKey == null || startKey.isEmpty()) {
start = UNSPECIFIED_KEY;
}
if (endKey == null || endKey.isEmpty()) {
end = UNSPECIFIED_KEY;
}
CompositeKey.validateSimpleKeys(start, end);
return executeGetStateByRange(collection, start, end);
}
@Override
public QueryResultsIterator getPrivateDataByPartialCompositeKey(final String collection, final String compositeKey) {
CompositeKey key;
if (compositeKey == null) {
key = new CompositeKey("");
} else if (compositeKey.startsWith(CompositeKey.NAMESPACE)) {
key = CompositeKey.parseCompositeKey(compositeKey);
} else {
key = new CompositeKey(compositeKey);
}
return getPrivateDataByPartialCompositeKey(collection, key);
}
@Override
public QueryResultsIterator getPrivateDataByPartialCompositeKey(final String collection, final CompositeKey compositeKey) {
String cKeyAsString;
if (compositeKey == null) {
cKeyAsString = new CompositeKey(UNSPECIFIED_KEY).toString();
} else {
cKeyAsString = compositeKey.toString();
}
return executeGetStateByRange(collection, cKeyAsString, cKeyAsString + MAX_UNICODE_RUNE);
}
@Override
public QueryResultsIterator getPrivateDataByPartialCompositeKey(final String collection, final String objectType, final String... attributes) {
return getPrivateDataByPartialCompositeKey(collection, new CompositeKey(objectType, attributes));
}
@Override
public QueryResultsIterator getPrivateDataQueryResult(final String collection, final String query) {
validateCollection(collection);
final ByteString requestPayload = GetQueryResult.newBuilder().setCollection(collection).setQuery(query).build().toByteString();
final ChaincodeMessage requestMessage = ChaincodeMessageFactory.newEventMessage(GET_QUERY_RESULT, channelId, txId, requestPayload);
final ByteString response = handler.invoke(requestMessage);
return new QueryResultsIteratorImpl(this.handler, channelId, txId, response, queryResultBytesToKv.andThen(KeyValueImpl::new));
}
@Override
public Response invokeChaincode(final String chaincodeName, final List args, final String channel) {
// internally we handle chaincode name as a composite name
final String compositeName;
if (channel != null && !channel.trim().isEmpty()) {
compositeName = chaincodeName + "/" + channel;
} else {
compositeName = chaincodeName;
}
// create invocation specification of the chaincode to invoke
final ByteString invocationSpecPayload = ChaincodeSpec.newBuilder().setChaincodeId(ChaincodeID.newBuilder().setName(compositeName).build())
.setInput(ChaincodeInput.newBuilder().addAllArgs(args.stream().map(ByteString::copyFrom).collect(Collectors.toList())).build()).build()
.toByteString();
final ChaincodeMessage invokeChaincodeMessage = ChaincodeMessageFactory.newInvokeChaincodeMessage(this.channelId, this.txId, invocationSpecPayload);
final ByteString response = this.handler.invoke(invokeChaincodeMessage);
try {
// response message payload should be yet another chaincode
// message (the actual response message)
final ChaincodeMessage responseMessage = ChaincodeMessage.parseFrom(response);
// the actual response message must be of type COMPLETED
LOGGER.fine(String.format("[%-8.8s] %s response received from other chaincode.", txId, responseMessage.getType()));
if (responseMessage.getType() == COMPLETED) {
// success
final ProposalResponsePackage.Response r = ProposalResponsePackage.Response.parseFrom(responseMessage.getPayload());
return new Chaincode.Response(Chaincode.Response.Status.forCode(r.getStatus()), r.getMessage(),
r.getPayload() == null ? null : r.getPayload().toByteArray());
} else {
// error
final String message = responseMessage.getPayload().toStringUtf8();
return new Chaincode.Response(Chaincode.Response.Status.INTERNAL_SERVER_ERROR, message, null);
}
} catch (final InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
}
@Override
public SignedProposal getSignedProposal() {
return signedProposal;
}
@Override
public Instant getTxTimestamp() {
return txTimestamp;
}
@Override
public byte[] getCreator() {
if (creator == null) {
return null;
}
return creator.toByteArray();
}
@Override
public Map getTransient() {
return transientMap.entrySet().stream().collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue().toByteArray()));
}
@Override
public byte[] getBinding() {
return this.binding;
}
private void validateKey(final String key) {
if (key == null) {
throw new NullPointerException("key cannot be null");
}
if (key.length() == 0) {
throw new IllegalArgumentException("key cannot not be an empty string");
}
}
private void validateCollection(final String collection) {
if (collection == null) {
throw new NullPointerException("collection cannot be null");
}
if (collection.isEmpty()) {
throw new IllegalArgumentException("collection must not be an empty string");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy