Skip to content

Commit

Permalink
added purgePrivateData function with tests
Browse files Browse the repository at this point in the history
Signed-off-by: fraVlaca <ocsenarf@outlook.com>
  • Loading branch information
fraVlaca authored and jt-nti committed Jul 14, 2022
1 parent 501af57 commit ffa075e
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 6 deletions.
1 change: 1 addition & 0 deletions apis/fabric-shim-api/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ declare module 'fabric-shim-api' {
getPrivateDataHash(collection: string, key: string): Promise<Uint8Array>;
putPrivateData(collection: string, key: string, value: Uint8Array): Promise<void>;
deletePrivateData(collection: string, key: string): Promise<void>;
purgePrivateData(collection: string, key: string): Promise<void>;
setPrivateDataValidationParameter(collection: string, key: string, ep: Uint8Array): Promise<void>;
getPrivateDataValidationParameter(collection: string, key: string): Promise<Uint8Array>;
getPrivateDataByRange(collection: string, startKey: string, endKey: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;
Expand Down
3 changes: 2 additions & 1 deletion ci/azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ variables:
- name: rewire_node_version_spec # see https://github.com/jhnns/rewire/issues/178
value: '16.4.0'
- name: FABRIC_VERSION
value: 2.4
value: 2.5

# Build on Ubuntu
pool:
Expand Down Expand Up @@ -147,6 +147,7 @@ stages:
set -ev
./tools/getEdgeDocker.sh # essential to get main docker images of peer etc.
docker image load --input build/fabric-nodeenv.tar.gz # gets the build image of nodeenv
docker tag hyperledger/fabric-nodeenv:latest hyperledger/fabric-nodeenv:$(FABRIC_VERSION)
docker images
node common/scripts/install-run-rush.js install
node common/scripts/install-run-rush.js update # should the tests need 'building' this will need to go here
Expand Down
2 changes: 1 addition & 1 deletion common/config/rush/command-line.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"name": "start-fabric",
"summary": "Starts local Fabric test network ",
"description": "Run this command to start local Fabric network for testing",
"shellCommand": "rm -rf ./fabric-samples && curl -sSL https://bit.ly/2ysbOFE | bash -s -- 2.4.0-beta 1.5.1 && cd ./fabric-samples/test-network && ./network.sh down && ./network.sh up createChannel -ca -s couchdb && cd -"
"shellCommand": "rm -rf ./fabric-samples && curl -sSL https://bit.ly/2ysbOFE | bash -s -- 2.4.5 1.5.5 -d && cd ./fabric-samples/test-network && ./network.sh down && ./network.sh up createChannel -ca -s couchdb && cd -"
},
{
"commandKind": "global",
Expand Down
13 changes: 13 additions & 0 deletions libraries/fabric-shim/lib/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,19 @@ class ChaincodeMessageHandler {
return await this._askPeerAndListen(msg, 'DeleteState');
}

async handlePurgeState(collection, key, channel_id, txId) {
const msgPb = new peer.PurgePrivateState();
msgPb.setKey(key);
msgPb.setCollection(collection);
const msg = mapToChaincodeMessage({
type: peer.ChaincodeMessage.Type.PURGE_PRIVATE_DATA,
payload: msgPb.serializeBinary(),
txid: txId,
channel_id: channel_id
});
return await this._askPeerAndListen(msg, 'PurgePrivateState');
}

async handlePutStateMetadata(collection, key, metakey, ep, channel_id, txId) {
const msgPb = new peer.PutStateMetadata();
msgPb.setCollection(collection);
Expand Down
24 changes: 24 additions & 0 deletions libraries/fabric-shim/lib/stub.js
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,30 @@ class ChaincodeStub {
return this.handler.handleDeleteState(collection, key, this.channel_id, this.txId);
}

/**
* PurgePrivateData records the specified `key` to be purged in the private writeset
* of the transaction. Note that only hash of the private writeset goes into the
* transaction proposal response (which is sent to the client who issued the
* transaction) and the actual private writeset gets temporarily stored in a
* transient store. The `key` and its value will be deleted from the collection
* when the transaction is validated and successfully committed, and will
* subsequently be completely removed from the private data store (that maintains
* the historical versions of private writesets) as a background operation.
* @param {string} collection The collection name
* @param {string} key Private data variable key to delete from the state store
*/
async purgePrivateData(collection, key) {
// Access public data by setting the collection to empty string
logger.debug('purgePrivateData called with collection:%s, key:%s', collection, key);
if (!collection || typeof collection !== 'string') {
throw new Error('collection must be a valid string');
}
if (!key || typeof key !== 'string') {
throw new Error('key must be a valid string');
}
return await this.handler.handlePurgeState(collection, key, this.channel_id, this.txId);
}

/**
* SetPrivateDataValidationParameter sets the key-level endorsement policy
* for the private data specified by `key`.
Expand Down
2 changes: 2 additions & 0 deletions libraries/fabric-shim/test/typescript/chaincode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ class TestTS implements ChaincodeInterface {
await putPrivData;
const delPrivateData: Promise<void> = stub.deletePrivateData(collection, key);
await delPrivateData;
const purgePrivData: Promise<void> = stub.purgePrivateData(collection, key);
await purgePrivData;

}

Expand Down
50 changes: 50 additions & 0 deletions libraries/fabric-shim/test/unit/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,56 @@ describe('Handler', () => {
});
});

describe('handlePurgeState', () => {
const key = 'theKey';
const collection = '';

let expectedMsg;

before(() => {
const payloadPb = new peer.PurgePrivateState();
payloadPb.setKey(key);
payloadPb.setCollection(collection);
expectedMsg = mapToChaincodeMessage({
type: peer.ChaincodeMessage.Type.PURGE_PRIVATE_DATA,
payload: payloadPb.serializeBinary(),
channel_id: 'theChannelID',
txid: 'theTxID'
});
});

afterEach(() => {
Handler = rewire('../../../fabric-shim/lib/handler.js');
sandbox.restore();
});

it ('should resolve when _askPeerAndListen resolves', async () => {
const mockStream = {write: sinon.stub(), end: sinon.stub()};
const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');

const result = await handler.handlePurgeState(collection, key, 'theChannelID', 'theTxID');

expect(result).to.deep.equal('some response');
expect(_askPeerAndListenStub.firstCall.args.length).to.deep.equal(2);
expect(_askPeerAndListenStub.firstCall.args[0]).to.deep.equal(expectedMsg);
expect(_askPeerAndListenStub.firstCall.args[1]).to.deep.equal('PurgePrivateState');
});

it ('should reject when _askPeerAndListen rejects', async () => {
const mockStream = {write: sinon.stub(), end: sinon.stub()};
const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();

const result = handler.handlePurgeState(collection, key, 'theChannelID', 'theTxID');

await expect(result).to.eventually.be.rejected;
expect(_askPeerAndListenStub.firstCall.args.length).to.deep.equal(2);
expect(_askPeerAndListenStub.firstCall.args[0]).to.deep.equal(expectedMsg);
expect(_askPeerAndListenStub.firstCall.args[1]).to.deep.equal('PurgePrivateState');
});
});

describe('handlePutStateMetadata', () => {
const key = 'theKey';
const collection = '';
Expand Down
35 changes: 35 additions & 0 deletions libraries/fabric-shim/test/unit/stub.js
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,41 @@ describe('Stub', () => {
});
});

describe('purgePrivateData', () => {
let handlePurgeStateStub;
let stub;

beforeEach(() => {
handlePurgeStateStub = sinon.stub().resolves('some state');
stub = new Stub({
handlePurgeState: handlePurgeStateStub
}, 'dummyChannelId', 'dummyTxid', chaincodeInput);
});

it ('should throw an error if no arguments supplied', async () => {
const result = stub.purgePrivateData();
await expect(result).to.eventually.be.rejectedWith(Error, 'collection must be a valid string');
});

it ('should throw an error if one argument supplied', async () => {
const result = stub.purgePrivateData('some arg');
await expect(result).to.eventually.be.rejectedWith(Error, 'key must be a valid string');
});

it ('should throw an error if collection null', async () => {
const result = stub.purgePrivateData(null, 'some key');
await expect(result).to.eventually.be.rejectedWith(Error, 'collection must be a valid string');
});

it ('should return handler.handlePurgeState', async () => {
const result = await stub.purgePrivateData('some collection', 'some key');

expect(result).to.deep.equal('some state');
expect(handlePurgeStateStub.calledOnce).to.be.true;
expect(handlePurgeStateStub.firstCall.args).to.deep.equal(['some collection', 'some key', 'dummyChannelId', 'dummyTxid']);
});
});

describe('setPrivateDataValidationParameter', () => {
it('should return handler.handlePutStateMetadata', async () => {
const handlePutStateMetadataStub = sinon.stub().resolves('nothing');
Expand Down
1 change: 1 addition & 0 deletions libraries/fabric-shim/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ declare module 'fabric-shim' {
getPrivateDataHash(collection: string, key: string): Promise<Uint8Array>;
putPrivateData(collection: string, key: string, value: Uint8Array): Promise<void>;
deletePrivateData(collection: string, key: string): Promise<void>;
purgePrivateData(collection: string, key: string): Promise<void>;
setPrivateDataValidationParameter(collection: string, key: string, ep: Uint8Array): Promise<void>;
getPrivateDataValidationParameter(collection: string, key: string): Promise<Uint8Array>;
getPrivateDataByRange(collection: string, startKey: string, endKey: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;
Expand Down
8 changes: 8 additions & 0 deletions test/chaincodes/privateData/chaincode.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ class privateDataContract extends Contract {
await ctx.stub.deletePrivateData("collection", assetId);
}

async purgeAsset(ctx, assetId) {
const exists = await this.assetExists(ctx, assetId);
if (!exists) {
throw new Error(`The asset asset ${assetId} does not exist`);
}
await ctx.stub.purgePrivateData("collection", assetId);
}

async verifyAsset(ctx, mspid, assetId, objectToVerify) {

const hashToVerify = crypto.createHash('sha256').update(objectToVerify).digest('hex');
Expand Down
11 changes: 11 additions & 0 deletions test/fv/privateData.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,15 @@ describe('Chaincode privateData', () => {
expect(payload).to.eql('false');
});

it('should call purge private data without failing', async function () {
this.timeout(MED_STEP);
const privateData = Buffer.from('privateData').toString('base64');
await utils.invoke(suite, 'privateDataContract:createAsset', ['4'], `{"privateValue":"${privateData}"}`);
let payload = await utils.query(suite, 'privateDataContract:readAsset', ['4']);
expect(payload).to.eql('{"privateValue":"privateData"}');
console.log(await utils.invoke(suite, 'privateDataContract:purgeAsset', ['4']));
payload = await utils.query(suite, 'privateDataContract:assetExists', ['4']);
expect(payload).to.eql('false');
});

});
8 changes: 4 additions & 4 deletions tools/getEdgeDocker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#
set -euo pipefail

version=${FABRIC_VERSION:-2.4}
version=${FABRIC_VERSION:-2.5}
artifactory_url=hyperledger-fabric.jfrog.io

for image in peer orderer ca baseos ccenv tools; do
Expand All @@ -17,6 +17,6 @@ for image in peer orderer ca baseos ccenv tools; do
done

docker pull -q couchdb:3.1
docker pull -q hyperledger/fabric-ca:1.5
docker tag hyperledger/fabric-ca:1.5 hyperledger/fabric-ca
docker rmi hyperledger/fabric-ca:1.5 >/dev/null
docker pull -q hyperledger/fabric-ca:1.5.5
docker tag hyperledger/fabric-ca:1.5.5 hyperledger/fabric-ca
docker rmi hyperledger/fabric-ca:1.5.5 >/dev/null

0 comments on commit ffa075e

Please sign in to comment.