@odatano/core 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +212 -0
- package/cds-plugin.js +5 -0
- package/config/preview/cardano-node/alonzo-genesis.json +196 -0
- package/config/preview/cardano-node/byron-genesis.json +117 -0
- package/config/preview/cardano-node/config.json +118 -0
- package/config/preview/cardano-node/conway-genesis.json +297 -0
- package/config/preview/cardano-node/shelley-genesis.json +68 -0
- package/config/preview/cardano-node/topology.json +19 -0
- package/db/schema.cds +1318 -0
- package/package.json +125 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +96 -0
- package/src/index.js.map +1 -0
- package/src/plugin.d.ts.map +1 -0
- package/src/plugin.js +92 -0
- package/src/plugin.js.map +1 -0
- package/srv/blockchain/backends/blockfrost-backend.d.ts.map +1 -0
- package/srv/blockchain/backends/blockfrost-backend.js +398 -0
- package/srv/blockchain/backends/blockfrost-backend.js.map +1 -0
- package/srv/blockchain/backends/cardano-backend.d.ts.map +1 -0
- package/srv/blockchain/backends/cardano-backend.js +12 -0
- package/srv/blockchain/backends/cardano-backend.js.map +1 -0
- package/srv/blockchain/backends/koios-backend.d.ts.map +1 -0
- package/srv/blockchain/backends/koios-backend.js +537 -0
- package/srv/blockchain/backends/koios-backend.js.map +1 -0
- package/srv/blockchain/backends/ogmios-backend.d.ts.map +1 -0
- package/srv/blockchain/backends/ogmios-backend.js +516 -0
- package/srv/blockchain/backends/ogmios-backend.js.map +1 -0
- package/srv/blockchain/cardano-client.d.ts.map +1 -0
- package/srv/blockchain/cardano-client.js +377 -0
- package/srv/blockchain/cardano-client.js.map +1 -0
- package/srv/blockchain/cardano-indexer.d.ts.map +1 -0
- package/srv/blockchain/cardano-indexer.js +542 -0
- package/srv/blockchain/cardano-indexer.js.map +1 -0
- package/srv/blockchain/cardano-tx-builder.d.ts.map +1 -0
- package/srv/blockchain/cardano-tx-builder.js +232 -0
- package/srv/blockchain/cardano-tx-builder.js.map +1 -0
- package/srv/blockchain/circuit-breaker.d.ts.map +1 -0
- package/srv/blockchain/circuit-breaker.js +110 -0
- package/srv/blockchain/circuit-breaker.js.map +1 -0
- package/srv/blockchain/signing/external-signer.d.ts.map +1 -0
- package/srv/blockchain/signing/external-signer.js +302 -0
- package/srv/blockchain/signing/external-signer.js.map +1 -0
- package/srv/blockchain/signing/signature-verifier.d.ts.map +1 -0
- package/srv/blockchain/signing/signature-verifier.js +249 -0
- package/srv/blockchain/signing/signature-verifier.js.map +1 -0
- package/srv/blockchain/transaction-building/buildooor-tx.d.ts.map +1 -0
- package/srv/blockchain/transaction-building/buildooor-tx.js +636 -0
- package/srv/blockchain/transaction-building/buildooor-tx.js.map +1 -0
- package/srv/blockchain/transaction-building/cardano-tx.d.ts.map +1 -0
- package/srv/blockchain/transaction-building/cardano-tx.js +3 -0
- package/srv/blockchain/transaction-building/cardano-tx.js.map +1 -0
- package/srv/blockchain/transaction-building/csl-tx.d.ts.map +1 -0
- package/srv/blockchain/transaction-building/csl-tx.js +766 -0
- package/srv/blockchain/transaction-building/csl-tx.js.map +1 -0
- package/srv/blockchain/transaction-building/tx-builder-registry.d.ts.map +1 -0
- package/srv/blockchain/transaction-building/tx-builder-registry.js +67 -0
- package/srv/blockchain/transaction-building/tx-builder-registry.js.map +1 -0
- package/srv/cardano-service.cds +179 -0
- package/srv/cardano-service.d.ts.map +1 -0
- package/srv/cardano-service.js +227 -0
- package/srv/cardano-service.js.map +1 -0
- package/srv/cardano-tx-service.cds +298 -0
- package/srv/cardano-tx-service.d.ts.map +1 -0
- package/srv/cardano-tx-service.js +646 -0
- package/srv/cardano-tx-service.js.map +1 -0
- package/srv/cardano-ui.cds +2949 -0
- package/srv/server.d.ts.map +1 -0
- package/srv/server.js +212 -0
- package/srv/server.js.map +1 -0
- package/srv/utils/backend-request-handler.d.ts.map +1 -0
- package/srv/utils/backend-request-handler.js +47 -0
- package/srv/utils/backend-request-handler.js.map +1 -0
- package/srv/utils/const.d.ts.map +1 -0
- package/srv/utils/const.js +86 -0
- package/srv/utils/const.js.map +1 -0
- package/srv/utils/error-codes.d.ts.map +1 -0
- package/srv/utils/error-codes.js +49 -0
- package/srv/utils/error-codes.js.map +1 -0
- package/srv/utils/errors.d.ts.map +1 -0
- package/srv/utils/errors.js +389 -0
- package/srv/utils/errors.js.map +1 -0
- package/srv/utils/mappers.d.ts.map +1 -0
- package/srv/utils/mappers.js +723 -0
- package/srv/utils/mappers.js.map +1 -0
- package/srv/utils/signing-helper.d.ts.map +1 -0
- package/srv/utils/signing-helper.js +128 -0
- package/srv/utils/signing-helper.js.map +1 -0
- package/srv/utils/tx-build-helper.d.ts.map +1 -0
- package/srv/utils/tx-build-helper.js +135 -0
- package/srv/utils/tx-build-helper.js.map +1 -0
- package/srv/utils/types.d.ts.map +1 -0
- package/srv/utils/types.js +36 -0
- package/srv/utils/types.js.map +1 -0
- package/srv/utils/validators.d.ts.map +1 -0
- package/srv/utils/validators.js +382 -0
- package/srv/utils/validators.js.map +1 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CardanoTransactionBuilder = void 0;
|
|
7
|
+
const cds_1 = __importDefault(require("@sap/cds"));
|
|
8
|
+
const tx_builder_registry_1 = require("./transaction-building/tx-builder-registry");
|
|
9
|
+
const logger = cds_1.default.log('CardanoTransactionBuilder');
|
|
10
|
+
/**
|
|
11
|
+
* CardanoTransactionBuilder - High-level transaction builder that utilizes specific CardanoTxBuilder implementations
|
|
12
|
+
* to build various types of Cardano transactions.
|
|
13
|
+
*/
|
|
14
|
+
class CardanoTransactionBuilder {
|
|
15
|
+
client;
|
|
16
|
+
txBuilder;
|
|
17
|
+
initialized = false;
|
|
18
|
+
/**
|
|
19
|
+
* Create a new CardanoTransactionBuilder instance
|
|
20
|
+
* @param client - The CardanoClient instance for UTxO fetching
|
|
21
|
+
*/
|
|
22
|
+
constructor(client) {
|
|
23
|
+
this.client = client;
|
|
24
|
+
logger.info('CardanoTransactionBuilder instance created');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Initialize the transaction builder
|
|
28
|
+
* @param protocolParams - Optional protocol parameters (if not provided, fetched from backend)
|
|
29
|
+
*/
|
|
30
|
+
async init(protocolParams) {
|
|
31
|
+
if (this.initialized && this.txBuilder)
|
|
32
|
+
return;
|
|
33
|
+
// Create transaction builder from registry
|
|
34
|
+
this.txBuilder = tx_builder_registry_1.TxBuilderRegistry.createDefault();
|
|
35
|
+
await this.txBuilder.init(this.client, protocolParams);
|
|
36
|
+
this.initialized = true;
|
|
37
|
+
logger.info(`Initialized with builder: ${this.txBuilder.name}`);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Ensure the builder is initialized and return it (lazy init)
|
|
41
|
+
* @returns {Promise<CardanoTxBuilder>} initialized builder
|
|
42
|
+
* @throws {Error} if builder cannot be initialized
|
|
43
|
+
*/
|
|
44
|
+
async ensureInitialized() {
|
|
45
|
+
if (!this.initialized || !this.txBuilder) {
|
|
46
|
+
await this.init();
|
|
47
|
+
}
|
|
48
|
+
if (!this.txBuilder) {
|
|
49
|
+
throw new Error('Transaction builder failed to initialize');
|
|
50
|
+
}
|
|
51
|
+
return this.txBuilder;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Reset the transaction builder (useful for testing)
|
|
55
|
+
*/
|
|
56
|
+
reset() {
|
|
57
|
+
this.txBuilder = undefined;
|
|
58
|
+
this.initialized = false;
|
|
59
|
+
logger.debug(`Builder reset`);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Set the transaction builder directly (for testing)
|
|
63
|
+
* @param builder - The builder to set
|
|
64
|
+
*/
|
|
65
|
+
setBuilder(builder) {
|
|
66
|
+
this.txBuilder = builder;
|
|
67
|
+
this.initialized = true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Build a simple ADA transfer transaction
|
|
71
|
+
* @param req transaction build request
|
|
72
|
+
* @param protocolParameters current protocol parameters
|
|
73
|
+
* @returns {Promise<TxBuildResult>} transaction build result
|
|
74
|
+
*/
|
|
75
|
+
async buildSimpleAdaTransaction(req, protocolParameters) {
|
|
76
|
+
const builder = await this.ensureInitialized();
|
|
77
|
+
// Prepare the transaction build context
|
|
78
|
+
const txContext = {
|
|
79
|
+
utxos: await this._fetchUtxosForAddress(req.senderAddress),
|
|
80
|
+
protocolParameters: protocolParameters
|
|
81
|
+
};
|
|
82
|
+
// Build the unsigned ADA transfer transaction
|
|
83
|
+
const txBuildResult = await builder.buildUnsignedAdaTransfer(req, txContext);
|
|
84
|
+
logger.info(`Built simple ADA transaction successfully.`);
|
|
85
|
+
// Return the transaction build result
|
|
86
|
+
return txBuildResult;
|
|
87
|
+
}
|
|
88
|
+
async buildTransactionWithMetadata(req, protocolParameters) {
|
|
89
|
+
const builder = await this.ensureInitialized();
|
|
90
|
+
// Prepare the transaction build context
|
|
91
|
+
const txContext = {
|
|
92
|
+
utxos: await this._fetchUtxosForAddress(req.senderAddress),
|
|
93
|
+
protocolParameters: protocolParameters
|
|
94
|
+
};
|
|
95
|
+
// Build the unsigned transaction with metadata
|
|
96
|
+
const txBuildResult = await builder.buildUnsignedTransactionWithMetadata(req, txContext);
|
|
97
|
+
logger.info(`Built transaction with metadata successfully.`);
|
|
98
|
+
// Return the transaction build result
|
|
99
|
+
return txBuildResult;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Build a multi-asset transaction
|
|
103
|
+
* @param req transaction build request
|
|
104
|
+
* @param protocolParameters current protocol parameters
|
|
105
|
+
* @returns {Promise<TxBuildResult>} transaction build result
|
|
106
|
+
*/
|
|
107
|
+
async buildMultiAssetTransaction(req, protocolParameters) {
|
|
108
|
+
const builder = await this.ensureInitialized();
|
|
109
|
+
// Prepare the transaction build context
|
|
110
|
+
const utxos = await this._fetchUtxosForAddress(req.senderAddress);
|
|
111
|
+
logger.info(`Fetched ${utxos.length} UTxOs for multi-asset transaction`);
|
|
112
|
+
for (const u of utxos) {
|
|
113
|
+
logger.info(`UTxO ${u.txHash}:${u.outputIndex} amounts: ${JSON.stringify(u.amount)}`);
|
|
114
|
+
}
|
|
115
|
+
const txContext = {
|
|
116
|
+
utxos,
|
|
117
|
+
protocolParameters: protocolParameters
|
|
118
|
+
};
|
|
119
|
+
// Build the unsigned multi-asset transaction
|
|
120
|
+
const txBuildResult = await builder.buildUnsignedMultiAssetTransaction(req, txContext);
|
|
121
|
+
logger.info(`Built multi-asset transaction successfully.`);
|
|
122
|
+
// Return the transaction build result
|
|
123
|
+
return txBuildResult;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Build a minting transaction
|
|
127
|
+
* @param req transaction build request
|
|
128
|
+
* @param protocolParameters current protocol parameters
|
|
129
|
+
* @returns {Promise<TxBuildResult>} transaction build result
|
|
130
|
+
*/
|
|
131
|
+
async buildMintTransaction(req, protocolParameters) {
|
|
132
|
+
// Validate mint-specific required fields
|
|
133
|
+
if (!req.mintActions || req.mintActions.length === 0) {
|
|
134
|
+
throw new Error('[CardanoTransactionBuilder] buildMintTransaction requires mintActions to be specified');
|
|
135
|
+
}
|
|
136
|
+
if (!req.mintingPolicyScript) {
|
|
137
|
+
throw new Error('[CardanoTransactionBuilder] buildMintTransaction requires mintingPolicyScript to be specified');
|
|
138
|
+
}
|
|
139
|
+
// Type is now narrowed to TxBuildMintRequest
|
|
140
|
+
const mintReq = req;
|
|
141
|
+
const builder = await this.ensureInitialized();
|
|
142
|
+
const cardanoClient = this.client;
|
|
143
|
+
// Prepare the transaction build context
|
|
144
|
+
const txContext = {
|
|
145
|
+
utxos: await this._fetchUtxosForAddress(req.senderAddress),
|
|
146
|
+
protocolParameters: protocolParameters,
|
|
147
|
+
// Pass evaluator if Ogmios is available for dynamic execution unit calculation
|
|
148
|
+
evaluateTransaction: cardanoClient.hasOgmiosBackend()
|
|
149
|
+
? (cbor) => cardanoClient.evaluateTransaction(cbor)
|
|
150
|
+
: undefined
|
|
151
|
+
};
|
|
152
|
+
if (txContext.evaluateTransaction) {
|
|
153
|
+
logger.info(`Ogmios available - will use dynamic script evaluation`);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
logger.info(`Ogmios not available - using default execution units`);
|
|
157
|
+
}
|
|
158
|
+
// Build the unsigned minting transaction
|
|
159
|
+
const txBuildResult = await builder.buildUnsignedMintTransaction(mintReq, txContext);
|
|
160
|
+
logger.info(`Built minting transaction successfully.`);
|
|
161
|
+
// Return the transaction build result
|
|
162
|
+
return txBuildResult;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Build a Plutus spending transaction (consume UTxO at script address)
|
|
166
|
+
* @param req transaction build request
|
|
167
|
+
* @param protocolParameters current protocol parameters
|
|
168
|
+
* @returns {Promise<TxBuildResult>} transaction build result
|
|
169
|
+
*/
|
|
170
|
+
async buildPlutusSpendTransaction(req, protocolParameters) {
|
|
171
|
+
if (!req.plutusScriptExecution) {
|
|
172
|
+
throw new Error('[CardanoTransactionBuilder] buildPlutusSpendTransaction requires plutusScriptExecution to be specified');
|
|
173
|
+
}
|
|
174
|
+
const spendReq = req;
|
|
175
|
+
const builder = await this.ensureInitialized();
|
|
176
|
+
const cardanoClient = this.client;
|
|
177
|
+
// Fetch sender UTxOs for fee payment
|
|
178
|
+
const senderUtxos = await this._fetchUtxosForAddress(req.senderAddress);
|
|
179
|
+
// Fetch the script UTxO separately (it's at the script address, not sender address)
|
|
180
|
+
// We include it in the UTxO set so the builder can find it
|
|
181
|
+
const scriptRef = spendReq.plutusScriptExecution.scriptUtxo;
|
|
182
|
+
const allUtxos = [...senderUtxos];
|
|
183
|
+
// Check if the script UTxO is already in sender UTxOs (unlikely but possible)
|
|
184
|
+
const alreadyIncluded = senderUtxos.some(u => u.txHash === scriptRef.txHash && u.outputIndex === scriptRef.outputIndex);
|
|
185
|
+
if (!alreadyIncluded) {
|
|
186
|
+
// Fetch script UTxO via transaction lookup - the backend needs to provide it
|
|
187
|
+
// For now, we create a minimal UTxO entry from what we know
|
|
188
|
+
// The actual UTxO data will be resolved by the backend during tx building
|
|
189
|
+
logger.info(`Script UTxO ${scriptRef.txHash}#${scriptRef.outputIndex} not in sender UTxOs - fetching from backend`);
|
|
190
|
+
const tx = await cardanoClient.getTransaction(scriptRef.txHash);
|
|
191
|
+
const scriptOutput = tx.outputs?.find(o => o.outputIndex === scriptRef.outputIndex);
|
|
192
|
+
if (!scriptOutput) {
|
|
193
|
+
throw new Error(`[CardanoTransactionBuilder] Script UTxO output ${scriptRef.txHash}#${scriptRef.outputIndex} not found in transaction`);
|
|
194
|
+
}
|
|
195
|
+
allUtxos.push({
|
|
196
|
+
txHash: scriptRef.txHash,
|
|
197
|
+
outputIndex: scriptRef.outputIndex,
|
|
198
|
+
address: scriptOutput.address,
|
|
199
|
+
amount: scriptOutput.amount,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const txContext = {
|
|
203
|
+
utxos: allUtxos,
|
|
204
|
+
protocolParameters: protocolParameters,
|
|
205
|
+
evaluateTransaction: cardanoClient.hasOgmiosBackend()
|
|
206
|
+
? (cbor) => cardanoClient.evaluateTransaction(cbor)
|
|
207
|
+
: undefined
|
|
208
|
+
};
|
|
209
|
+
if (txContext.evaluateTransaction) {
|
|
210
|
+
logger.info(`Ogmios available - will use dynamic script evaluation`);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
logger.info(`Ogmios not available - using default execution units`);
|
|
214
|
+
}
|
|
215
|
+
const txBuildResult = await builder.buildUnsignedPlutusSpendTransaction(spendReq, txContext);
|
|
216
|
+
logger.info(`Built Plutus spending transaction successfully.`);
|
|
217
|
+
return txBuildResult;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Fetch UTxOs for a given address
|
|
221
|
+
* @param address bech32 address
|
|
222
|
+
* @returns {Promise<UTxO[]>} list of UTxOs
|
|
223
|
+
*/
|
|
224
|
+
async _fetchUtxosForAddress(address) {
|
|
225
|
+
// fetch UTxOs directly using cardano client
|
|
226
|
+
logger.debug(`Fetching UTxOs for address: ${address}`);
|
|
227
|
+
const utxos = await this.client.getAddressUtxos(address);
|
|
228
|
+
return utxos;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
exports.CardanoTransactionBuilder = CardanoTransactionBuilder;
|
|
232
|
+
//# sourceMappingURL=cardano-tx-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cardano-tx-builder.js","sourceRoot":"","sources":["cardano-tx-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["circuit-breaker.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,gBAAgB,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,cAAc,EAAE,MAAM,CAAC;CACxB;AAeD;;;;;;;;GAQG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,MAAM,CAAuB;gBAEzB,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC;IAIlD;;;OAGG;IACH,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAqB3C,6CAA6C;IAC7C,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAkBxC,yCAAyC;IACzC,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAqBxC,oDAAoD;IACpD,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY;IAY3C,8CAA8C;IAC9C,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CircuitBreakerManager = void 0;
|
|
7
|
+
const cds_1 = __importDefault(require("@sap/cds"));
|
|
8
|
+
const logger = cds_1.default.log('CircuitBreaker');
|
|
9
|
+
const DEFAULT_CONFIG = {
|
|
10
|
+
failureThreshold: 5,
|
|
11
|
+
successThreshold: 2,
|
|
12
|
+
resetTimeoutMs: 30_000,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Circuit Breaker for backend failover.
|
|
16
|
+
* Tracks per-backend failure counts and prevents hammering unhealthy backends.
|
|
17
|
+
*
|
|
18
|
+
* States:
|
|
19
|
+
* closed → normal operation, requests pass through
|
|
20
|
+
* open → backend is unhealthy, requests are skipped
|
|
21
|
+
* half-open → probe period, limited requests allowed to test recovery
|
|
22
|
+
*/
|
|
23
|
+
class CircuitBreakerManager {
|
|
24
|
+
circuits = new Map();
|
|
25
|
+
config;
|
|
26
|
+
constructor(config) {
|
|
27
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if a backend should be attempted.
|
|
31
|
+
* Transitions open → half-open if reset timeout has elapsed.
|
|
32
|
+
*/
|
|
33
|
+
shouldAttempt(backendName) {
|
|
34
|
+
const entry = this.circuits.get(backendName);
|
|
35
|
+
if (!entry)
|
|
36
|
+
return true; // no history = closed
|
|
37
|
+
if (entry.state === 'closed')
|
|
38
|
+
return true;
|
|
39
|
+
if (entry.state === 'open') {
|
|
40
|
+
const elapsed = Date.now() - entry.lastFailureTime;
|
|
41
|
+
if (elapsed >= this.config.resetTimeoutMs) {
|
|
42
|
+
entry.state = 'half-open';
|
|
43
|
+
entry.successes = 0;
|
|
44
|
+
logger.debug(`Circuit half-open for ${backendName} after ${elapsed}ms`);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
// half-open: allow probe requests
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
/** Record a successful call to a backend. */
|
|
53
|
+
recordSuccess(backendName) {
|
|
54
|
+
const entry = this.circuits.get(backendName);
|
|
55
|
+
if (!entry)
|
|
56
|
+
return;
|
|
57
|
+
if (entry.state === 'half-open') {
|
|
58
|
+
entry.successes++;
|
|
59
|
+
if (entry.successes >= this.config.successThreshold) {
|
|
60
|
+
entry.state = 'closed';
|
|
61
|
+
entry.failures = 0;
|
|
62
|
+
entry.successes = 0;
|
|
63
|
+
logger.info(`Circuit closed for ${backendName} (recovered)`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (entry.state === 'closed') {
|
|
67
|
+
// Reset failure count on success
|
|
68
|
+
entry.failures = 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Record a failed call to a backend. */
|
|
72
|
+
recordFailure(backendName) {
|
|
73
|
+
let entry = this.circuits.get(backendName);
|
|
74
|
+
if (!entry) {
|
|
75
|
+
entry = { state: 'closed', failures: 0, successes: 0, lastFailureTime: 0 };
|
|
76
|
+
this.circuits.set(backendName, entry);
|
|
77
|
+
}
|
|
78
|
+
entry.failures++;
|
|
79
|
+
entry.lastFailureTime = Date.now();
|
|
80
|
+
if (entry.state === 'half-open') {
|
|
81
|
+
// Any failure in half-open reopens the circuit
|
|
82
|
+
entry.state = 'open';
|
|
83
|
+
entry.successes = 0;
|
|
84
|
+
logger.warn(`Circuit re-opened for ${backendName} (failed during probe)`);
|
|
85
|
+
}
|
|
86
|
+
else if (entry.state === 'closed' && entry.failures >= this.config.failureThreshold) {
|
|
87
|
+
entry.state = 'open';
|
|
88
|
+
logger.warn(`Circuit opened for ${backendName} after ${entry.failures} failures`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Get the current state of a backend's circuit. */
|
|
92
|
+
getState(backendName) {
|
|
93
|
+
const entry = this.circuits.get(backendName);
|
|
94
|
+
if (!entry)
|
|
95
|
+
return 'closed';
|
|
96
|
+
// Check for auto-transition to half-open
|
|
97
|
+
if (entry.state === 'open') {
|
|
98
|
+
const elapsed = Date.now() - entry.lastFailureTime;
|
|
99
|
+
if (elapsed >= this.config.resetTimeoutMs)
|
|
100
|
+
return 'half-open';
|
|
101
|
+
}
|
|
102
|
+
return entry.state;
|
|
103
|
+
}
|
|
104
|
+
/** Reset all circuit states (for testing). */
|
|
105
|
+
reset() {
|
|
106
|
+
this.circuits.clear();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.CircuitBreakerManager = CircuitBreakerManager;
|
|
110
|
+
//# sourceMappingURL=circuit-breaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["circuit-breaker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-signer.d.ts","sourceRoot":"","sources":["external-signer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAwB,MAAM,sBAAsB,CAAC;AAE/E,OAAO,EACL,kBAAkB,EAGlB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,2BAA2B,EAC3B,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAS3B;;;;;;;;;;;;;GAaG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,YAAY,CAAS;gBAEjB,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;IAK/C;;;;;;;;;;;;;OAaG;IACI,oBAAoB,CACzB,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACR,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3B,cAAc,CAAC,EAAE,kBAAkB,CAAC;KACrC,GACA,uBAAuB;IAmC1B;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAiBnC;;;;;;;;;;OAUG;IACI,uBAAuB,CAC5B,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,MAAM,EAC1B,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,oBAAoB,CAAC,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GACjF,2BAA2B;IAqC9B;;;;;;;OAOG;IACI,aAAa,CAClB,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,MAAM,EAC1B,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,oBAAoB,CAAC,GACxD,2BAA2B;IAY9B;;;;;;;;OAQG;IACI,mBAAmB,CAAC,cAAc,EAAE,uBAAuB,GAAG,oBAAoB;IAUzF;;;;;;OAMG;IACI,YAAY,CAAC,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,GAAG,oBAAoB;IAY5F;;;;;;OAMG;IACI,cAAc,CACnB,KAAK,EAAE,oBAAoB,EAC3B,kBAAkB,EAAE,2BAA2B,GAC9C,oBAAoB;IAgBvB;;;;;;OAMG;IACI,eAAe,CAAC,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,GAAG,oBAAoB;IAYzF;;;;;;OAMG;IACI,YAAY,CAAC,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,GAAG,oBAAoB;IAY5F;;;;;OAKG;IACI,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI5C;;;;;;;;;;;;OAYG;IACI,qBAAqB,CAC1B,OAAO,EAAE,eAAe,EACxB,uBAAuB,EAAE,MAAM,GAC9B,2BAA2B;IAiB9B;;OAEG;IACI,WAAW,IAAI,iBAAiB;CAGxC;AAKD;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,oBAAoB,CAK9D;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,CAAC,EAAE;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,oBAAoB,CAEvB"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ExternalSignerModule = void 0;
|
|
7
|
+
exports.getExternalSignerModule = getExternalSignerModule;
|
|
8
|
+
exports.createExternalSignerModule = createExternalSignerModule;
|
|
9
|
+
const cds_1 = __importDefault(require("@sap/cds"));
|
|
10
|
+
const signature_verifier_1 = require("./signature-verifier");
|
|
11
|
+
const errors_1 = require("../../utils/errors");
|
|
12
|
+
const types_1 = require("../../utils/types");
|
|
13
|
+
const logger = cds_1.default.log('ExternalSigner');
|
|
14
|
+
/**
|
|
15
|
+
* Default TTL for signing requests (30 minutes)
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_SIGNING_TTL_MS = 30 * 60 * 1000;
|
|
18
|
+
/**
|
|
19
|
+
* ExternalSignerModule - Manages the external signing workflow
|
|
20
|
+
*
|
|
21
|
+
* This module orchestrates the complete flow:
|
|
22
|
+
* 1. Create signing request from build result
|
|
23
|
+
* 2. Export unsigned transaction for external signing
|
|
24
|
+
* 3. Receive and verify signed transaction
|
|
25
|
+
* 4. Prepare for submission
|
|
26
|
+
*
|
|
27
|
+
* Key security principles:
|
|
28
|
+
* - NO private keys are ever handled by this service
|
|
29
|
+
* - Signature verification ensures transaction integrity
|
|
30
|
+
* - Complete audit trail of the signing workflow
|
|
31
|
+
*/
|
|
32
|
+
class ExternalSignerModule {
|
|
33
|
+
verifier;
|
|
34
|
+
signingTtlMs;
|
|
35
|
+
constructor(options) {
|
|
36
|
+
this.verifier = (0, signature_verifier_1.getSignatureVerifier)();
|
|
37
|
+
this.signingTtlMs = options?.signingTtlMs ?? DEFAULT_SIGNING_TTL_MS;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create an unsigned transaction export payload for external signing
|
|
41
|
+
*
|
|
42
|
+
* This is the entry point for the external signing workflow.
|
|
43
|
+
* Call this after building a transaction to get a standardized
|
|
44
|
+
* payload that external signers can use.
|
|
45
|
+
*
|
|
46
|
+
* @param buildId - The build ID from TransactionBuilds
|
|
47
|
+
* @param unsignedTxCbor - The unsigned transaction CBOR
|
|
48
|
+
* @param txBodyHash - The transaction body hash
|
|
49
|
+
* @param network - The Cardano network
|
|
50
|
+
* @param options - Additional options
|
|
51
|
+
* @returns Unsigned transaction export payload
|
|
52
|
+
*/
|
|
53
|
+
createSigningRequest(buildId, unsignedTxCbor, txBodyHash, network, message, options) {
|
|
54
|
+
const signingRequestId = cds_1.default.utils.uuid();
|
|
55
|
+
const now = new Date();
|
|
56
|
+
const expiresAt = new Date(now.getTime() + this.signingTtlMs);
|
|
57
|
+
const signerTypeHint = options?.signerTypeHint ?? types_1.ExternalSignerType.CARDANO_CLI;
|
|
58
|
+
const payload = {
|
|
59
|
+
signingRequestId,
|
|
60
|
+
buildId,
|
|
61
|
+
txBodyHash,
|
|
62
|
+
unsignedTxCbor,
|
|
63
|
+
network,
|
|
64
|
+
createdAt: now.toISOString(),
|
|
65
|
+
expiresAt: expiresAt.toISOString(),
|
|
66
|
+
requiredSigners: options?.requiredSigners,
|
|
67
|
+
signingInstructions: this.generateSigningInstructions(unsignedTxCbor, network, message, signerTypeHint),
|
|
68
|
+
};
|
|
69
|
+
logger.info({
|
|
70
|
+
signingRequestId,
|
|
71
|
+
buildId,
|
|
72
|
+
txBodyHash,
|
|
73
|
+
network,
|
|
74
|
+
expiresAt: expiresAt.toISOString(),
|
|
75
|
+
}, 'Created signing request');
|
|
76
|
+
return payload;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Generate signing instructions for different signer types
|
|
80
|
+
*/
|
|
81
|
+
generateSigningInstructions(unsignedTxCbor, network, message, signerTypeHint) {
|
|
82
|
+
return {
|
|
83
|
+
signerTypeHint,
|
|
84
|
+
message: message,
|
|
85
|
+
network,
|
|
86
|
+
cip30SigningRequest: {
|
|
87
|
+
txCbor: unsignedTxCbor,
|
|
88
|
+
partialSign: false,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Verify a signed transaction
|
|
94
|
+
*
|
|
95
|
+
* Call this after receiving a signed transaction from an external signer.
|
|
96
|
+
* This verifies the signature integrity before submission.
|
|
97
|
+
*
|
|
98
|
+
* @param signedTxCbor - The signed transaction CBOR
|
|
99
|
+
* @param expectedTxBodyHash - The expected transaction body hash (from the build)
|
|
100
|
+
* @param options - Additional verification options
|
|
101
|
+
* @returns Verification result
|
|
102
|
+
*/
|
|
103
|
+
verifySignedTransaction(signedTxCbor, expectedTxBodyHash, options) {
|
|
104
|
+
logger.debug({ expectedTxBodyHash }, 'Verifying signed transaction');
|
|
105
|
+
// Enforce signing TTL if expiresAt is provided
|
|
106
|
+
if (options?.expiresAt && this.isExpired(options.expiresAt)) {
|
|
107
|
+
return {
|
|
108
|
+
isValid: false,
|
|
109
|
+
txBodyHash: '',
|
|
110
|
+
witnessCount: 0,
|
|
111
|
+
signerKeyHashes: [],
|
|
112
|
+
warnings: [],
|
|
113
|
+
errorMessage: 'Signing request has expired',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const result = this.verifier.verify(signedTxCbor, {
|
|
117
|
+
...options,
|
|
118
|
+
expectedTxBodyHash,
|
|
119
|
+
requireSignature: true,
|
|
120
|
+
});
|
|
121
|
+
if (result.isValid) {
|
|
122
|
+
logger.info({
|
|
123
|
+
txBodyHash: result.txBodyHash,
|
|
124
|
+
witnessCount: result.witnessCount,
|
|
125
|
+
signers: result.signerKeyHashes,
|
|
126
|
+
}, 'Signature verification successful');
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
logger.warn({
|
|
130
|
+
error: result.errorMessage,
|
|
131
|
+
warnings: result.warnings,
|
|
132
|
+
}, 'Signature verification failed');
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Verify signed transaction and throw on failure
|
|
138
|
+
*
|
|
139
|
+
* @param signedTxCbor - The signed transaction CBOR
|
|
140
|
+
* @param expectedTxBodyHash - The expected transaction body hash
|
|
141
|
+
* @param options - Additional verification options
|
|
142
|
+
* @throws {TransactionValidationError} if verification fails
|
|
143
|
+
*/
|
|
144
|
+
verifyOrThrow(signedTxCbor, expectedTxBodyHash, options) {
|
|
145
|
+
const result = this.verifySignedTransaction(signedTxCbor, expectedTxBodyHash, options);
|
|
146
|
+
if (!result.isValid) {
|
|
147
|
+
throw new errors_1.TransactionValidationError(result.errorMessage || 'Signature verification failed');
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Create a complete signing workflow state
|
|
153
|
+
*
|
|
154
|
+
* This creates a trackable workflow state that can be stored
|
|
155
|
+
* and updated throughout the signing process.
|
|
156
|
+
*
|
|
157
|
+
* @param signingRequest - The unsigned transaction export payload
|
|
158
|
+
* @returns Initial workflow state
|
|
159
|
+
*/
|
|
160
|
+
createWorkflowState(signingRequest) {
|
|
161
|
+
return {
|
|
162
|
+
status: types_1.SigningStatus.PENDING,
|
|
163
|
+
request: signingRequest,
|
|
164
|
+
timestamps: {
|
|
165
|
+
created: signingRequest.createdAt,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Update workflow state after signing
|
|
171
|
+
*
|
|
172
|
+
* @param state - Current workflow state
|
|
173
|
+
* @param signedTxCbor - The signed transaction CBOR
|
|
174
|
+
* @returns Updated workflow state
|
|
175
|
+
*/
|
|
176
|
+
markAsSigned(state, signedTxCbor) {
|
|
177
|
+
return {
|
|
178
|
+
...state,
|
|
179
|
+
status: types_1.SigningStatus.SIGNED,
|
|
180
|
+
signedTxCbor,
|
|
181
|
+
timestamps: {
|
|
182
|
+
...state.timestamps,
|
|
183
|
+
signed: new Date().toISOString(),
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Update workflow state after verification
|
|
189
|
+
*
|
|
190
|
+
* @param state - Current workflow state
|
|
191
|
+
* @param verificationResult - The verification result
|
|
192
|
+
* @returns Updated workflow state
|
|
193
|
+
*/
|
|
194
|
+
markAsVerified(state, verificationResult) {
|
|
195
|
+
if (!verificationResult.isValid) {
|
|
196
|
+
return this.markAsFailed(state, String(verificationResult.errorMessage));
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
...state,
|
|
200
|
+
status: types_1.SigningStatus.VERIFIED,
|
|
201
|
+
verificationResult,
|
|
202
|
+
timestamps: {
|
|
203
|
+
...state.timestamps,
|
|
204
|
+
verified: new Date().toISOString(),
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Update workflow state after submission
|
|
210
|
+
*
|
|
211
|
+
* @param state - Current workflow state
|
|
212
|
+
* @param txHash - The submitted transaction hash
|
|
213
|
+
* @returns Updated workflow state
|
|
214
|
+
*/
|
|
215
|
+
markAsSubmitted(state, txHash) {
|
|
216
|
+
return {
|
|
217
|
+
...state,
|
|
218
|
+
status: types_1.SigningStatus.SUBMITTED,
|
|
219
|
+
txHash,
|
|
220
|
+
timestamps: {
|
|
221
|
+
...state.timestamps,
|
|
222
|
+
submitted: new Date().toISOString(),
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Update workflow state on failure
|
|
228
|
+
*
|
|
229
|
+
* @param state - Current workflow state
|
|
230
|
+
* @param errorMessage - The error message
|
|
231
|
+
* @returns Updated workflow state
|
|
232
|
+
*/
|
|
233
|
+
markAsFailed(state, errorMessage) {
|
|
234
|
+
return {
|
|
235
|
+
...state,
|
|
236
|
+
status: types_1.SigningStatus.FAILED,
|
|
237
|
+
errorMessage,
|
|
238
|
+
timestamps: {
|
|
239
|
+
...state.timestamps,
|
|
240
|
+
failed: new Date().toISOString(),
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Check if a signing request has expired
|
|
246
|
+
*
|
|
247
|
+
* @param expiresAt - The expiration timestamp (ISO 8601)
|
|
248
|
+
* @returns true if expired
|
|
249
|
+
*/
|
|
250
|
+
isExpired(expiresAt) {
|
|
251
|
+
return new Date(expiresAt) < new Date();
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Validate a signed transaction payload before submission
|
|
255
|
+
*
|
|
256
|
+
* This performs all pre-submission checks:
|
|
257
|
+
* 1. Verify the signature
|
|
258
|
+
* 2. Check transaction hasn't been tampered with
|
|
259
|
+
* 3. Ensure the build ID matches
|
|
260
|
+
*
|
|
261
|
+
* @param payload - The signed transaction payload
|
|
262
|
+
* @param originalBuildTxBodyHash - The original transaction body hash from the build
|
|
263
|
+
* @returns Validation result with verification details
|
|
264
|
+
* @throws {TransactionValidationError} if validation fails
|
|
265
|
+
*/
|
|
266
|
+
validateForSubmission(payload, originalBuildTxBodyHash) {
|
|
267
|
+
// Verify the signature and transaction integrity
|
|
268
|
+
const result = this.verifyOrThrow(payload.signedTxCbor, originalBuildTxBodyHash);
|
|
269
|
+
logger.info({
|
|
270
|
+
signingRequestId: payload.signingRequestId,
|
|
271
|
+
buildId: payload.buildId,
|
|
272
|
+
signerType: payload.signerType,
|
|
273
|
+
witnessCount: result.witnessCount,
|
|
274
|
+
}, 'Transaction validated for submission');
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get the signature verifier instance
|
|
279
|
+
*/
|
|
280
|
+
getVerifier() {
|
|
281
|
+
return this.verifier;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
exports.ExternalSignerModule = ExternalSignerModule;
|
|
285
|
+
// Singleton instance
|
|
286
|
+
let moduleInstance = null;
|
|
287
|
+
/**
|
|
288
|
+
* Get the singleton ExternalSignerModule instance
|
|
289
|
+
*/
|
|
290
|
+
function getExternalSignerModule() {
|
|
291
|
+
if (!moduleInstance) {
|
|
292
|
+
moduleInstance = new ExternalSignerModule();
|
|
293
|
+
}
|
|
294
|
+
return moduleInstance;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Create a new ExternalSignerModule instance with custom options
|
|
298
|
+
*/
|
|
299
|
+
function createExternalSignerModule(options) {
|
|
300
|
+
return new ExternalSignerModule(options);
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=external-signer.js.map
|