/web3.js/test/transaction.test.js

https://github.com/solana-labs/solana · JavaScript · 491 lines · 420 code · 66 blank · 5 comment · 3 complexity · 64f4f21c9df356b48b57d62b45966fac MD5 · raw file

  1. // @flow
  2. import bs58 from 'bs58';
  3. import nacl from 'tweetnacl';
  4. import {Account} from '../src/account';
  5. import {PublicKey} from '../src/publickey';
  6. import {Transaction} from '../src/transaction';
  7. import {StakeProgram} from '../src/stake-program';
  8. import {SystemProgram} from '../src/system-program';
  9. import {Message} from '../src/message';
  10. describe('compileMessage', () => {
  11. test('accountKeys are ordered', () => {
  12. const payer = new Account();
  13. const account2 = new Account();
  14. const account3 = new Account();
  15. const recentBlockhash = new Account().publicKey.toBase58();
  16. const programId = new Account().publicKey;
  17. const transaction = new Transaction({recentBlockhash}).add({
  18. keys: [
  19. {pubkey: account3.publicKey, isSigner: true, isWritable: false},
  20. {pubkey: payer.publicKey, isSigner: true, isWritable: true},
  21. {pubkey: account2.publicKey, isSigner: true, isWritable: true},
  22. ],
  23. programId,
  24. });
  25. transaction.setSigners(
  26. payer.publicKey,
  27. account2.publicKey,
  28. account3.publicKey,
  29. );
  30. const message = transaction.compileMessage();
  31. expect(message.accountKeys[0].equals(payer.publicKey)).toBe(true);
  32. expect(message.accountKeys[1].equals(account2.publicKey)).toBe(true);
  33. expect(message.accountKeys[2].equals(account3.publicKey)).toBe(true);
  34. });
  35. test('payer is first account meta', () => {
  36. const payer = new Account();
  37. const other = new Account();
  38. const recentBlockhash = new Account().publicKey.toBase58();
  39. const programId = new Account().publicKey;
  40. const transaction = new Transaction({recentBlockhash}).add({
  41. keys: [
  42. {pubkey: other.publicKey, isSigner: true, isWritable: true},
  43. {pubkey: payer.publicKey, isSigner: true, isWritable: true},
  44. ],
  45. programId,
  46. });
  47. transaction.sign(payer, other);
  48. const message = transaction.compileMessage();
  49. expect(message.accountKeys[0].equals(payer.publicKey)).toBe(true);
  50. expect(message.accountKeys[1].equals(other.publicKey)).toBe(true);
  51. expect(message.header.numRequiredSignatures).toEqual(2);
  52. expect(message.header.numReadonlySignedAccounts).toEqual(0);
  53. expect(message.header.numReadonlyUnsignedAccounts).toEqual(1);
  54. });
  55. test('validation', () => {
  56. const payer = new Account();
  57. const other = new Account();
  58. const recentBlockhash = new Account().publicKey.toBase58();
  59. const programId = new Account().publicKey;
  60. const transaction = new Transaction();
  61. expect(() => {
  62. transaction.compileMessage();
  63. }).toThrow('Transaction recentBlockhash required');
  64. transaction.recentBlockhash = recentBlockhash;
  65. expect(() => {
  66. transaction.compileMessage();
  67. }).toThrow('No instructions provided');
  68. transaction.add({
  69. keys: [
  70. {pubkey: other.publicKey, isSigner: true, isWritable: true},
  71. {pubkey: payer.publicKey, isSigner: true, isWritable: true},
  72. ],
  73. programId,
  74. });
  75. expect(() => {
  76. transaction.compileMessage();
  77. }).toThrow('Transaction feePayer required');
  78. transaction.setSigners(payer.publicKey);
  79. expect(() => {
  80. transaction.compileMessage();
  81. }).toThrow('missing signer');
  82. transaction.setSigners(payer.publicKey, new Account().publicKey);
  83. expect(() => {
  84. transaction.compileMessage();
  85. }).toThrow('unknown signer');
  86. });
  87. test('payer is writable', () => {
  88. const payer = new Account();
  89. const recentBlockhash = new Account().publicKey.toBase58();
  90. const programId = new Account().publicKey;
  91. const transaction = new Transaction({recentBlockhash}).add({
  92. keys: [{pubkey: payer.publicKey, isSigner: true, isWritable: false}],
  93. programId,
  94. });
  95. transaction.sign(payer);
  96. const message = transaction.compileMessage();
  97. expect(message.accountKeys[0].equals(payer.publicKey)).toBe(true);
  98. expect(message.header.numRequiredSignatures).toEqual(1);
  99. expect(message.header.numReadonlySignedAccounts).toEqual(0);
  100. expect(message.header.numReadonlyUnsignedAccounts).toEqual(1);
  101. });
  102. });
  103. test('partialSign', () => {
  104. const account1 = new Account();
  105. const account2 = new Account();
  106. const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash
  107. const transfer = SystemProgram.transfer({
  108. fromPubkey: account1.publicKey,
  109. toPubkey: account2.publicKey,
  110. lamports: 123,
  111. });
  112. const transaction = new Transaction({recentBlockhash}).add(transfer);
  113. transaction.sign(account1, account2);
  114. const partialTransaction = new Transaction({recentBlockhash}).add(transfer);
  115. partialTransaction.setSigners(account1.publicKey, account2.publicKey);
  116. expect(partialTransaction.signatures[0].signature).toBeNull();
  117. expect(partialTransaction.signatures[1].signature).toBeNull();
  118. partialTransaction.partialSign(account1);
  119. expect(partialTransaction.signatures[0].signature).not.toBeNull();
  120. expect(partialTransaction.signatures[1].signature).toBeNull();
  121. expect(() => partialTransaction.serialize()).toThrow();
  122. expect(() =>
  123. partialTransaction.serialize({requireAllSignatures: false}),
  124. ).not.toThrow();
  125. partialTransaction.partialSign(account2);
  126. expect(partialTransaction.signatures[0].signature).not.toBeNull();
  127. expect(partialTransaction.signatures[1].signature).not.toBeNull();
  128. expect(() => partialTransaction.serialize()).not.toThrow();
  129. expect(partialTransaction).toEqual(transaction);
  130. if (
  131. partialTransaction.signatures[0].signature != null /* <-- pacify flow */
  132. ) {
  133. partialTransaction.signatures[0].signature[0] = 0;
  134. expect(() =>
  135. partialTransaction.serialize({requireAllSignatures: false}),
  136. ).toThrow();
  137. expect(() =>
  138. partialTransaction.serialize({
  139. verifySignatures: false,
  140. requireAllSignatures: false,
  141. }),
  142. ).not.toThrow();
  143. } else {
  144. throw new Error('unreachable');
  145. }
  146. });
  147. describe('dedupe', () => {
  148. const payer = new Account();
  149. const duplicate1 = payer;
  150. const duplicate2 = payer;
  151. const recentBlockhash = new Account().publicKey.toBase58();
  152. const programId = new Account().publicKey;
  153. test('setSigners', () => {
  154. const transaction = new Transaction({recentBlockhash}).add({
  155. keys: [
  156. {pubkey: duplicate1.publicKey, isSigner: true, isWritable: true},
  157. {pubkey: payer.publicKey, isSigner: false, isWritable: true},
  158. {pubkey: duplicate2.publicKey, isSigner: true, isWritable: false},
  159. ],
  160. programId,
  161. });
  162. transaction.setSigners(
  163. payer.publicKey,
  164. duplicate1.publicKey,
  165. duplicate2.publicKey,
  166. );
  167. expect(transaction.signatures.length).toEqual(1);
  168. expect(transaction.signatures[0].publicKey.equals(payer.publicKey)).toBe(
  169. true,
  170. );
  171. const message = transaction.compileMessage();
  172. expect(message.accountKeys[0].equals(payer.publicKey)).toBe(true);
  173. expect(message.header.numRequiredSignatures).toEqual(1);
  174. expect(message.header.numReadonlySignedAccounts).toEqual(0);
  175. expect(message.header.numReadonlyUnsignedAccounts).toEqual(1);
  176. transaction.signatures;
  177. });
  178. test('sign', () => {
  179. const transaction = new Transaction({recentBlockhash}).add({
  180. keys: [
  181. {pubkey: duplicate1.publicKey, isSigner: true, isWritable: true},
  182. {pubkey: payer.publicKey, isSigner: false, isWritable: true},
  183. {pubkey: duplicate2.publicKey, isSigner: true, isWritable: false},
  184. ],
  185. programId,
  186. });
  187. transaction.sign(payer, duplicate1, duplicate2);
  188. expect(transaction.signatures.length).toEqual(1);
  189. expect(transaction.signatures[0].publicKey.equals(payer.publicKey)).toBe(
  190. true,
  191. );
  192. const message = transaction.compileMessage();
  193. expect(message.accountKeys[0].equals(payer.publicKey)).toBe(true);
  194. expect(message.header.numRequiredSignatures).toEqual(1);
  195. expect(message.header.numReadonlySignedAccounts).toEqual(0);
  196. expect(message.header.numReadonlyUnsignedAccounts).toEqual(1);
  197. transaction.signatures;
  198. });
  199. });
  200. test('transfer signatures', () => {
  201. const account1 = new Account();
  202. const account2 = new Account();
  203. const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash
  204. const transfer1 = SystemProgram.transfer({
  205. fromPubkey: account1.publicKey,
  206. toPubkey: account2.publicKey,
  207. lamports: 123,
  208. });
  209. const transfer2 = SystemProgram.transfer({
  210. fromPubkey: account2.publicKey,
  211. toPubkey: account1.publicKey,
  212. lamports: 123,
  213. });
  214. const orgTransaction = new Transaction({recentBlockhash}).add(
  215. transfer1,
  216. transfer2,
  217. );
  218. orgTransaction.sign(account1, account2);
  219. const newTransaction = new Transaction({
  220. recentBlockhash: orgTransaction.recentBlockhash,
  221. signatures: orgTransaction.signatures,
  222. }).add(transfer1, transfer2);
  223. expect(newTransaction).toEqual(orgTransaction);
  224. });
  225. test('dedup signatures', () => {
  226. const account1 = new Account();
  227. const account2 = new Account();
  228. const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash
  229. const transfer1 = SystemProgram.transfer({
  230. fromPubkey: account1.publicKey,
  231. toPubkey: account2.publicKey,
  232. lamports: 123,
  233. });
  234. const transfer2 = SystemProgram.transfer({
  235. fromPubkey: account1.publicKey,
  236. toPubkey: account2.publicKey,
  237. lamports: 123,
  238. });
  239. const orgTransaction = new Transaction({recentBlockhash}).add(
  240. transfer1,
  241. transfer2,
  242. );
  243. orgTransaction.sign(account1);
  244. });
  245. test('use nonce', () => {
  246. const account1 = new Account();
  247. const account2 = new Account();
  248. const nonceAccount = new Account();
  249. const nonce = account2.publicKey.toBase58(); // Fake Nonce hash
  250. const nonceInfo = {
  251. nonce,
  252. nonceInstruction: SystemProgram.nonceAdvance({
  253. noncePubkey: nonceAccount.publicKey,
  254. authorizedPubkey: account1.publicKey,
  255. }),
  256. };
  257. const transferTransaction = new Transaction({nonceInfo}).add(
  258. SystemProgram.transfer({
  259. fromPubkey: account1.publicKey,
  260. toPubkey: account2.publicKey,
  261. lamports: 123,
  262. }),
  263. );
  264. transferTransaction.sign(account1);
  265. let expectedData = Buffer.alloc(4);
  266. expectedData.writeInt32LE(4, 0);
  267. expect(transferTransaction.instructions).toHaveLength(2);
  268. expect(transferTransaction.instructions[0].programId).toEqual(
  269. SystemProgram.programId,
  270. );
  271. expect(transferTransaction.instructions[0].data).toEqual(expectedData);
  272. expect(transferTransaction.recentBlockhash).toEqual(nonce);
  273. const stakeAccount = new Account();
  274. const voteAccount = new Account();
  275. const stakeTransaction = new Transaction({nonceInfo}).add(
  276. StakeProgram.delegate({
  277. stakePubkey: stakeAccount.publicKey,
  278. authorizedPubkey: account1.publicKey,
  279. votePubkey: voteAccount.publicKey,
  280. }),
  281. );
  282. stakeTransaction.sign(account1);
  283. expect(stakeTransaction.instructions).toHaveLength(2);
  284. expect(stakeTransaction.instructions[0].programId).toEqual(
  285. SystemProgram.programId,
  286. );
  287. expect(stakeTransaction.instructions[0].data).toEqual(expectedData);
  288. expect(stakeTransaction.recentBlockhash).toEqual(nonce);
  289. });
  290. test('parse wire format and serialize', () => {
  291. const keypair = nacl.sign.keyPair.fromSeed(
  292. Uint8Array.from(Array(32).fill(8)),
  293. );
  294. const sender = new Account(Buffer.from(keypair.secretKey)); // Arbitrary known account
  295. const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash
  296. const recipient = new PublicKey(
  297. 'J3dxNj7nDRRqRRXuEMynDG57DkZK4jYRuv3Garmb1i99',
  298. ); // Arbitrary known public key
  299. const transfer = SystemProgram.transfer({
  300. fromPubkey: sender.publicKey,
  301. toPubkey: recipient,
  302. lamports: 49,
  303. });
  304. const expectedTransaction = new Transaction({recentBlockhash}).add(transfer);
  305. expectedTransaction.sign(sender);
  306. const wireTransaction = Buffer.from(
  307. 'AVuErQHaXv0SG0/PchunfxHKt8wMRfMZzqV0tkC5qO6owYxWU2v871AoWywGoFQr4z+q/7mE8lIufNl/kxj+nQ0BAAEDE5j2LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9Q5/Mtmcn8onFxt47xKj+XdXXd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0ROug7bEsbx0xxuDkqEvwUusBAgIAAQwCAAAAMQAAAAAAAAA=',
  308. 'base64',
  309. );
  310. const tx = Transaction.from(wireTransaction);
  311. expect(tx).toEqual(expectedTransaction);
  312. expect(wireTransaction).toEqual(expectedTransaction.serialize());
  313. });
  314. test('populate transaction', () => {
  315. const recentBlockhash = new PublicKey(1).toString();
  316. const message = {
  317. accountKeys: [
  318. new PublicKey(1).toString(),
  319. new PublicKey(2).toString(),
  320. new PublicKey(3).toString(),
  321. new PublicKey(4).toString(),
  322. new PublicKey(5).toString(),
  323. ],
  324. header: {
  325. numReadonlySignedAccounts: 0,
  326. numReadonlyUnsignedAccounts: 3,
  327. numRequiredSignatures: 2,
  328. },
  329. instructions: [
  330. {
  331. accounts: [1, 2, 3],
  332. data: bs58.encode(Buffer.alloc(5).fill(9)),
  333. programIdIndex: 4,
  334. },
  335. ],
  336. recentBlockhash,
  337. };
  338. const signatures = [
  339. bs58.encode(Buffer.alloc(64).fill(1)),
  340. bs58.encode(Buffer.alloc(64).fill(2)),
  341. ];
  342. const transaction = Transaction.populate(new Message(message), signatures);
  343. expect(transaction.instructions.length).toEqual(1);
  344. expect(transaction.signatures.length).toEqual(2);
  345. expect(transaction.recentBlockhash).toEqual(recentBlockhash);
  346. });
  347. test('serialize unsigned transaction', () => {
  348. const keypair = nacl.sign.keyPair.fromSeed(
  349. Uint8Array.from(Array(32).fill(8)),
  350. );
  351. const sender = new Account(Buffer.from(keypair.secretKey)); // Arbitrary known account
  352. const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash
  353. const recipient = new PublicKey(
  354. 'J3dxNj7nDRRqRRXuEMynDG57DkZK4jYRuv3Garmb1i99',
  355. ); // Arbitrary known public key
  356. const transfer = SystemProgram.transfer({
  357. fromPubkey: sender.publicKey,
  358. toPubkey: recipient,
  359. lamports: 49,
  360. });
  361. const expectedTransaction = new Transaction({recentBlockhash}).add(transfer);
  362. // Empty signature array fails.
  363. expect(expectedTransaction.signatures.length).toBe(0);
  364. expect(() => {
  365. expectedTransaction.serialize();
  366. }).toThrow(Error);
  367. expect(() => {
  368. expectedTransaction.serialize({verifySignatures: false});
  369. }).toThrow(Error);
  370. expect(() => {
  371. expectedTransaction.serializeMessage();
  372. }).toThrow('Transaction feePayer required');
  373. expectedTransaction.setSigners(sender.publicKey);
  374. expect(expectedTransaction.signatures.length).toBe(1);
  375. // Signature array populated with null signatures fails.
  376. expect(() => {
  377. expectedTransaction.serialize();
  378. }).toThrow(Error);
  379. // Serializing the message is allowed when signature array has null signatures
  380. expectedTransaction.serializeMessage();
  381. const expectedSerializationWithNoSignatures = Buffer.from(
  382. 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
  383. 'AAAAAAAAAAAAAAAAAAABAAEDE5j2LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9' +
  384. 'Q5/Mtmcn8onFxt47xKj+XdXXd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAAAAAAAAAAAAAA' +
  385. 'AAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0ROug7bEsbx0xxuDkqEvwUusBAgIAAQwC' +
  386. 'AAAAMQAAAAAAAAA=',
  387. 'base64',
  388. );
  389. expect(
  390. expectedTransaction.serialize({requireAllSignatures: false}),
  391. ).toStrictEqual(expectedSerializationWithNoSignatures);
  392. // Properly signed transaction succeeds
  393. expectedTransaction.partialSign(sender);
  394. expect(expectedTransaction.signatures.length).toBe(1);
  395. const expectedSerialization = Buffer.from(
  396. 'AVuErQHaXv0SG0/PchunfxHKt8wMRfMZzqV0tkC5qO6owYxWU2v871AoWywGoFQr4z+q/7mE8lIufNl/' +
  397. 'kxj+nQ0BAAEDE5j2LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9Q5/Mtmcn8onFxt47xKj+XdXX' +
  398. 'd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0' +
  399. 'ROug7bEsbx0xxuDkqEvwUusBAgIAAQwCAAAAMQAAAAAAAAA=',
  400. 'base64',
  401. );
  402. expect(expectedTransaction.serialize()).toStrictEqual(expectedSerialization);
  403. expect(expectedTransaction.signatures.length).toBe(1);
  404. });
  405. test('externally signed stake delegate', () => {
  406. const from_keypair = nacl.sign.keyPair.fromSeed(
  407. Uint8Array.from(Array(32).fill(1)),
  408. );
  409. const authority = new Account(Buffer.from(from_keypair.secretKey));
  410. const stake = new PublicKey(2);
  411. const recentBlockhash = new PublicKey(3).toBuffer();
  412. const vote = new PublicKey(4);
  413. var tx = StakeProgram.delegate({
  414. stakePubkey: stake,
  415. authorizedPubkey: authority.publicKey,
  416. votePubkey: vote,
  417. });
  418. const from = authority;
  419. tx.recentBlockhash = bs58.encode(recentBlockhash);
  420. tx.setSigners(from.publicKey);
  421. const tx_bytes = tx.serializeMessage();
  422. const signature = nacl.sign.detached(tx_bytes, from.secretKey);
  423. tx.addSignature(from.publicKey, signature);
  424. expect(tx.verifySignatures()).toBe(true);
  425. });