Skip to content

Commit 8769f0c

Browse files
asimm241zone117x
authored andcommitted
test: add tests for rosetta construction/combine endpoint
1 parent 8d7f0dc commit 8769f0c

File tree

2 files changed

+246
-0
lines changed

2 files changed

+246
-0
lines changed

src/rosetta-helpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,3 +452,10 @@ export function makePresignHash(transaction: StacksTransaction): string | undefi
452452
transaction.auth.spendingCondition?.nonce
453453
);
454454
}
455+
456+
export function getSignature(transaction: StacksTransaction): MessageSignature | undefined {
457+
if (transaction.auth.spendingCondition && isSingleSig(transaction.auth.spendingCondition)) {
458+
return transaction.auth.spendingCondition.signature;
459+
}
460+
return undefined;
461+
}

src/tests-rosetta/api.ts

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ import {
1717
SignedTokenTransferOptions,
1818
StacksTestnet,
1919
standardPrincipalCV,
20+
TransactionSigner,
2021
UnsignedTokenTransferOptions,
2122
} from '@blockstack/stacks-transactions';
2223
import * as BN from 'bn.js';
2324
import { getCoreNodeEndpoint, StacksCoreRpcClient } from '../core-rpc/client';
2425
import { bufferToHexPrefixString } from '../helpers';
2526
import {
27+
RosettaConstructionCombineRequest,
28+
RosettaConstructionCombineResponse,
2629
RosettaConstructionDeriveRequest,
2730
RosettaConstructionDeriveResponse,
2831
RosettaConstructionHashRequest,
@@ -36,6 +39,8 @@ import {
3639
} from '@blockstack/stacks-blockchain-api-types';
3740
import { RosettaConstants, RosettaErrors } from '../api/rosetta-constants';
3841
import { GetStacksTestnetNetwork, testnetKeys } from '../api/routes/debug';
42+
import { getSignature } from '../rosetta-helpers';
43+
import { cloneDeep } from '@blockstack/stacks-transactions/lib/utils';
3944

4045
describe('Rosetta API', () => {
4146
let db: PgDataStore;
@@ -123,6 +128,13 @@ describe('Rosetta API', () => {
123128
{ code: 630, message: 'Amount not available', retriable: false },
124129
{ code: 631, message: 'Fees not available', retriable: false },
125130
{ code: 632, message: 'Public key not available', retriable: false },
131+
{ code: 633, message: 'no signature found', retriable: false },
132+
{ code: 634, message: 'Invalid Signature', retriable: false },
133+
{
134+
code: 635,
135+
message: 'Signature(s) not verified with this public key(s)',
136+
retriable: false,
137+
},
126138
],
127139
historical_balance_lookup: true,
128140
},
@@ -1400,6 +1412,233 @@ describe('Rosetta API', () => {
14001412
expect(JSON.parse(result.text)).toEqual(expectedResponse);
14011413
});
14021414

1415+
test('combine success', async () => {
1416+
const publicKey = publicKeyToString(pubKeyfromPrivKey(testnetKeys[0].secretKey));
1417+
1418+
const txOptions: UnsignedTokenTransferOptions = {
1419+
publicKey: publicKey,
1420+
recipient: standardPrincipalCV(testnetKeys[1].stacksAddress),
1421+
amount: new BigNum(12345),
1422+
network: GetStacksTestnetNetwork(),
1423+
memo: 'test memo',
1424+
nonce: new BigNum(0),
1425+
fee: new BigNum(200),
1426+
};
1427+
1428+
const unsignedTransaction = await makeUnsignedSTXTokenTransfer(txOptions);
1429+
const unsignedSerializedTx = unsignedTransaction.serialize().toString('hex');
1430+
1431+
const signer = new TransactionSigner(unsignedTransaction);
1432+
signer.signOrigin(createStacksPrivateKey(testnetKeys[0].secretKey));
1433+
const signedSerializedTx = unsignedTransaction.serialize().toString('hex');
1434+
1435+
const signature = getSignature(unsignedTransaction);
1436+
if (!signature) return;
1437+
1438+
const request: RosettaConstructionCombineRequest = {
1439+
network_identifier: {
1440+
blockchain: 'stacks',
1441+
network: 'testnet',
1442+
},
1443+
unsigned_transaction: unsignedSerializedTx,
1444+
signatures: [
1445+
{
1446+
signing_payload: {
1447+
hex_bytes: signature.data,
1448+
signature_type: 'ecdsa',
1449+
},
1450+
public_key: {
1451+
hex_bytes: publicKey,
1452+
curve_type: 'secp256k1',
1453+
},
1454+
signature_type: 'ecdsa',
1455+
hex_bytes: signature.data,
1456+
},
1457+
],
1458+
};
1459+
1460+
const result = await supertest(api.server)
1461+
.post(`/rosetta/v1/construction/combine`)
1462+
.send(request);
1463+
1464+
expect(result.status).toBe(200);
1465+
expect(result.type).toBe('application/json');
1466+
1467+
const expectedResponse: RosettaConstructionCombineResponse = {
1468+
signed_transaction: signedSerializedTx,
1469+
};
1470+
1471+
expect(JSON.parse(result.text)).toEqual(expectedResponse);
1472+
});
1473+
1474+
test('combine invalid transaction', async () => {
1475+
const request: RosettaConstructionCombineRequest = {
1476+
network_identifier: {
1477+
blockchain: 'stacks',
1478+
network: 'testnet',
1479+
},
1480+
unsigned_transaction: 'invalid transaction',
1481+
signatures: [
1482+
{
1483+
signing_payload: {
1484+
hex_bytes:
1485+
'0136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f',
1486+
signature_type: 'ecdsa',
1487+
},
1488+
public_key: {
1489+
hex_bytes: '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51',
1490+
curve_type: 'secp256k1',
1491+
},
1492+
signature_type: 'ecdsa',
1493+
hex_bytes:
1494+
'0136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f',
1495+
},
1496+
],
1497+
};
1498+
1499+
const result = await supertest(api.server)
1500+
.post(`/rosetta/v1/construction/combine`)
1501+
.send(request);
1502+
1503+
expect(result.status).toBe(400);
1504+
expect(result.type).toBe('application/json');
1505+
1506+
const expectedResponse = RosettaErrors.invalidTransactionString;
1507+
1508+
expect(JSON.parse(result.text)).toEqual(expectedResponse);
1509+
});
1510+
1511+
test('combine invalid signature', async () => {
1512+
const request: RosettaConstructionCombineRequest = {
1513+
network_identifier: {
1514+
blockchain: 'stacks',
1515+
network: 'testnet',
1516+
},
1517+
unsigned_transaction:
1518+
'00000000010400539886f96611ba3ba6cef9618f8c78118b37c5be0000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003020000000000051ab71a091b4b8b7661a661c620966ab6573bc2dcd3000000000007a12074657374207472616e73616374696f6e000000000000000000000000000000000000',
1519+
signatures: [
1520+
{
1521+
signing_payload: {
1522+
hex_bytes: 'invalid signature',
1523+
signature_type: 'ecdsa',
1524+
},
1525+
public_key: {
1526+
hex_bytes: '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51',
1527+
curve_type: 'secp256k1',
1528+
},
1529+
signature_type: 'ecdsa',
1530+
hex_bytes:
1531+
'0136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f',
1532+
},
1533+
],
1534+
};
1535+
1536+
const result = await supertest(api.server)
1537+
.post(`/rosetta/v1/construction/combine`)
1538+
.send(request);
1539+
1540+
expect(result.status).toBe(400);
1541+
expect(result.type).toBe('application/json');
1542+
1543+
const expectedResponse = RosettaErrors.invalidSignature;
1544+
1545+
expect(JSON.parse(result.text)).toEqual(expectedResponse);
1546+
});
1547+
1548+
test('combine signature not verified', async () => {
1549+
const publicKey = publicKeyToString(pubKeyfromPrivKey(testnetKeys[0].secretKey));
1550+
1551+
const txOptions: UnsignedTokenTransferOptions = {
1552+
publicKey: publicKey,
1553+
recipient: standardPrincipalCV(testnetKeys[1].stacksAddress),
1554+
amount: new BigNum(12345),
1555+
network: GetStacksTestnetNetwork(),
1556+
memo: 'test memo',
1557+
nonce: new BigNum(0),
1558+
fee: new BigNum(200),
1559+
};
1560+
1561+
const unsignedTransaction = await makeUnsignedSTXTokenTransfer(txOptions);
1562+
const unsignedSerializedTx = unsignedTransaction.serialize().toString('hex');
1563+
1564+
const signer = new TransactionSigner(unsignedTransaction);
1565+
signer.signOrigin(createStacksPrivateKey(testnetKeys[1].secretKey)); // use different secret key to sign
1566+
1567+
const signature = getSignature(unsignedTransaction);
1568+
if (!signature) return;
1569+
1570+
const request: RosettaConstructionCombineRequest = {
1571+
network_identifier: {
1572+
blockchain: 'stacks',
1573+
network: 'testnet',
1574+
},
1575+
unsigned_transaction: unsignedSerializedTx,
1576+
signatures: [
1577+
{
1578+
signing_payload: {
1579+
hex_bytes: signature.data,
1580+
signature_type: 'ecdsa',
1581+
},
1582+
public_key: {
1583+
hex_bytes: '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51',
1584+
curve_type: 'secp256k1',
1585+
},
1586+
signature_type: 'ecdsa',
1587+
hex_bytes: signature.data,
1588+
},
1589+
],
1590+
};
1591+
1592+
const result = await supertest(api.server)
1593+
.post(`/rosetta/v1/construction/combine`)
1594+
.send(request);
1595+
1596+
expect(result.status).toBe(400);
1597+
expect(result.type).toBe('application/json');
1598+
1599+
const expectedResponse = RosettaErrors.signatureNotVerified;
1600+
1601+
expect(JSON.parse(result.text)).toEqual(expectedResponse);
1602+
});
1603+
1604+
test('combine invalid public key', async () => {
1605+
const request: RosettaConstructionCombineRequest = {
1606+
network_identifier: {
1607+
blockchain: 'stacks',
1608+
network: 'testnet',
1609+
},
1610+
unsigned_transaction:
1611+
'80800000000400539886f96611ba3ba6cef9618f8c78118b37c5be000000000000000000000000000000b4000136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f03020000000000051ab71a091b4b8b7661a661c620966ab6573bc2dcd3000000000007a12074657374207472616e73616374696f6e000000000000000000000000000000000000',
1612+
signatures: [
1613+
{
1614+
signing_payload: {
1615+
hex_bytes:
1616+
'0136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f',
1617+
signature_type: 'ecdsa',
1618+
},
1619+
public_key: {
1620+
hex_bytes: 'invalid public key',
1621+
curve_type: 'secp256k1',
1622+
},
1623+
signature_type: 'ecdsa',
1624+
hex_bytes:
1625+
'0136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f',
1626+
},
1627+
],
1628+
};
1629+
1630+
const result = await supertest(api.server)
1631+
.post(`/rosetta/v1/construction/combine`)
1632+
.send(request);
1633+
1634+
expect(result.status).toBe(400);
1635+
expect(result.type).toBe('application/json');
1636+
1637+
const expectedResponse = RosettaErrors.signatureNotVerified;
1638+
1639+
expect(JSON.parse(result.text)).toEqual(expectedResponse);
1640+
});
1641+
14031642
/* rosetta construction end */
14041643

14051644
afterAll(async () => {

0 commit comments

Comments
 (0)