• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hirosystems / stacks-blockchain-api / 3885427095

pending completion
3885427095

push

github

Matthew Little
chore!: support for Stacks 2.1

2029 of 3104 branches covered (65.37%)

7123 of 9267 relevant lines covered (76.86%)

1177.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.81
/src/api/rosetta-validate.ts
1
import * as Ajv from 'ajv';
31✔
2
import { hexToBuffer, logger, has0xPrefix, isValidC32Address, isValidPrincipal } from '../helpers';
31✔
3
import {
31✔
4
  RosettaConstants,
5
  RosettaErrors,
6
  RosettaSchemas,
7
  SchemaFiles,
8
  RosettaRequestType,
9
  getRosettaNetworkName,
10
  RosettaErrorsTypes,
11
} from './rosetta-constants';
12
import * as T from '@stacks/stacks-blockchain-api-types';
13
import { dereferenceSchema, getDocSchemaFile } from './validate';
31✔
14
import { ChainID } from '@stacks/transactions';
15

16
export interface ValidSchema {
17
  valid: boolean;
18
  error?: string; // discovered during schema validation
19
  errorType?: RosettaErrorsTypes; // discovered using our validation
20
}
21

22
export async function validate(schemaFilePath: string, data: any): Promise<ValidSchema> {
31✔
23
  const resolvedFilePath = getDocSchemaFile(schemaFilePath);
509✔
24
  const schemaDef = await dereferenceSchema(resolvedFilePath);
509✔
25
  const ajv = new Ajv({ schemaId: 'auto' });
509✔
26
  const valid = await ajv.validate(schemaDef, data);
509✔
27
  if (!valid) {
509✔
28
    logger.error(`Schema validation:\n\n ${JSON.stringify(ajv.errors, null, 2)}`);
7✔
29
    const errors = ajv.errors || [{ message: 'error' }];
7!
30
    return { valid: false, error: errors[0].message };
7✔
31
  }
32

33
  return { valid: true };
502✔
34
}
35

36
export async function rosettaValidateRequest(
31✔
37
  url: string,
38
  body: RosettaRequestType,
39
  chainId: ChainID
40
): Promise<ValidSchema> {
41
  // remove trailing slashes, if any
42
  if (url.endsWith('/')) {
503✔
43
    url = url.slice(0, url.length - 1);
16✔
44
  }
45
  // Check schemas
46
  const schemas: SchemaFiles = RosettaSchemas[url];
503✔
47
  // Return early if we don't know about this endpoint.
48
  if (!schemas) {
503!
49
    logger.warn(`Schema validation:\n\n unknown endpoint: ${url}`);
×
50
    return { valid: true };
×
51
  }
52

53
  const valid = await validate(schemas.request, body);
503✔
54

55
  if (!valid.valid) {
503✔
56
    return valid;
7✔
57
  }
58

59
  // Other request checks
60
  if ('network_identifier' in body) {
496✔
61
    if (RosettaConstants.blockchain != body.network_identifier.blockchain) {
496✔
62
      return { valid: false, errorType: RosettaErrorsTypes.invalidBlockchain };
2✔
63
    }
64

65
    if (getRosettaNetworkName(chainId) != body.network_identifier.network) {
494✔
66
      return { valid: false, errorType: RosettaErrorsTypes.invalidNetwork };
2✔
67
    }
68
  }
69

70
  if ('block_identifier' in body && !validHexId(body.block_identifier)) {
492✔
71
    return { valid: false, errorType: RosettaErrorsTypes.invalidBlockHash };
1✔
72
  }
73

74
  if ('transaction_identifier' in body && !validHexId(body.transaction_identifier)) {
491✔
75
    return { valid: false, errorType: RosettaErrorsTypes.invalidTransactionHash };
1✔
76
  }
77

78
  if ('account_identifier' in body && isValidPrincipal(body.account_identifier.address) == false) {
490✔
79
    return { valid: false, errorType: RosettaErrorsTypes.invalidAccount };
1✔
80
  }
81

82
  return { valid: true };
489✔
83
}
84

85
function validHexId(
86
  identifier:
87
    | T.RosettaBlockIdentifier
88
    | T.RosettaPartialBlockIdentifier
89
    | T.TransactionIdentifier
90
    | undefined
91
): boolean {
92
  if (identifier === undefined) {
209!
93
    return true;
×
94
  }
95

96
  if ('hash' in identifier) {
209✔
97
    let hash = identifier.hash as string;
103✔
98

99
    if (hash === undefined) {
103!
100
      return true;
×
101
    }
102

103
    try {
103✔
104
      if (!has0xPrefix(hash)) {
103✔
105
        hash = '0x' + hash;
3✔
106
        hexToBuffer(hash);
3✔
107
      }
108
    } catch (_e) {
109
      return false;
2✔
110
    }
111
  }
112
  return true;
207✔
113
}
114

115
// TODO: there has to be a better way to go from ajv errors to rosetta errors.
116
export function makeRosettaError(notValid: ValidSchema): Readonly<T.RosettaError> {
31✔
117
  const error = notValid.error || '';
12✔
118
  if (error.search(/network_identifier/) != -1) {
12✔
119
    return {
3✔
120
      ...RosettaErrors[RosettaErrorsTypes.emptyNetworkIdentifier],
121
      details: { message: error },
122
    };
123
  } else if (error.search(/blockchain/) != -1) {
9!
124
    return {
×
125
      ...RosettaErrors[RosettaErrorsTypes.emptyBlockchain],
126
      details: { message: error },
127
    };
128
  } else if (error.search(/network/) != -1) {
9!
129
    return {
×
130
      ...RosettaErrors[RosettaErrorsTypes.emptyNetwork],
131
      details: { message: error },
132
    };
133
  } else if (error.search(/block_identifier/) != -1) {
9✔
134
    return {
1✔
135
      ...RosettaErrors[RosettaErrorsTypes.invalidBlockIdentifier],
136
      details: { message: error },
137
    };
138
  } else if (error.search(/transaction_identifier/) != -1) {
8!
139
    return {
×
140
      ...RosettaErrors[RosettaErrorsTypes.invalidTransactionIdentifier],
141
      details: { message: error },
142
    };
143
  } else if (error.search(/operations/) != -1) {
8!
144
    return {
×
145
      ...RosettaErrors[RosettaErrorsTypes.invalidOperation],
146
      details: { message: error },
147
    };
148
  } else if (error.search(/should have required property/) != -1) {
8✔
149
    return {
1✔
150
      ...RosettaErrors[RosettaErrorsTypes.invalidParams],
151
      details: { message: error },
152
    };
153
  }
154

155
  // we've already identified the problem
156
  if (notValid.errorType !== undefined) {
7✔
157
    return RosettaErrors[notValid.errorType];
7✔
158
  }
159

160
  return RosettaErrors[RosettaErrorsTypes.unknownError];
×
161
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc