Skip to content

Commit 5a7d32f

Browse files
authored
Merge pull request #100 from kaleido-io/1.1-validation
Add block number to tokens pools/custom contract listeners + better 1.1 support
2 parents 0b57d3c + 9a97abd commit 5a7d32f

File tree

16 files changed

+270
-51
lines changed

16 files changed

+270
-51
lines changed

server/src/controllers/common.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Get, JsonController, Param, QueryParam } from 'routing-controllers';
1+
import { Get, InternalServerError, JsonController, Param, QueryParam } from 'routing-controllers';
22
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
33
import { firefly } from '../clients/firefly';
44
import { FFStatus, Organization, Plugin, Plugins, Transaction, Verifier } from '../interfaces';
@@ -33,19 +33,27 @@ export class CommonController {
3333
@ResponseSchema(Verifier, { isArray: true })
3434
@OpenAPI({ summary: 'List verifiers (such as Ethereum keys) for all organizations in network' })
3535
async verifiers(): Promise<Verifier[]> {
36-
const orgs = await firefly.getOrganizations();
37-
const defaultVerifiers = await firefly.getVerifiers('default');
38-
const legacyVerifiers = await firefly.getVerifiers('ff_system');
39-
const verifiers = defaultVerifiers.concat(legacyVerifiers);
40-
41-
const result: Verifier[] = [];
42-
for (const v of verifiers) {
43-
const o = orgs.find((o) => o.id === v.identity);
44-
if (o !== undefined) {
45-
result.push({ did: o.did, type: v.type, value: v.value });
36+
try {
37+
const orgs = await firefly.getOrganizations();
38+
let verifiers = await firefly.getVerifiers('default');
39+
if (verifiers.length === 0) {
40+
// attempt to query legacy ff_system verifiers
41+
verifiers = await firefly.getVerifiers('ff_system');
4642
}
43+
const result: Verifier[] = [];
44+
for (const v of verifiers) {
45+
const o = orgs.find((o) => o.id === v.identity);
46+
if (o !== undefined) {
47+
result.push({ did: o.did, type: v.type, value: v.value });
48+
}
49+
}
50+
return result;
51+
} catch (err) {
52+
if (err.message == "FF10187: Namespace does not exist") {
53+
return [];
54+
}
55+
throw new InternalServerError(err.message);
4756
}
48-
return result;
4957
}
5058

5159
@Get('/verifiers/self')
@@ -92,7 +100,7 @@ export class CommonController {
92100
const status = await firefly.getStatus();
93101
if ("multiparty" in status) {
94102
return {
95-
multiparty: status.multiparty.enabled,
103+
multiparty: status.multiparty?.enabled,
96104
};
97105
} else {
98106
// Assume multiparty mode if `multiparty` key is missing from status

server/src/controllers/contracts.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ export class ContractsController {
152152
const listener = await firefly.createContractAPIListener(body.apiName, body.eventPath, {
153153
topic: body.topic,
154154
name: body.name,
155+
options: {
156+
firstEvent: body.firstEvent
157+
}
155158
});
156159
return {
157160
id: listener.id,
@@ -214,6 +217,9 @@ export class ContractsTemplateController {
214217
{
215218
topic: <%= ${q('topic')} %>,<% if (name) { %>
216219
<% print('name: ' + ${q('name')} + ',') } %>
220+
options: {<% if (firstEvent) { %>
221+
<% print('firstEvent: ' + ${q('firstEvent')} + ',') } %>
222+
}
217223
},
218224
);
219225
return {

server/src/controllers/tokens.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export class TokensController {
6767
type: body.type,
6868
config: {
6969
address: body.address,
70+
blockNumber: body.blockNumber,
7071
},
7172
});
7273
return { type: 'token_pool', id: pool.id };
@@ -259,6 +260,7 @@ export class TokensTemplateController {
259260
type: <%= ${q('type')} %>,
260261
config: {<% if (address) { %>
261262
<% print('address: ' + ${q('address')} + ',') } %>
263+
blockNumber: <%= ${q('blockNumber')} %>,
262264
}
263265
});
264266
return { type: 'token_pool', id: pool.id };

server/src/interfaces.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ export class DatatypeDefinition {
4040
version: string;
4141
}
4242

43+
export class FFContractListenerOptions {
44+
@IsOptional()
45+
firstEvent: string;
46+
}
47+
4348
export class BroadcastValue extends BaseMessageFields {
4449
@IsString()
4550
@IsOptional()
@@ -144,6 +149,10 @@ export class TokenPoolInput {
144149
@IsString()
145150
@IsOptional()
146151
address?: string;
152+
153+
@IsString()
154+
@IsOptional()
155+
blockNumber?: string;
147156
}
148157

149158
export class TokenPool extends TokenPoolInput {
@@ -305,6 +314,10 @@ export class ContractListener {
305314

306315
@IsString()
307316
eventPath: string;
317+
318+
@IsOptional()
319+
@IsString()
320+
firstEvent?: string;
308321
}
309322

310323
export class ContractListenerLookup {

server/test/common.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ describe('Common Operations', () => {
6262

6363
mockFireFly.getOrganizations.mockResolvedValueOnce(orgs);
6464
mockFireFly.getVerifiers.mockResolvedValueOnce(verifiers);
65-
mockFireFly.getVerifiers.mockResolvedValueOnce([]);
6665

6766
await request(server)
6867
.get('/api/common/verifiers')
@@ -71,8 +70,5 @@ describe('Common Operations', () => {
7170

7271
expect(mockFireFly.getOrganizations).toHaveBeenCalledWith();
7372
expect(mockFireFly.getVerifiers).toHaveBeenCalledWith('default');
74-
75-
expect(mockFireFly.getOrganizations).toHaveBeenCalledWith();
76-
expect(mockFireFly.getVerifiers).toHaveBeenCalledWith('ff_system');
7773
});
7874
});

server/test/contracts.template.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ describe('Templates: Smart Contracts', () => {
127127
topic: 'app1',
128128
apiName: 'api1',
129129
eventPath: 'set',
130+
firstEvent: 'newest',
130131
}),
131132
).toBe(
132133
formatTemplate(`
@@ -135,6 +136,9 @@ describe('Templates: Smart Contracts', () => {
135136
'set',
136137
{
137138
topic: 'app1',
139+
options: {
140+
firstEvent: 'newest',
141+
}
138142
},
139143
);
140144
return {

server/test/contracts.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ describe('Smart Contracts', () => {
226226
apiName: 'my-api',
227227
eventPath: 'Changed',
228228
topic: 'my-app',
229+
firstEvent: 'newest'
229230
};
230231
const listener = {
231232
id: 'listener1',
@@ -245,6 +246,9 @@ describe('Smart Contracts', () => {
245246

246247
expect(mockFireFly.createContractAPIListener).toHaveBeenCalledWith('my-api', 'Changed', {
247248
topic: 'my-app',
249+
options: {
250+
firstEvent: 'newest',
251+
},
248252
});
249253
});
250254
});

server/test/tokens.template.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ describe('Templates: Tokens', () => {
1616
symbol: 'P1',
1717
type: 'fungible',
1818
address: undefined,
19+
blockNumber: '0',
1920
}),
2021
).toBe(
2122
formatTemplate(`
@@ -24,6 +25,7 @@ describe('Templates: Tokens', () => {
2425
symbol: 'P1',
2526
type: 'fungible',
2627
config: {
28+
blockNumber: '0',
2729
}
2830
});
2931
return { type: 'token_pool', id: pool.id };

ui/src/App.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import {
1111
SnackbarMessageType,
1212
} from './components/Snackbar/MessageSnackbar';
1313
import { SDK_PATHS } from './constants/SDK_PATHS';
14+
import {
15+
GatewayTutorialSections,
16+
TutorialSections,
17+
} from './constants/TutorialSections';
1418
import { ApplicationContext } from './contexts/ApplicationContext';
1519
import { SnackbarContext } from './contexts/SnackbarContext';
1620
import {
@@ -19,13 +23,14 @@ import {
1923
ISelfIdentity,
2024
IVerifier,
2125
} from './interfaces/api';
26+
import { ITutorialSection } from './interfaces/tutorialSection';
2227
import { themeOptions } from './theme';
2328
import { fetchCatcher, summarizeFetchError } from './utils/fetches';
2429

2530
export const MAX_FORM_ROWS = 10;
2631

2732
function App() {
28-
const [initialized, setInitialized] = useState(true);
33+
const [initialized, setInitialized] = useState(false);
2934
const [message, setMessage] = useState('');
3035
const [messageType, setMessageType] = useState<SnackbarMessageType>('error');
3136

@@ -41,6 +46,9 @@ function App() {
4146
});
4247
const [tokensDisabled, setTokensDisabled] = useState(false);
4348
const [blockchainPlugin, setBlockchainPlugin] = useState('');
49+
const [tutorialSections, setTutorialSections] = useState<ITutorialSection[]>(
50+
[]
51+
);
4452

4553
useEffect(() => {
4654
Promise.all([
@@ -59,6 +67,7 @@ function App() {
5967
const ffStatus = statusResponse as IFireflyStatus;
6068
setMultiparty(ffStatus.multiparty);
6169
if (ffStatus.multiparty === true) {
70+
setTutorialSections(TutorialSections);
6271
fetchCatcher(SDK_PATHS.verifiers)
6372
.then((verifierRes: IVerifier[]) => {
6473
setSelfIdentity({
@@ -71,6 +80,8 @@ function App() {
7180
.catch((err) => {
7281
reportFetchError(err);
7382
});
83+
} else {
84+
setTutorialSections(GatewayTutorialSections);
7485
}
7586
})
7687
.finally(() => {
@@ -110,6 +121,7 @@ function App() {
110121
tokensDisabled,
111122
blockchainPlugin,
112123
multiparty,
124+
tutorialSections,
113125
}}
114126
>
115127
<StyledEngineProvider injectFirst>

ui/src/AppWrapper.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,17 @@ export const DEFAULT_ACTION = [
4040
TUTORIAL_FORMS.BROADCAST,
4141
];
4242

43+
export const DEFAULT_GATEWAY_ACTION = [
44+
TUTORIAL_CATEGORIES.TOKENS,
45+
TUTORIAL_FORMS.POOL,
46+
];
47+
4348
export const AppWrapper: React.FC = () => {
4449
const { pathname, search } = useLocation();
4550
const [searchParams, setSearchParams] = useSearchParams();
4651
const { t } = useTranslation();
47-
const { setPayloadMissingFields } = useContext(ApplicationContext);
52+
const { setPayloadMissingFields, multiparty, tutorialSections } =
53+
useContext(ApplicationContext);
4854
const [action, setAction] = useState<string | null>(null);
4955
const [categoryID, setCategoryID] = useState<string | undefined>(undefined);
5056
const [formID, setFormID] = useState<string | undefined>(undefined);
@@ -64,7 +70,7 @@ export const AppWrapper: React.FC = () => {
6470

6571
useEffect(() => {
6672
initializeFocusedForm();
67-
}, [pathname, search]);
73+
}, [pathname, search, tutorialSections]);
6874

6975
// Set form object based on action
7076
useEffect(() => {
@@ -84,11 +90,16 @@ export const AppWrapper: React.FC = () => {
8490

8591
const initializeFocusedForm = () => {
8692
const existingAction = searchParams.get(ACTION_QUERY_KEY);
87-
8893
if (existingAction === null) {
89-
setCategoryID(DEFAULT_ACTION[0]);
90-
setFormID(DEFAULT_ACTION[1]);
91-
setActionParam(DEFAULT_ACTION[0], DEFAULT_ACTION[1]);
94+
if (multiparty) {
95+
setCategoryID(DEFAULT_ACTION[0]);
96+
setFormID(DEFAULT_ACTION[1]);
97+
setActionParam(DEFAULT_ACTION[0], DEFAULT_ACTION[1]);
98+
} else {
99+
setCategoryID(DEFAULT_GATEWAY_ACTION[0]);
100+
setFormID(DEFAULT_GATEWAY_ACTION[1]);
101+
setActionParam(DEFAULT_GATEWAY_ACTION[0], DEFAULT_GATEWAY_ACTION[1]);
102+
}
92103
} else {
93104
const validAction: string[] = getValidAction(existingAction);
94105
setCategoryID(validAction[0]);
@@ -134,7 +145,11 @@ export const AppWrapper: React.FC = () => {
134145

135146
const getValidAction = (action: string) => {
136147
if (!isValidAction(action)) {
137-
return DEFAULT_ACTION;
148+
if (multiparty) {
149+
return DEFAULT_ACTION;
150+
} else {
151+
return DEFAULT_GATEWAY_ACTION;
152+
}
138153
}
139154

140155
return action.split(ACTION_DELIM);

0 commit comments

Comments
 (0)