Skip to content

Commit c560719

Browse files
authored
feat: implement stub API fallbacks for read/write batching. Added fallback methods, updated types, and achieved 100% coverage. Ref: #453 (#497)
Signed-off-by: Sourav <souravkjha2007@gmail.com>
1 parent 5ec1616 commit c560719

File tree

3 files changed

+134
-0
lines changed

3 files changed

+134
-0
lines changed

libraries/fabric-shim/lib/stub.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,62 @@ class ChaincodeStub {
937937
return await this.handler.handleGetState(collection, key, this.channel_id, this.txId);
938938
}
939939

940+
/**
941+
* Retrieves the current values of the state variables for multiple keys.
942+
* This is a fallback implementation that processes reads without actual gRPC batching.
943+
* @async
944+
* @param {string[]} keys Array of state variable keys to retrieve from the state store
945+
* @returns {Promise<byte[][]>} Promise for an array of the current values of the state variables
946+
*/
947+
async getMultipleStates(keys) {
948+
logger.debug('getMultipleStates called with keys:%j', keys);
949+
if (!Array.isArray(keys)) {
950+
throw new Error('keys must be an array of strings');
951+
}
952+
953+
const promises = keys.map(key => this.getState(key));
954+
return await Promise.all(promises);
955+
}
956+
957+
/**
958+
* getMultiplePrivateData returns the values of the specified `keys` from the specified `collection`.
959+
* This is a fallback implementation that processes reads without actual gRPC batching.
960+
* @async
961+
* @param {string} collection The collection name
962+
* @param {string[]} keys Array of private data variable keys to retrieve from the state store
963+
* @returns {Promise<byte[][]>} Promise for an array of private values from the state store
964+
*/
965+
async getMultiplePrivateData(collection, keys) {
966+
logger.debug('getMultiplePrivateData called with collection:%s, keys:%j', collection, keys);
967+
if (!collection || typeof collection !== 'string') {
968+
throw new Error('collection must be a valid string');
969+
}
970+
if (!Array.isArray(keys)) {
971+
throw new Error('keys must be an array of strings');
972+
}
973+
974+
const promises = keys.map(key => this.getPrivateData(collection, key));
975+
return await Promise.all(promises);
976+
}
977+
978+
/**
979+
* startWriteBatch indicates the beginning of a block of PutState/PutPrivateData calls
980+
* that should be batched together.
981+
* (Fallback behavior: no-op)
982+
*/
983+
startWriteBatch() {
984+
logger.debug('startWriteBatch called');
985+
}
986+
987+
/**
988+
* finishWriteBatch sends the currently accumulated batch of state writes to the peer.
989+
* (Fallback behavior: no-op)
990+
* @async
991+
*/
992+
async finishWriteBatch() {
993+
logger.debug('finishWriteBatch called');
994+
}
995+
940996
/**
941997
* getPrivateDataHash returns the hash of the value of the specified `key` from
942998
* the specified `collection`.

libraries/fabric-shim/test/unit/stub.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,79 @@ describe('Stub', () => {
11031103
});
11041104
});
11051105

1106+
describe('getMultipleStates', () => {
1107+
let stub;
1108+
1109+
beforeEach(() => {
1110+
stub = new Stub({
1111+
handleGetState: sinon.stub().resolves(Buffer.from('value'))
1112+
}, 'dummyChannelId', 'dummyTxid', chaincodeInput);
1113+
});
1114+
1115+
it('should throw an error if keys is not an array', async () => {
1116+
await expect(stub.getMultipleStates('not-an-array')).to.be.rejectedWith(/keys must be an array of strings/);
1117+
});
1118+
1119+
it('should call getState for each key and return the results', async () => {
1120+
sandbox.stub(stub, 'getState').callsFake(async (key) => {
1121+
if (key === 'key1') {
1122+
return Buffer.from('value1');
1123+
} else if (key === 'key2') {
1124+
return Buffer.from('value2');
1125+
}
1126+
});
1127+
1128+
const results = await stub.getMultipleStates(['key1', 'key2']);
1129+
expect(results).to.deep.equal([Buffer.from('value1'), Buffer.from('value2')]);
1130+
sinon.assert.calledTwice(stub.getState);
1131+
});
1132+
});
1133+
1134+
describe('getMultiplePrivateData', () => {
1135+
let stub;
1136+
1137+
beforeEach(() => {
1138+
stub = new Stub({
1139+
handleGetState: sinon.stub().resolves(Buffer.from('value'))
1140+
}, 'dummyChannelId', 'dummyTxid', chaincodeInput);
1141+
});
1142+
1143+
it('should throw an error if collection is missing', async () => {
1144+
await expect(stub.getMultiplePrivateData(null, ['key1'])).to.be.rejectedWith(/collection must be a valid string/);
1145+
});
1146+
1147+
it('should throw an error if keys is not an array', async () => {
1148+
await expect(stub.getMultiplePrivateData('collection', 'not-an-array')).to.be.rejectedWith(/keys must be an array of strings/);
1149+
});
1150+
1151+
it('should call getPrivateData for each key and return the results', async () => {
1152+
sandbox.stub(stub, 'getPrivateData').callsFake(async (collection, key) => {
1153+
if (key === 'key1') {
1154+
return Buffer.from('value1');
1155+
} else if (key === 'key2') {
1156+
return Buffer.from('value2');
1157+
}
1158+
});
1159+
1160+
const results = await stub.getMultiplePrivateData('collection', ['key1', 'key2']);
1161+
expect(results).to.deep.equal([Buffer.from('value1'), Buffer.from('value2')]);
1162+
sinon.assert.calledTwice(stub.getPrivateData);
1163+
});
1164+
});
1165+
1166+
describe('Write Batching Fallbacks', () => {
1167+
it('should execute startWriteBatch as a no-op', () => {
1168+
const stub = new Stub('dummyClient', 'dummyChannelId', 'dummyTxid', chaincodeInput);
1169+
stub.startWriteBatch();
1170+
});
1171+
1172+
it('should execute finishWriteBatch as a no-op', async () => {
1173+
const stub = new Stub('dummyClient', 'dummyChannelId', 'dummyTxid', chaincodeInput);
1174+
await stub.finishWriteBatch();
1175+
});
1176+
});
1177+
1178+
11061179
describe('getPrivateDataHash', () => {
11071180
let handleGetPrivateDataHashStub;
11081181
let stub;

libraries/fabric-shim/types/index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ declare module 'fabric-shim' {
142142
getPrivateDataByRange(collection: string, startKey: string, endKey: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;
143143
getPrivateDataByPartialCompositeKey(collection: string, objectType: string, attributes: string[]): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;
144144
getPrivateDataQueryResult(collection: string, query: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;
145+
146+
getMultipleStates(keys: string[]): Promise<Uint8Array[]>;
147+
getMultiplePrivateData(collection: string, keys: string[]): Promise<Uint8Array[]>;
148+
startWriteBatch(): void;
149+
finishWriteBatch(): Promise<void>;
145150
}
146151

147152
export class KeyEndorsementPolicy {

0 commit comments

Comments
 (0)