Compare commits
	
		
			459 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					8c989da683 | ||
| 
						 | 
					b5305587ea | ||
| 
						 | 
					a61d24bfa9 | ||
| 
						 | 
					c162ac27dd | ||
| 
						 | 
					2b22ef1650 | ||
| 
						 | 
					456e300244 | ||
| 
						 | 
					27948563f5 | ||
| 
						 | 
					af91768a24 | ||
| 
						 | 
					94102f4501 | ||
| 
						 | 
					f53e0af159 | ||
| 
						 | 
					227804c306 | ||
| 
						 | 
					4fd298abd5 | ||
| 
						 | 
					7a33ef1547 | ||
| 
						 | 
					f4a2045876 | ||
| 
						 | 
					dfabe35b27 | ||
| 
						 | 
					75fec798bc | ||
| 
						 | 
					0c4743df03 | ||
| 
						 | 
					29f9c1e1f2 | ||
| 
						 | 
					c4609d0b6d | ||
| 
						 | 
					3b70849885 | ||
| 
						 | 
					5979005454 | ||
| 
						 | 
					27c8e7b905 | ||
| 
						 | 
					3612964a58 | ||
| 
						 | 
					495e237935 | ||
| 
						 | 
					1757cb85a1 | ||
| 
						 | 
					b10fb46a4b | ||
| 
						 | 
					7045aa9760 | ||
| 
						 | 
					e371681f53 | ||
| 
						 | 
					e972db03bd | ||
| 
						 | 
					a8f7a0c648 | ||
| 
						 | 
					687254497c | ||
| 
						 | 
					b8932ad0c8 | ||
| 
						 | 
					1d4dbe0090 | ||
| 
						 | 
					1f8e9c2364 | ||
| 
						 | 
					ac07fb392d | ||
| 
						 | 
					a492357964 | ||
| 
						 | 
					e0bb26d9db | ||
| 
						 | 
					d687dd9f13 | ||
| 
						 | 
					4b649a71df | ||
| 
						 | 
					388a285517 | ||
| 
						 | 
					45c368d670 | ||
| 
						 | 
					bf5609b4fc | ||
| 
						 | 
					d8c0633f0f | ||
| 
						 | 
					aebb8187a8 | ||
| 
						 | 
					159722a960 | ||
| 
						 | 
					c5b36cf18c | ||
| 
						 | 
					fae90ff397 | ||
| 
						 | 
					a91a106319 | ||
| 
						 | 
					7c8debd14a | ||
| 
						 | 
					9c64e5e3b1 | ||
| 
						 | 
					e48ce1e682 | ||
| 
						 | 
					2c239904cc | ||
| 
						 | 
					9e459f0093 | ||
| 
						 | 
					166251fccd | ||
| 
						 | 
					ebbb106fb7 | ||
| 
						 | 
					a018d78056 | ||
| 
						 | 
					67daa6f01e | ||
| 
						 | 
					d5aa648947 | ||
| 
						 | 
					acfd72d7c4 | ||
| 
						 | 
					096d9ce5c7 | ||
| 
						 | 
					a8e522702c | ||
| 
						 | 
					e3753186af | ||
| 
						 | 
					82d9624736 | ||
| 
						 | 
					6101c1d690 | ||
| 
						 | 
					7b7b7be99c | ||
| 
						 | 
					2b4af48537 | ||
| 
						 | 
					2a842408bd | ||
| 
						 | 
					755ccbc253 | ||
| 
						 | 
					cf0de48dc0 | ||
| 
						 | 
					d44058edc3 | ||
| 
						 | 
					5496d52097 | ||
| 
						 | 
					b4da705f97 | ||
| 
						 | 
					75d334147c | ||
| 
						 | 
					006d36dd95 | ||
| 
						 | 
					8c9e17bbab | ||
| 
						 | 
					42017ebe69 | ||
| 
						 | 
					b34f179546 | ||
| 
						 | 
					1afd1db4fc | ||
| 
						 | 
					daba428a3d | ||
| 
						 | 
					11f4443a7b | ||
| 
						 | 
					a8eea4f42d | ||
| 
						 | 
					18e7112608 | ||
| 
						 | 
					857e44c147 | ||
| 
						 | 
					bd0536c80f | ||
| 
						 | 
					184e4253c7 | ||
| 
						 | 
					5828d2cff7 | ||
| 
						 | 
					eeb7503fb6 | ||
| 
						 | 
					7e1aa02ce4 | ||
| 
						 | 
					734e669581 | ||
| 
						 | 
					81db361d77 | ||
| 
						 | 
					97c3ff8a4f | ||
| 
						 | 
					5f85ecd457 | ||
| 
						 | 
					fb1985fe5e | ||
| 
						 | 
					3296c13ef2 | ||
| 
						 | 
					b8cc10749a | ||
| 
						 | 
					c1f9a9a021 | ||
| 
						 | 
					f88b0c4827 | ||
| 
						 | 
					c3564203e9 | ||
| 
						 | 
					de4e548105 | ||
| 
						 | 
					8d67204123 | ||
| 
						 | 
					99a01abfd7 | ||
| 
						 | 
					973b00debb | ||
| 
						 | 
					900139da3e | ||
| 
						 | 
					d94ad7523b | ||
| 
						 | 
					4e0dbd6a73 | ||
| 
						 | 
					9ee69017dc | ||
| 
						 | 
					7c956a87e5 | ||
| 
						 | 
					0beb443d44 | ||
| 
						 | 
					5d8ae9628c | ||
| 
						 | 
					de76df0cbb | ||
| 
						 | 
					a0190b4105 | ||
| 
						 | 
					5255a6ebd2 | ||
| 
						 | 
					8575514235 | ||
| 
						 | 
					2a1946436b | ||
| 
						 | 
					80649a9c3d | ||
| 
						 | 
					ad74ba0eb0 | ||
| 
						 | 
					fd41ad5d8f | ||
| 
						 | 
					63855d5c2a | ||
| 
						 | 
					00251f710e | ||
| 
						 | 
					14bc623989 | ||
| 
						 | 
					fb90fb3feb | ||
| 
						 | 
					d6ca879d39 | ||
| 
						 | 
					2dcde5281d | ||
| 
						 | 
					e2626dad83 | ||
| 
						 | 
					070fbeb69a | ||
| 
						 | 
					497ec24754 | ||
| 
						 | 
					1bda09bf0e | ||
| 
						 | 
					7ca7f8604d | ||
| 
						 | 
					e2b5f2dd9c | ||
| 
						 | 
					3652bd57a9 | ||
| 
						 | 
					5077d6bfb3 | ||
| 
						 | 
					f0ee3e9deb | ||
| 
						 | 
					babad39846 | ||
| 
						 | 
					c15aa4a968 | ||
| 
						 | 
					3124a88284 | ||
| 
						 | 
					e76a2065e3 | ||
| 
						 | 
					45f8e453a9 | ||
| 
						 | 
					20f9c12855 | ||
| 
						 | 
					4218414c87 | ||
| 
						 | 
					60c91d386f | ||
| 
						 | 
					e477501687 | ||
| 
						 | 
					20463e141e | ||
| 
						 | 
					e699462ed3 | ||
| 
						 | 
					8b345f3258 | ||
| 
						 | 
					56436a6271 | ||
| 
						 | 
					805ea6f469 | ||
| 
						 | 
					1db1d173fc | ||
| 
						 | 
					11476038cd | ||
| 
						 | 
					a669ef3abb | ||
| 
						 | 
					dbbdfa1dbb | ||
| 
						 | 
					768c6b4bef | ||
| 
						 | 
					8bcc04c275 | ||
| 
						 | 
					2fd822887f | ||
| 
						 | 
					e2c8aa0847 | ||
| 
						 | 
					9b049402c9 | ||
| 
						 | 
					d0e1779893 | ||
| 
						 | 
					929ffc5a4e | ||
| 
						 | 
					1f63fb06f1 | ||
| 
						 | 
					b49aa125c9 | ||
| 
						 | 
					55836d133e | ||
| 
						 | 
					277e402d55 | ||
| 
						 | 
					0ab8312b23 | ||
| 
						 | 
					bc4c5c5a97 | ||
| 
						 | 
					1a9aa78129 | ||
| 
						 | 
					798a6db915 | ||
| 
						 | 
					0a4a3fd37e | ||
| 
						 | 
					66242eab41 | ||
| 
						 | 
					7f0d4f0656 | ||
| 
						 | 
					acba8d6026 | ||
| 
						 | 
					1ff9555099 | ||
| 
						 | 
					72a13e2a72 | ||
| 
						 | 
					74cdfc2213 | ||
| 
						 | 
					7b8e5a9f47 | ||
| 
						 | 
					80525ac862 | ||
| 
						 | 
					c14f98c6fc | ||
| 
						 | 
					c6edfc3944 | ||
| 
						 | 
					b95c493d66 | ||
| 
						 | 
					5871462241 | ||
| 
						 | 
					53bb826375 | ||
| 
						 | 
					c769bcc418 | ||
| 
						 | 
					f06a4c7861 | ||
| 
						 | 
					0cae099d12 | ||
| 
						 | 
					4bc3653906 | ||
| 
						 | 
					3e7050983a | ||
| 
						 | 
					9f1bb75445 | ||
| 
						 | 
					139bb32dba | ||
| 
						 | 
					158f6f3725 | ||
| 
						 | 
					e33f9ea6b5 | ||
| 
						 | 
					473037db86 | ||
| 
						 | 
					b0e14ea83c | ||
| 
						 | 
					782a549613 | ||
| 
						 | 
					c805f7dc4e | ||
| 
						 | 
					782829152e | ||
| 
						 | 
					da6f09afb8 | ||
| 
						 | 
					004b1b9c3f | ||
| 
						 | 
					2f8d0f88d6 | ||
| 
						 | 
					177d241160 | ||
| 
						 | 
					5323622842 | ||
| 
						 | 
					c852923347 | ||
| 
						 | 
					5dc4410d58 | ||
| 
						 | 
					da4642d634 | ||
| 
						 | 
					a264be1791 | ||
| 
						 | 
					9aff121949 | ||
| 
						 | 
					a7f4d1487a | ||
| 
						 | 
					11e43e1654 | ||
| 
						 | 
					82be47bc18 | ||
| 
						 | 
					6498e4fbf6 | ||
| 
						 | 
					9978971bd9 | ||
| 
						 | 
					e28ac2c377 | ||
| 
						 | 
					ef296aa7db | ||
| 
						 | 
					43e7107f65 | ||
| 
						 | 
					752fa29390 | ||
| 
						 | 
					7bb7b42356 | ||
| 
						 | 
					2a7fc744f9 | ||
| 
						 | 
					90e3da0389 | ||
| 
						 | 
					1a62bcee42 | ||
| 
						 | 
					b83a4cae90 | ||
| 
						 | 
					05ef21cd3b | ||
| 
						 | 
					dfa27b04d7 | ||
| 
						 | 
					880b04906e | ||
| 
						 | 
					1fe0b1e516 | ||
| 
						 | 
					f9fd4bd24c | ||
| 
						 | 
					c55a11d160 | ||
| 
						 | 
					92118de0e1 | ||
| 
						 | 
					0d9802a2cd | ||
| 
						 | 
					f6beede01b | ||
| 
						 | 
					ff48ea20de | ||
| 
						 | 
					dd9cb18d65 | ||
| 
						 | 
					71932aed0a | ||
| 
						 | 
					24dc6680e1 | ||
| 
						 | 
					61d9d40e48 | ||
| 
						 | 
					e9b40db319 | ||
| 
						 | 
					316356861d | ||
| 
						 | 
					e07c00710a | ||
| 
						 | 
					bc47c80610 | ||
| 
						 | 
					14baa511f0 | ||
| 
						 | 
					e773faeb24 | ||
| 
						 | 
					42847516a2 | ||
| 
						 | 
					47e9a1ae4f | ||
| 
						 | 
					549a154394 | ||
| 
						 | 
					dca00d1bde | ||
| 
						 | 
					45ce1b4f96 | ||
| 
						 | 
					a9232c0633 | ||
| 
						 | 
					3da254c745 | ||
| 
						 | 
					9ba3ee9683 | ||
| 
						 | 
					b0addba2a9 | ||
| 
						 | 
					bb59525ff8 | ||
| 
						 | 
					acd25124d4 | ||
| 
						 | 
					d718ab2491 | ||
| 
						 | 
					1860aacd1f | ||
| 
						 | 
					d4bbb7f516 | ||
| 
						 | 
					d1c0f4b4f1 | ||
| 
						 | 
					b72b837ba2 | ||
| 
						 | 
					fde85c96c0 | ||
| 
						 | 
					121418dad2 | ||
| 
						 | 
					f44f94fe23 | ||
| 
						 | 
					55a4481022 | ||
| 
						 | 
					e859ad37a8 | ||
| 
						 | 
					1a28c7fc12 | ||
| 
						 | 
					c706a07764 | ||
| 
						 | 
					59568e5776 | ||
| 
						 | 
					33ca8fa72a | ||
| 
						 | 
					4bb66a81fb | ||
| 
						 | 
					468c14b14f | ||
| 
						 | 
					03e505897a | ||
| 
						 | 
					5205eb382e | ||
| 
						 | 
					b07b6e56fa | ||
| 
						 | 
					bcc890e705 | ||
| 
						 | 
					07d14f6f07 | ||
| 
						 | 
					03b213e296 | ||
| 
						 | 
					1bfce24c9f | ||
| 
						 | 
					94b2565969 | ||
| 
						 | 
					2896fdb603 | ||
| 
						 | 
					50970bc8f9 | ||
| 
						 | 
					10df45b173 | ||
| 
						 | 
					d3b8129593 | ||
| 
						 | 
					f7fb5aebac | ||
| 
						 | 
					9311a6e356 | ||
| 
						 | 
					8c706892df | ||
| 
						 | 
					7f2b11756c | ||
| 
						 | 
					f324547600 | ||
| 
						 | 
					36e8977f1d | ||
| 
						 | 
					b88db2689e | ||
| 
						 | 
					1584ec220c | ||
| 
						 | 
					fb366a7236 | ||
| 
						 | 
					b903158543 | ||
| 
						 | 
					9dad9c6333 | ||
| 
						 | 
					a6658b9d75 | ||
| 
						 | 
					a97feedcc1 | ||
| 
						 | 
					8021bce41f | ||
| 
						 | 
					d8fa19336c | ||
| 
						 | 
					191483cf9f | ||
| 
						 | 
					1eb8314d42 | ||
| 
						 | 
					88eeb817e4 | ||
| 
						 | 
					b777126bd2 | ||
| 
						 | 
					89d78dcfcf | ||
| 
						 | 
					1cf142c193 | ||
| 
						 | 
					3e29325410 | ||
| 
						 | 
					4dc98c3dbd | ||
| 
						 | 
					9caad645e2 | ||
| 
						 | 
					6cb76ac326 | ||
| 
						 | 
					0001e5c0a1 | ||
| 
						 | 
					ab32d13da1 | ||
| 
						 | 
					cefe46e981 | ||
| 
						 | 
					f4d70e78b6 | ||
| 
						 | 
					d130adf582 | ||
| 
						 | 
					1e6285e64e | ||
| 
						 | 
					e3c90c3807 | ||
| 
						 | 
					85750307aa | ||
| 
						 | 
					0ee4a5e799 | ||
| 
						 | 
					55cb9cf681 | ||
| 
						 | 
					d3af7e0653 | ||
| 
						 | 
					729a24d557 | ||
| 
						 | 
					55b92c16da | ||
| 
						 | 
					835bacce4f | ||
| 
						 | 
					ccb7b1a698 | ||
| 
						 | 
					85dbdeb4c3 | ||
| 
						 | 
					397f9f11c5 | ||
| 
						 | 
					a11986ad1d | ||
| 
						 | 
					a4d373f0af | ||
| 
						 | 
					52eea215ce | ||
| 
						 | 
					6f48aafd3a | ||
| 
						 | 
					2d94c09aee | ||
| 
						 | 
					9699b61679 | ||
| 
						 | 
					8865bfbd59 | ||
| 
						 | 
					5f80c1d37d | ||
| 
						 | 
					f616f5dec6 | ||
| 
						 | 
					db1003b5f8 | ||
| 
						 | 
					f52ff777b7 | ||
| 
						 | 
					4314a29953 | ||
| 
						 | 
					e560fff840 | ||
| 
						 | 
					5ac747ea7d | ||
| 
						 | 
					f522dc1e18 | ||
| 
						 | 
					486812bf54 | ||
| 
						 | 
					7df8f76df1 | ||
| 
						 | 
					bbe4990e80 | ||
| 
						 | 
					a5baaf790d | ||
| 
						 | 
					0a36ed1b8c | ||
| 
						 | 
					b7ad240375 | ||
| 
						 | 
					2cc71f2d55 | ||
| 
						 | 
					3125c74681 | ||
| 
						 | 
					d5b1dee8d6 | ||
| 
						 | 
					4b33a2a1b8 | ||
| 
						 | 
					58e6a5c281 | ||
| 
						 | 
					7eb61074ab | ||
| 
						 | 
					9b2edbaa9b | ||
| 
						 | 
					e8659b45c7 | ||
| 
						 | 
					a9553cb401 | ||
| 
						 | 
					800c409698 | ||
| 
						 | 
					b6f484ddee | ||
| 
						 | 
					3c39fee5a8 | ||
| 
						 | 
					560f34d1f6 | ||
| 
						 | 
					dbda50941a | ||
| 
						 | 
					f1e68ac25c | ||
| 
						 | 
					95029b9b05 | ||
| 
						 | 
					a789bf4761 | ||
| 
						 | 
					d2e7ffa8b9 | ||
| 
						 | 
					0914519f6a | ||
| 
						 | 
					43cd5f3730 | ||
| 
						 | 
					d396a5f45a | ||
| 
						 | 
					76a7071dba | ||
| 
						 | 
					133baa8ce6 | ||
| 
						 | 
					5df3510fde | ||
| 
						 | 
					357339273f | ||
| 
						 | 
					2500881e0b | ||
| 
						 | 
					0013bfff4e | ||
| 
						 | 
					f13498b428 | ||
| 
						 | 
					b567138170 | ||
| 
						 | 
					653982cae5 | ||
| 
						 | 
					605f4906ba | ||
| 
						 | 
					d27f24e312 | ||
| 
						 | 
					c9c1cb5c9c | ||
| 
						 | 
					1cc6493ccf | ||
| 
						 | 
					ae47862be2 | ||
| 
						 | 
					8590184df7 | ||
| 
						 | 
					d840bbab08 | ||
| 
						 | 
					63314de516 | ||
| 
						 | 
					c47a6e12c7 | ||
| 
						 | 
					7937c45ba4 | ||
| 
						 | 
					813b11ac56 | ||
| 
						 | 
					ad6883b66a | ||
| 
						 | 
					a8f4c4e297 | ||
| 
						 | 
					6d68e94e4e | ||
| 
						 | 
					5dd40d7d88 | ||
| 
						 | 
					3f58177670 | ||
| 
						 | 
					edfd65b115 | ||
| 
						 | 
					51da66ec84 | ||
| 
						 | 
					ba36308d69 | ||
| 
						 | 
					ee450b2dd0 | ||
| 
						 | 
					84b28fb261 | ||
| 
						 | 
					1586b86797 | ||
| 
						 | 
					8f065e487e | ||
| 
						 | 
					953eadd983 | ||
| 
						 | 
					a4a792facd | ||
| 
						 | 
					055f808f98 | ||
| 
						 | 
					0404878445 | ||
| 
						 | 
					053907f8a4 | ||
| 
						 | 
					f76dcc1f05 | ||
| 
						 | 
					823bc138cd | ||
| 
						 | 
					18f746b025 | ||
| 
						 | 
					c81adaf901 | ||
| 
						 | 
					2d12ddd0f6 | ||
| 
						 | 
					bee36cc8d0 | ||
| 
						 | 
					f7aee67023 | ||
| 
						 | 
					c021727009 | ||
| 
						 | 
					6653136e1d | ||
| 
						 | 
					06c40c807c | ||
| 
						 | 
					9b262b4915 | ||
| 
						 | 
					cc2d3ecfd7 | ||
| 
						 | 
					92743499bf | ||
| 
						 | 
					aa6a00a03e | ||
| 
						 | 
					bd19f7c4cb | ||
| 
						 | 
					988bf65ba4 | ||
| 
						 | 
					d5b03bd824 | ||
| 
						 | 
					6a72dab111 | ||
| 
						 | 
					56e8319a6d | ||
| 
						 | 
					aed1e51ef1 | ||
| 
						 | 
					f4278d61df | ||
| 
						 | 
					a5c3ae3cef | ||
| 
						 | 
					05c052e212 | ||
| 
						 | 
					dc05bb648a | ||
| 
						 | 
					800b65b2f6 | ||
| 
						 | 
					ae1a0f57c5 | ||
| 
						 | 
					df7c44bd0c | ||
| 
						 | 
					3e29cfd712 | ||
| 
						 | 
					202031538f | ||
| 
						 | 
					29ff1b925d | ||
| 
						 | 
					5a91db6e62 | ||
| 
						 | 
					94ba700e58 | ||
| 
						 | 
					1964c6ec29 | ||
| 
						 | 
					4dd6591bfd | ||
| 
						 | 
					163217815b | ||
| 
						 | 
					37c182cd5d | ||
| 
						 | 
					0c68f27ac3 | ||
| 
						 | 
					5fb8da9b35 | ||
| 
						 | 
					74d9fd1e4f | ||
| 
						 | 
					e71206c578 | ||
| 
						 | 
					0141c80238 | ||
| 
						 | 
					ed928cfdf7 | ||
| 
						 | 
					2fd319ab7a | ||
| 
						 | 
					7813a1decd | ||
| 
						 | 
					93e4ed1f75 | ||
| 
						 | 
					a70f31b3da | ||
| 
						 | 
					2d25227d0a | ||
| 
						 | 
					fc7bfd0f67 | ||
| 
						 | 
					2996291b37 | ||
| 
						 | 
					3e80b9231c | ||
| 
						 | 
					78231a8682 | ||
| 
						 | 
					ace711e7f1 | ||
| 
						 | 
					c9cbc39ec9 | ||
| 
						 | 
					606a392d50 | ||
| 
						 | 
					c67596ceb4 | ||
| 
						 | 
					9a42cc7555 | ||
| 
						 | 
					2e5ef2a802 | ||
| 
						 | 
					8c8e2c4b2b | ||
| 
						 | 
					0578801f99 | ||
| 
						 | 
					6141e1410a | ||
| 
						 | 
					4fc86807ff | ||
| 
						 | 
					d2a2eba69e | 
							
								
								
									
										14
									
								
								.buildkite/env/secrets.ejson
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.buildkite/env/secrets.ejson
									
									
									
									
										vendored
									
									
								
							@@ -1,12 +1,12 @@
 | 
			
		||||
{
 | 
			
		||||
    "_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
 | 
			
		||||
    "environment": {
 | 
			
		||||
      "CODECOV_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:JnxhrIxh09AvqdJgrVSYmb7PxSrh19aE:07WzVExCHEd1lJ1m8QizRRthGri+WBNeZRKjjEvsy5eo4gv3HD7zVEm42tVTGkqITKkBNQ==]",
 | 
			
		||||
      "CRATES_IO_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:d0jJqC32/axwzq/N7kMRmpxKhnRrhtpt:zvcPHwkOzGnjhNkAQSejwdy1Jkr9wR1qXFFCnfIjyt/XQYubzB1tLkoly/qdmeb5]",
 | 
			
		||||
      "GEOLOCATION_API_KEY": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R4gfB6Ey4i50HyfLt4UZDLBqg3qHEUye:UfZCOgt8XI6Y2g+ivCRVoS1fjFycFs7/GSevvCqh1B50mG0+hzpEyzXQLuKG5OeI]",
 | 
			
		||||
      "GITHUB_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Vq2dkGTOzfEpRht0BAGHFp/hDogMvXJe:tFXHg1epVt2mq9hkuc5sRHe+KAnVREi/p8S+IZu67XRyzdiA/nGak1k860FXYuuzuaE0QWekaEc=]",
 | 
			
		||||
      "INFLUX_DATABASE": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:5KI9WBkXx3R/W4m256mU5MJOE7N8aAT9:Cb8QFELZ9I60t5zhJ9h55Kcs]",
 | 
			
		||||
      "INFLUX_PASSWORD": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:hQRMpLCrav+OYkNphkeM4hagdVoZv5Iw:AUO76rr6+gF1OLJA8ZLSG8wHKXgYCPNk6gRCV8rBhZBJ4KwDaxpvOhMl7bxxXG6jol7v4aRa/Lk=]",
 | 
			
		||||
      "INFLUX_USERNAME": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R7BNmQjfeqoGDAFTJu9bYTGHol2NgnYN:Q2tOT/EBcFvhFk+DKLKmVU7tLCpVC3Ui]"
 | 
			
		||||
      "CODECOV_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:3K68mE38LJ2RB98VWmjuNLFBNn1XTGR4:cR4r05/TOZQKmEZp1v4CSgUJtC6QJiOaL85QjXW0qZ061fMnsBA8AtAPMDoDq4WCGOZM1A==]",
 | 
			
		||||
      "CRATES_IO_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:GGRTYDjMXksevzR6kq4Jx+FaIQZz50RU:xkbwDxcgoCyU+aT2tiI9mymigrEl6YiOr3axe3aX70ELIBKbCdPGilXP/wixvKi94g2u]",
 | 
			
		||||
      "GEOLOCATION_API_KEY": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:U2PZLi5MU3Ru/zK1SilianEeizcMvxml:AJKf2OAtDHmJh0KyXrBnNnistItZvVVP3cZ7ZLtrVupjmWN/PzmKwSsXeCNObWS+]",
 | 
			
		||||
      "GITHUB_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:0NJNlpD/O19mvOakCGBYDhIDfySxWFSC:Dz4NXv9x6ncRQ1u9sVoWOcqmkg0sI09qmefghB0GXZgPcFGgn6T0mw7ynNnbUvjyH8dLruKHauk=]",
 | 
			
		||||
      "INFLUX_DATABASE": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:SzwHIeOVpmbTcGQOGngoFgYumsLZJUGq:t7Rpk49njsWvoM+ztv5Uwuiz]",
 | 
			
		||||
      "INFLUX_PASSWORD": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:/MUs+q7pdGrUjzwcq+6pgIFxur4hxdqu:am22z2E2dtmw1f1J1Mq5JLcUHZsrEjQAJ0pp21M4AZeJbNO6bVb44d9zSkHj7xdN6U+GNlCk+wU=]",
 | 
			
		||||
      "INFLUX_USERNAME": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:XjghH20xGVWro9B+epGlJaJcW8Wze0Bi:ZIdOtXudTY5TqKseDU7gVvQXfmXV99Xh]"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,16 +3,19 @@
 | 
			
		||||
#
 | 
			
		||||
# Save target/ for the next CI build on this machine
 | 
			
		||||
#
 | 
			
		||||
(
 | 
			
		||||
  set -x
 | 
			
		||||
  d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL"
 | 
			
		||||
  mkdir -p "$d"
 | 
			
		||||
  set -x
 | 
			
		||||
  rsync -a --delete --link-dest="$PWD" target "$d"
 | 
			
		||||
  du -hs "$d"
 | 
			
		||||
  read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
 | 
			
		||||
  echo "--- ${cacheSizeInGB}GB: $d"
 | 
			
		||||
)
 | 
			
		||||
if [[ -z $CARGO_TARGET_CACHE ]]; then
 | 
			
		||||
  echo "+++ CARGO_TARGET_CACHE not defined" # pre-command should have defined it
 | 
			
		||||
else
 | 
			
		||||
  (
 | 
			
		||||
    set -x
 | 
			
		||||
    mkdir -p "$CARGO_TARGET_CACHE"
 | 
			
		||||
    set -x
 | 
			
		||||
    rsync -a --delete --link-dest="$PWD" target "$CARGO_TARGET_CACHE"
 | 
			
		||||
    du -hs "$CARGO_TARGET_CACHE"
 | 
			
		||||
    read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$CARGO_TARGET_CACHE")
 | 
			
		||||
    echo "--- ${cacheSizeInGB}GB: $CARGO_TARGET_CACHE"
 | 
			
		||||
  )
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Add job_stats data point
 | 
			
		||||
 
 | 
			
		||||
@@ -11,23 +11,24 @@ export PS4="++"
 | 
			
		||||
#
 | 
			
		||||
# Restore target/ from the previous CI build on this machine
 | 
			
		||||
#
 | 
			
		||||
eval "$(ci/channel-info.sh)"
 | 
			
		||||
export CARGO_TARGET_CACHE=$HOME/cargo-target-cache/"$CHANNEL"-"$BUILDKITE_LABEL"
 | 
			
		||||
(
 | 
			
		||||
  set -x
 | 
			
		||||
  d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL"
 | 
			
		||||
  MAX_CACHE_SIZE=18 # gigabytes
 | 
			
		||||
 | 
			
		||||
  if [[ -d $d ]]; then
 | 
			
		||||
    du -hs "$d"
 | 
			
		||||
    read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
 | 
			
		||||
    echo "--- ${cacheSizeInGB}GB: $d"
 | 
			
		||||
  if [[ -d $CARGO_TARGET_CACHE ]]; then
 | 
			
		||||
    du -hs "$CARGO_TARGET_CACHE"
 | 
			
		||||
    read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$CARGO_TARGET_CACHE")
 | 
			
		||||
    echo "--- ${cacheSizeInGB}GB: $CARGO_TARGET_CACHE"
 | 
			
		||||
    if [[ $cacheSizeInGB -gt $MAX_CACHE_SIZE ]]; then
 | 
			
		||||
      echo "--- $d is too large, removing it"
 | 
			
		||||
      rm -rf "$d"
 | 
			
		||||
      echo "--- $CARGO_TARGET_CACHE is too large, removing it"
 | 
			
		||||
      rm -rf "$CARGO_TARGET_CACHE"
 | 
			
		||||
    fi
 | 
			
		||||
  else
 | 
			
		||||
    echo "--- $d not present"
 | 
			
		||||
    echo "--- $CARGO_TARGET_CACHE not present"
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  mkdir -p "$d"/target
 | 
			
		||||
  rsync -a --delete --link-dest="$d" "$d"/target .
 | 
			
		||||
  mkdir -p "$CARGO_TARGET_CACHE"/target
 | 
			
		||||
  rsync -a --delete --link-dest="$CARGO_TARGET_CACHE" "$CARGO_TARGET_CACHE"/target .
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
root: ./docs/src
 | 
			
		||||
 | 
			
		||||
structure:
 | 
			
		||||
    readme: introduction.md
 | 
			
		||||
    summary: SUMMARY.md
 | 
			
		||||
 | 
			
		||||
redirects:
 | 
			
		||||
    wallet: ./wallet-guide/README.md
 | 
			
		||||
    wallet/app-wallets: ./wallet-guide/apps.md
 | 
			
		||||
    wallet/app-wallets/trust-wallet: ./wallet-guide/trust-wallet.md
 | 
			
		||||
    wallet/app-wallets/ledger-live:  ./wallet-guide/ledger-live.md
 | 
			
		||||
    wallet/cli-wallets:  ./wallet-guide/cli.md
 | 
			
		||||
    wallet/cli-wallets/paper-wallet:  ./paper-wallet/README.md
 | 
			
		||||
    wallet/cli-wallets/paper-wallet/paper-wallet-usage: ./paper-wallet/paper-wallet-usage.md
 | 
			
		||||
    wallet/cli-wallets/remote-wallet: ./hardware-wallets/README.md
 | 
			
		||||
    wallet/cli-wallets/remote-wallet/ledger: ./hardware-wallets/ledger.md
 | 
			
		||||
    wallet/cli-wallets/file-system-wallet: ./file-system-wallet/README.md
 | 
			
		||||
    wallet/support: ./wallet-guide/support.md
 | 
			
		||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -23,3 +23,7 @@ log-*/
 | 
			
		||||
/.idea/
 | 
			
		||||
/solana.iml
 | 
			
		||||
/.vscode/
 | 
			
		||||
 | 
			
		||||
# fetch-spl.sh artifacts
 | 
			
		||||
/spl-genesis-args.sh
 | 
			
		||||
/spl_*.so
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								.mergify.yml
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								.mergify.yml
									
									
									
									
									
								
							@@ -1,9 +1,40 @@
 | 
			
		||||
# Validate your changes with:
 | 
			
		||||
#
 | 
			
		||||
#   $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate
 | 
			
		||||
#   $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate/
 | 
			
		||||
#
 | 
			
		||||
# https://doc.mergify.io/
 | 
			
		||||
pull_request_rules:
 | 
			
		||||
  - name: automatic merge (squash) on CI success
 | 
			
		||||
    conditions:
 | 
			
		||||
      - status-success=buildkite/solana
 | 
			
		||||
      #- status-success=Travis CI - Pull Request
 | 
			
		||||
      - status-success=ci-gate
 | 
			
		||||
      - label=automerge
 | 
			
		||||
      - author≠@dont-squash-my-commits
 | 
			
		||||
    actions:
 | 
			
		||||
      merge:
 | 
			
		||||
        method: squash
 | 
			
		||||
  # Join the dont-squash-my-commits group if you won't like your commits squashed
 | 
			
		||||
  - name: automatic merge (rebase) on CI success
 | 
			
		||||
    conditions:
 | 
			
		||||
      - status-success=buildkite/solana
 | 
			
		||||
      #- status-success=Travis CI - Pull Request
 | 
			
		||||
      - status-success=ci-gate
 | 
			
		||||
      - label=automerge
 | 
			
		||||
      - author=@dont-squash-my-commits
 | 
			
		||||
    actions:
 | 
			
		||||
      merge:
 | 
			
		||||
        method: rebase
 | 
			
		||||
  - name: remove automerge label on CI failure
 | 
			
		||||
    conditions:
 | 
			
		||||
      - label=automerge
 | 
			
		||||
      - "#status-failure!=0"
 | 
			
		||||
    actions:
 | 
			
		||||
      label:
 | 
			
		||||
        remove:
 | 
			
		||||
          - automerge
 | 
			
		||||
      comment:
 | 
			
		||||
        message: automerge label removed due to a CI failure
 | 
			
		||||
  - name: remove outdated reviews
 | 
			
		||||
    conditions:
 | 
			
		||||
      - base=master
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										96
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								.travis.yml
									
									
									
									
									
								
							@@ -1,18 +1,3 @@
 | 
			
		||||
os:
 | 
			
		||||
  - osx
 | 
			
		||||
  - windows
 | 
			
		||||
 | 
			
		||||
language: rust
 | 
			
		||||
rust:
 | 
			
		||||
  - stable
 | 
			
		||||
 | 
			
		||||
install:
 | 
			
		||||
  - source ci/rust-version.sh
 | 
			
		||||
 | 
			
		||||
script:
 | 
			
		||||
  - source ci/env.sh
 | 
			
		||||
  - ci/publish-tarball.sh
 | 
			
		||||
 | 
			
		||||
branches:
 | 
			
		||||
  only:
 | 
			
		||||
    - master
 | 
			
		||||
@@ -23,21 +8,66 @@ notifications:
 | 
			
		||||
    on_success: change
 | 
			
		||||
    secure: F4IjOE05MyaMOdPRL+r8qhs7jBvv4yDM3RmFKE1zNXnfUOqV4X38oQM1EI+YVsgpMQLj/pxnEB7wcTE4Bf86N6moLssEULCpvAuMVoXj4QbWdomLX+01WbFa6fLVeNQIg45NHrz2XzVBhoKOrMNnl+QI5mbR2AlS5oqsudHsXDnyLzZtd4Y5SDMdYG1zVWM01+oNNjgNfjcCGmOE/K0CnOMl6GPi3X9C34tJ19P2XT7MTDsz1/IfEF7fro2Q8DHEYL9dchJMoisXSkem5z7IDQkGzXsWdWT4NnndUvmd1MlTCE9qgoXDqRf95Qh8sB1Dz08HtvgfaosP2XjtNTfDI9BBYS15Ibw9y7PchAJE1luteNjF35EOy6OgmCLw/YpnweqfuNViBZz+yOPWXVC0kxnPIXKZ1wyH9ibeH6E4hr7a8o9SV/6SiWIlbYF+IR9jPXyTCLP/cc3sYljPWxDnhWFwFdRVIi3PbVAhVu7uWtVUO17Oc9gtGPgs/GrhOMkJfwQPXaudRJDpVZowxTX4x9kefNotlMAMRgq+Drbmgt4eEBiCNp0ITWgh17BiE1U09WS3myuduhoct85+FoVeaUkp1sxzHVtGsNQH0hcz7WcpZyOM+AwistJA/qzeEDQao5zi1eKWPbO2xAhi2rV1bDH6bPf/4lDBwLRqSiwvlWU=
 | 
			
		||||
 | 
			
		||||
deploy:
 | 
			
		||||
  - provider: s3
 | 
			
		||||
    access_key_id: $AWS_ACCESS_KEY_ID
 | 
			
		||||
    secret_access_key: $AWS_SECRET_ACCESS_KEY
 | 
			
		||||
    bucket: release.solana.com
 | 
			
		||||
    region: us-west-1
 | 
			
		||||
    skip_cleanup: true
 | 
			
		||||
    acl: public_read
 | 
			
		||||
    local_dir: travis-s3-upload
 | 
			
		||||
    on:
 | 
			
		||||
      all_branches: true
 | 
			
		||||
  - provider: releases
 | 
			
		||||
    api_key: $GITHUB_TOKEN
 | 
			
		||||
    skip_cleanup: true
 | 
			
		||||
    file_glob: true
 | 
			
		||||
    file: travis-release-upload/*
 | 
			
		||||
    on:
 | 
			
		||||
      tags: true
 | 
			
		||||
os: linux
 | 
			
		||||
dist: bionic
 | 
			
		||||
language: minimal
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  include:
 | 
			
		||||
    - &release-artifacts
 | 
			
		||||
      if: type = push
 | 
			
		||||
      name: "macOS release artifacts"
 | 
			
		||||
      os: osx
 | 
			
		||||
      language: rust
 | 
			
		||||
      rust:
 | 
			
		||||
        - stable
 | 
			
		||||
      install:
 | 
			
		||||
        - source ci/rust-version.sh
 | 
			
		||||
      script:
 | 
			
		||||
        - source ci/env.sh
 | 
			
		||||
        - ci/publish-tarball.sh
 | 
			
		||||
      deploy:
 | 
			
		||||
        - provider: s3
 | 
			
		||||
          access_key_id: $AWS_ACCESS_KEY_ID
 | 
			
		||||
          secret_access_key: $AWS_SECRET_ACCESS_KEY
 | 
			
		||||
          bucket: release.solana.com
 | 
			
		||||
          region: us-west-1
 | 
			
		||||
          skip_cleanup: true
 | 
			
		||||
          acl: public_read
 | 
			
		||||
          local_dir: travis-s3-upload
 | 
			
		||||
          on:
 | 
			
		||||
            all_branches: true
 | 
			
		||||
        - provider: releases
 | 
			
		||||
          token: $GITHUB_TOKEN
 | 
			
		||||
          skip_cleanup: true
 | 
			
		||||
          file_glob: true
 | 
			
		||||
          file: travis-release-upload/*
 | 
			
		||||
          on:
 | 
			
		||||
            tags: true
 | 
			
		||||
    - <<: *release-artifacts
 | 
			
		||||
      name: "Windows release artifacts"
 | 
			
		||||
      os: windows
 | 
			
		||||
 | 
			
		||||
    # docs pull request or commit
 | 
			
		||||
    - name: "docs"
 | 
			
		||||
      if: type IN (push, pull_request) OR tag IS present
 | 
			
		||||
      language: node_js
 | 
			
		||||
      node_js:
 | 
			
		||||
        - "node"
 | 
			
		||||
 | 
			
		||||
      services:
 | 
			
		||||
        - docker
 | 
			
		||||
 | 
			
		||||
      cache:
 | 
			
		||||
        directories:
 | 
			
		||||
          - ~/.npm
 | 
			
		||||
 | 
			
		||||
      before_install:
 | 
			
		||||
        - source ci/env.sh
 | 
			
		||||
        - .travis/channel_restriction.sh edge beta || travis_terminate 0
 | 
			
		||||
        - .travis/affects.sh docs/ .travis || travis_terminate 0
 | 
			
		||||
        - cd docs/
 | 
			
		||||
        - source .travis/before_install.sh
 | 
			
		||||
 | 
			
		||||
      script:
 | 
			
		||||
        - source .travis/script.sh
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								.travis/affects.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										25
									
								
								.travis/affects.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
#
 | 
			
		||||
# Check if files in the commit range match one or more prefixes
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# Always run the job if we are on a tagged release
 | 
			
		||||
if [[ -n "$TRAVIS_TAG" ]]; then
 | 
			
		||||
  exit 0
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
(
 | 
			
		||||
  set -x
 | 
			
		||||
  git diff --name-only "$TRAVIS_COMMIT_RANGE"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
for file in $(git diff --name-only "$TRAVIS_COMMIT_RANGE"); do
 | 
			
		||||
  for prefix in "$@"; do
 | 
			
		||||
    if [[ $file =~ ^"$prefix" ]]; then
 | 
			
		||||
      exit 0
 | 
			
		||||
    fi
 | 
			
		||||
    done
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
echo "No modifications to $*"
 | 
			
		||||
exit 1
 | 
			
		||||
							
								
								
									
										17
									
								
								.travis/channel_restriction.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								.travis/channel_restriction.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
#
 | 
			
		||||
# Only proceed if we are on one of the channels passed in when calling this file
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
set -ex
 | 
			
		||||
 | 
			
		||||
eval "$(ci/channel-info.sh)"
 | 
			
		||||
 | 
			
		||||
for acceptable_channel in "$@"; do
 | 
			
		||||
  if [[ "$CHANNEL" == "$acceptable_channel" ]]; then
 | 
			
		||||
    exit 0
 | 
			
		||||
  fi
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
echo "Not running from one of the following channels: $*"
 | 
			
		||||
exit 1
 | 
			
		||||
							
								
								
									
										3208
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3208
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -6,6 +6,7 @@ members = [
 | 
			
		||||
    "accounts-bench",
 | 
			
		||||
    "banking-bench",
 | 
			
		||||
    "cli-config",
 | 
			
		||||
    "cli-output",
 | 
			
		||||
    "client",
 | 
			
		||||
    "core",
 | 
			
		||||
    "dos",
 | 
			
		||||
@@ -25,6 +26,7 @@ members = [
 | 
			
		||||
    "log-analyzer",
 | 
			
		||||
    "merkle-tree",
 | 
			
		||||
    "stake-o-matic",
 | 
			
		||||
    "storage-bigtable",
 | 
			
		||||
    "streamer",
 | 
			
		||||
    "measure",
 | 
			
		||||
    "metrics",
 | 
			
		||||
@@ -52,6 +54,7 @@ members = [
 | 
			
		||||
    "sys-tuner",
 | 
			
		||||
    "tokens",
 | 
			
		||||
    "transaction-status",
 | 
			
		||||
    "account-decoder",
 | 
			
		||||
    "upload-perf",
 | 
			
		||||
    "net-utils",
 | 
			
		||||
    "version",
 | 
			
		||||
@@ -63,6 +66,4 @@ members = [
 | 
			
		||||
 | 
			
		||||
exclude = [
 | 
			
		||||
    "programs/bpf",
 | 
			
		||||
    "programs/move_loader",
 | 
			
		||||
    "programs/librapay",
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -116,7 +116,8 @@ There are three release channels that map to branches as follows:
 | 
			
		||||
 | 
			
		||||
1. After the new release has been tagged, update the Cargo.toml files on **release branch** to the next semantic version (e.g. 0.9.0 -> 0.9.1) with:
 | 
			
		||||
     ```
 | 
			
		||||
     scripts/increment-cargo-version.sh patch
 | 
			
		||||
     $ scripts/increment-cargo-version.sh patch
 | 
			
		||||
     $ ./scripts/cargo-for-all-lock-files.sh tree
 | 
			
		||||
     ```
 | 
			
		||||
1. Rebuild to get an updated version of `Cargo.lock`:
 | 
			
		||||
    ```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								account-decoder/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								account-decoder/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "solana-account-decoder"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
description = "Solana account decoder"
 | 
			
		||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
 | 
			
		||||
repository = "https://github.com/solana-labs/solana"
 | 
			
		||||
homepage = "https://solana.com/"
 | 
			
		||||
license = "Apache-2.0"
 | 
			
		||||
edition = "2018"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
bincode = "1.3.1"
 | 
			
		||||
base64 = "0.12.3"
 | 
			
		||||
bs58 = "0.3.1"
 | 
			
		||||
bv = "0.11.1"
 | 
			
		||||
Inflector = "0.11.4"
 | 
			
		||||
lazy_static = "1.4.0"
 | 
			
		||||
serde = "1.0.112"
 | 
			
		||||
serde_derive = "1.0.103"
 | 
			
		||||
serde_json = "1.0.54"
 | 
			
		||||
solana-config-program = { path = "../programs/config", version = "1.2.32" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.32" }
 | 
			
		||||
solana-stake-program = { path = "../programs/stake", version = "1.2.32" }
 | 
			
		||||
solana-vote-program = { path = "../programs/vote", version = "1.2.32" }
 | 
			
		||||
spl-token-v2-0 = { package = "spl-token", version = "2.0.6", features = ["skip-no-mangle"] }
 | 
			
		||||
thiserror = "1.0"
 | 
			
		||||
 | 
			
		||||
[package.metadata.docs.rs]
 | 
			
		||||
targets = ["x86_64-unknown-linux-gnu"]
 | 
			
		||||
							
								
								
									
										182
									
								
								account-decoder/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								account-decoder/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
#[macro_use]
 | 
			
		||||
extern crate lazy_static;
 | 
			
		||||
#[macro_use]
 | 
			
		||||
extern crate serde_derive;
 | 
			
		||||
 | 
			
		||||
pub mod parse_account_data;
 | 
			
		||||
pub mod parse_config;
 | 
			
		||||
pub mod parse_nonce;
 | 
			
		||||
pub mod parse_stake;
 | 
			
		||||
pub mod parse_sysvar;
 | 
			
		||||
pub mod parse_token;
 | 
			
		||||
pub mod parse_vote;
 | 
			
		||||
pub mod validator_info;
 | 
			
		||||
 | 
			
		||||
use crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount};
 | 
			
		||||
use solana_sdk::{account::Account, clock::Epoch, fee_calculator::FeeCalculator, pubkey::Pubkey};
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
pub type StringAmount = String;
 | 
			
		||||
 | 
			
		||||
/// A duplicate representation of an Account for pretty JSON serialization
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiAccount {
 | 
			
		||||
    pub lamports: u64,
 | 
			
		||||
    pub data: UiAccountData,
 | 
			
		||||
    pub owner: String,
 | 
			
		||||
    pub executable: bool,
 | 
			
		||||
    pub rent_epoch: Epoch,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase", untagged)]
 | 
			
		||||
pub enum UiAccountData {
 | 
			
		||||
    LegacyBinary(String), // Legacy. Retained for RPC backwards compatibility
 | 
			
		||||
    Json(ParsedAccount),
 | 
			
		||||
    Binary(String, UiAccountEncoding),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum UiAccountEncoding {
 | 
			
		||||
    Binary, // Legacy. Retained for RPC backwards compatibility
 | 
			
		||||
    Base58,
 | 
			
		||||
    Base64,
 | 
			
		||||
    JsonParsed,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl UiAccount {
 | 
			
		||||
    pub fn encode(
 | 
			
		||||
        pubkey: &Pubkey,
 | 
			
		||||
        account: Account,
 | 
			
		||||
        encoding: UiAccountEncoding,
 | 
			
		||||
        additional_data: Option<AccountAdditionalData>,
 | 
			
		||||
        data_slice_config: Option<UiDataSliceConfig>,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let data = match encoding {
 | 
			
		||||
            UiAccountEncoding::Binary => UiAccountData::LegacyBinary(
 | 
			
		||||
                bs58::encode(slice_data(&account.data, data_slice_config)).into_string(),
 | 
			
		||||
            ),
 | 
			
		||||
            UiAccountEncoding::Base58 => UiAccountData::Binary(
 | 
			
		||||
                bs58::encode(slice_data(&account.data, data_slice_config)).into_string(),
 | 
			
		||||
                encoding,
 | 
			
		||||
            ),
 | 
			
		||||
            UiAccountEncoding::Base64 => UiAccountData::Binary(
 | 
			
		||||
                base64::encode(slice_data(&account.data, data_slice_config)),
 | 
			
		||||
                encoding,
 | 
			
		||||
            ),
 | 
			
		||||
            UiAccountEncoding::JsonParsed => {
 | 
			
		||||
                if let Ok(parsed_data) =
 | 
			
		||||
                    parse_account_data(pubkey, &account.owner, &account.data, additional_data)
 | 
			
		||||
                {
 | 
			
		||||
                    UiAccountData::Json(parsed_data)
 | 
			
		||||
                } else {
 | 
			
		||||
                    UiAccountData::Binary(base64::encode(&account.data), UiAccountEncoding::Base64)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        UiAccount {
 | 
			
		||||
            lamports: account.lamports,
 | 
			
		||||
            data,
 | 
			
		||||
            owner: account.owner.to_string(),
 | 
			
		||||
            executable: account.executable,
 | 
			
		||||
            rent_epoch: account.rent_epoch,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn decode(&self) -> Option<Account> {
 | 
			
		||||
        let data = match &self.data {
 | 
			
		||||
            UiAccountData::Json(_) => None,
 | 
			
		||||
            UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(),
 | 
			
		||||
            UiAccountData::Binary(blob, encoding) => match encoding {
 | 
			
		||||
                UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(),
 | 
			
		||||
                UiAccountEncoding::Base64 => base64::decode(blob).ok(),
 | 
			
		||||
                UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None,
 | 
			
		||||
            },
 | 
			
		||||
        }?;
 | 
			
		||||
        Some(Account {
 | 
			
		||||
            lamports: self.lamports,
 | 
			
		||||
            data,
 | 
			
		||||
            owner: Pubkey::from_str(&self.owner).ok()?,
 | 
			
		||||
            executable: self.executable,
 | 
			
		||||
            rent_epoch: self.rent_epoch,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiFeeCalculator {
 | 
			
		||||
    pub lamports_per_signature: StringAmount,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<FeeCalculator> for UiFeeCalculator {
 | 
			
		||||
    fn from(fee_calculator: FeeCalculator) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            lamports_per_signature: fee_calculator.lamports_per_signature.to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for UiFeeCalculator {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            lamports_per_signature: "0".to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiDataSliceConfig {
 | 
			
		||||
    pub offset: usize,
 | 
			
		||||
    pub length: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn slice_data(data: &[u8], data_slice_config: Option<UiDataSliceConfig>) -> &[u8] {
 | 
			
		||||
    if let Some(UiDataSliceConfig { offset, length }) = data_slice_config {
 | 
			
		||||
        if offset >= data.len() {
 | 
			
		||||
            &[]
 | 
			
		||||
        } else if length > data.len() - offset {
 | 
			
		||||
            &data[offset..]
 | 
			
		||||
        } else {
 | 
			
		||||
            &data[offset..offset + length]
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        data
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_slice_data() {
 | 
			
		||||
        let data = vec![1, 2, 3, 4, 5];
 | 
			
		||||
        let slice_config = Some(UiDataSliceConfig {
 | 
			
		||||
            offset: 0,
 | 
			
		||||
            length: 5,
 | 
			
		||||
        });
 | 
			
		||||
        assert_eq!(slice_data(&data, slice_config), &data[..]);
 | 
			
		||||
 | 
			
		||||
        let slice_config = Some(UiDataSliceConfig {
 | 
			
		||||
            offset: 0,
 | 
			
		||||
            length: 10,
 | 
			
		||||
        });
 | 
			
		||||
        assert_eq!(slice_data(&data, slice_config), &data[..]);
 | 
			
		||||
 | 
			
		||||
        let slice_config = Some(UiDataSliceConfig {
 | 
			
		||||
            offset: 1,
 | 
			
		||||
            length: 2,
 | 
			
		||||
        });
 | 
			
		||||
        assert_eq!(slice_data(&data, slice_config), &data[1..3]);
 | 
			
		||||
 | 
			
		||||
        let slice_config = Some(UiDataSliceConfig {
 | 
			
		||||
            offset: 10,
 | 
			
		||||
            length: 2,
 | 
			
		||||
        });
 | 
			
		||||
        assert_eq!(slice_data(&data, slice_config), &[] as &[u8]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										145
									
								
								account-decoder/src/parse_account_data.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								account-decoder/src/parse_account_data.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    parse_config::parse_config,
 | 
			
		||||
    parse_nonce::parse_nonce,
 | 
			
		||||
    parse_stake::parse_stake,
 | 
			
		||||
    parse_sysvar::parse_sysvar,
 | 
			
		||||
    parse_token::{parse_token, spl_token_id_v2_0},
 | 
			
		||||
    parse_vote::parse_vote,
 | 
			
		||||
};
 | 
			
		||||
use inflector::Inflector;
 | 
			
		||||
use serde_json::Value;
 | 
			
		||||
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program, sysvar};
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
lazy_static! {
 | 
			
		||||
    static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id();
 | 
			
		||||
    static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id();
 | 
			
		||||
    static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
 | 
			
		||||
    static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id();
 | 
			
		||||
    static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0();
 | 
			
		||||
    static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
 | 
			
		||||
    pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
 | 
			
		||||
        let mut m = HashMap::new();
 | 
			
		||||
        m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
 | 
			
		||||
        m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
 | 
			
		||||
        m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
 | 
			
		||||
        m.insert(*STAKE_PROGRAM_ID, ParsableAccount::Stake);
 | 
			
		||||
        m.insert(*SYSVAR_PROGRAM_ID, ParsableAccount::Sysvar);
 | 
			
		||||
        m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
 | 
			
		||||
        m
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Error, Debug)]
 | 
			
		||||
pub enum ParseAccountError {
 | 
			
		||||
    #[error("{0:?} account not parsable")]
 | 
			
		||||
    AccountNotParsable(ParsableAccount),
 | 
			
		||||
 | 
			
		||||
    #[error("Program not parsable")]
 | 
			
		||||
    ProgramNotParsable,
 | 
			
		||||
 | 
			
		||||
    #[error("Additional data required to parse: {0}")]
 | 
			
		||||
    AdditionalDataMissing(String),
 | 
			
		||||
 | 
			
		||||
    #[error("Instruction error")]
 | 
			
		||||
    InstructionError(#[from] InstructionError),
 | 
			
		||||
 | 
			
		||||
    #[error("Serde json error")]
 | 
			
		||||
    SerdeJsonError(#[from] serde_json::error::Error),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct ParsedAccount {
 | 
			
		||||
    pub program: String,
 | 
			
		||||
    pub parsed: Value,
 | 
			
		||||
    pub space: u64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum ParsableAccount {
 | 
			
		||||
    Config,
 | 
			
		||||
    Nonce,
 | 
			
		||||
    SplToken,
 | 
			
		||||
    Stake,
 | 
			
		||||
    Sysvar,
 | 
			
		||||
    Vote,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct AccountAdditionalData {
 | 
			
		||||
    pub spl_token_decimals: Option<u8>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse_account_data(
 | 
			
		||||
    pubkey: &Pubkey,
 | 
			
		||||
    program_id: &Pubkey,
 | 
			
		||||
    data: &[u8],
 | 
			
		||||
    additional_data: Option<AccountAdditionalData>,
 | 
			
		||||
) -> Result<ParsedAccount, ParseAccountError> {
 | 
			
		||||
    let program_name = PARSABLE_PROGRAM_IDS
 | 
			
		||||
        .get(program_id)
 | 
			
		||||
        .ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
 | 
			
		||||
    let additional_data = additional_data.unwrap_or_default();
 | 
			
		||||
    let parsed_json = match program_name {
 | 
			
		||||
        ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
 | 
			
		||||
        ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
 | 
			
		||||
        ParsableAccount::SplToken => {
 | 
			
		||||
            serde_json::to_value(parse_token(data, additional_data.spl_token_decimals)?)?
 | 
			
		||||
        }
 | 
			
		||||
        ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
 | 
			
		||||
        ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
 | 
			
		||||
        ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
 | 
			
		||||
    };
 | 
			
		||||
    Ok(ParsedAccount {
 | 
			
		||||
        program: format!("{:?}", program_name).to_kebab_case(),
 | 
			
		||||
        parsed: parsed_json,
 | 
			
		||||
        space: data.len() as u64,
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use solana_sdk::nonce::{
 | 
			
		||||
        state::{Data, Versions},
 | 
			
		||||
        State,
 | 
			
		||||
    };
 | 
			
		||||
    use solana_vote_program::vote_state::{VoteState, VoteStateVersions};
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_account_data() {
 | 
			
		||||
        let account_pubkey = Pubkey::new_rand();
 | 
			
		||||
        let other_program = Pubkey::new_rand();
 | 
			
		||||
        let data = vec![0; 4];
 | 
			
		||||
        assert!(parse_account_data(&account_pubkey, &other_program, &data, None).is_err());
 | 
			
		||||
 | 
			
		||||
        let vote_state = VoteState::default();
 | 
			
		||||
        let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
 | 
			
		||||
        let versioned = VoteStateVersions::Current(Box::new(vote_state));
 | 
			
		||||
        VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
 | 
			
		||||
        let parsed = parse_account_data(
 | 
			
		||||
            &account_pubkey,
 | 
			
		||||
            &solana_vote_program::id(),
 | 
			
		||||
            &vote_account_data,
 | 
			
		||||
            None,
 | 
			
		||||
        )
 | 
			
		||||
        .unwrap();
 | 
			
		||||
        assert_eq!(parsed.program, "vote".to_string());
 | 
			
		||||
        assert_eq!(parsed.space, VoteState::size_of() as u64);
 | 
			
		||||
 | 
			
		||||
        let nonce_data = Versions::new_current(State::Initialized(Data::default()));
 | 
			
		||||
        let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
 | 
			
		||||
        let parsed = parse_account_data(
 | 
			
		||||
            &account_pubkey,
 | 
			
		||||
            &system_program::id(),
 | 
			
		||||
            &nonce_account_data,
 | 
			
		||||
            None,
 | 
			
		||||
        )
 | 
			
		||||
        .unwrap();
 | 
			
		||||
        assert_eq!(parsed.program, "nonce".to_string());
 | 
			
		||||
        assert_eq!(parsed.space, State::size() as u64);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										146
									
								
								account-decoder/src/parse_config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								account-decoder/src/parse_config.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    parse_account_data::{ParsableAccount, ParseAccountError},
 | 
			
		||||
    validator_info,
 | 
			
		||||
};
 | 
			
		||||
use bincode::deserialize;
 | 
			
		||||
use serde_json::Value;
 | 
			
		||||
use solana_config_program::{get_config_data, ConfigKeys};
 | 
			
		||||
use solana_sdk::pubkey::Pubkey;
 | 
			
		||||
use solana_stake_program::config::Config as StakeConfig;
 | 
			
		||||
 | 
			
		||||
pub fn parse_config(data: &[u8], pubkey: &Pubkey) -> Result<ConfigAccountType, ParseAccountError> {
 | 
			
		||||
    let parsed_account = if pubkey == &solana_stake_program::config::id() {
 | 
			
		||||
        get_config_data(data)
 | 
			
		||||
            .ok()
 | 
			
		||||
            .and_then(|data| deserialize::<StakeConfig>(data).ok())
 | 
			
		||||
            .map(|config| ConfigAccountType::StakeConfig(config.into()))
 | 
			
		||||
    } else {
 | 
			
		||||
        deserialize::<ConfigKeys>(data).ok().and_then(|key_list| {
 | 
			
		||||
            if !key_list.keys.is_empty() && key_list.keys[0].0 == validator_info::id() {
 | 
			
		||||
                parse_config_data::<String>(data, key_list.keys).and_then(|validator_info| {
 | 
			
		||||
                    Some(ConfigAccountType::ValidatorInfo(UiConfig {
 | 
			
		||||
                        keys: validator_info.keys,
 | 
			
		||||
                        config_data: serde_json::from_str(&validator_info.config_data).ok()?,
 | 
			
		||||
                    }))
 | 
			
		||||
                })
 | 
			
		||||
            } else {
 | 
			
		||||
                None
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    };
 | 
			
		||||
    parsed_account.ok_or(ParseAccountError::AccountNotParsable(
 | 
			
		||||
        ParsableAccount::Config,
 | 
			
		||||
    ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_config_data<T>(data: &[u8], keys: Vec<(Pubkey, bool)>) -> Option<UiConfig<T>>
 | 
			
		||||
where
 | 
			
		||||
    T: serde::de::DeserializeOwned,
 | 
			
		||||
{
 | 
			
		||||
    let config_data: T = deserialize(&get_config_data(data).ok()?).ok()?;
 | 
			
		||||
    let keys = keys
 | 
			
		||||
        .iter()
 | 
			
		||||
        .map(|key| UiConfigKey {
 | 
			
		||||
            pubkey: key.0.to_string(),
 | 
			
		||||
            signer: key.1,
 | 
			
		||||
        })
 | 
			
		||||
        .collect();
 | 
			
		||||
    Some(UiConfig { keys, config_data })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
 | 
			
		||||
pub enum ConfigAccountType {
 | 
			
		||||
    StakeConfig(UiStakeConfig),
 | 
			
		||||
    ValidatorInfo(UiConfig<Value>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiConfigKey {
 | 
			
		||||
    pub pubkey: String,
 | 
			
		||||
    pub signer: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiStakeConfig {
 | 
			
		||||
    pub warmup_cooldown_rate: f64,
 | 
			
		||||
    pub slash_penalty: u8,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<StakeConfig> for UiStakeConfig {
 | 
			
		||||
    fn from(config: StakeConfig) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            warmup_cooldown_rate: config.warmup_cooldown_rate,
 | 
			
		||||
            slash_penalty: config.slash_penalty,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiConfig<T> {
 | 
			
		||||
    pub keys: Vec<UiConfigKey>,
 | 
			
		||||
    pub config_data: T,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::validator_info::ValidatorInfo;
 | 
			
		||||
    use serde_json::json;
 | 
			
		||||
    use solana_config_program::create_config_account;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_config() {
 | 
			
		||||
        let stake_config = StakeConfig {
 | 
			
		||||
            warmup_cooldown_rate: 0.25,
 | 
			
		||||
            slash_penalty: 50,
 | 
			
		||||
        };
 | 
			
		||||
        let stake_config_account = create_config_account(vec![], &stake_config, 10);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_config(
 | 
			
		||||
                &stake_config_account.data,
 | 
			
		||||
                &solana_stake_program::config::id()
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap(),
 | 
			
		||||
            ConfigAccountType::StakeConfig(UiStakeConfig {
 | 
			
		||||
                warmup_cooldown_rate: 0.25,
 | 
			
		||||
                slash_penalty: 50,
 | 
			
		||||
            }),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let validator_info = ValidatorInfo {
 | 
			
		||||
            info: serde_json::to_string(&json!({
 | 
			
		||||
                "name": "Solana",
 | 
			
		||||
            }))
 | 
			
		||||
            .unwrap(),
 | 
			
		||||
        };
 | 
			
		||||
        let info_pubkey = Pubkey::new_rand();
 | 
			
		||||
        let validator_info_config_account = create_config_account(
 | 
			
		||||
            vec![(validator_info::id(), false), (info_pubkey, true)],
 | 
			
		||||
            &validator_info,
 | 
			
		||||
            10,
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_config(&validator_info_config_account.data, &info_pubkey).unwrap(),
 | 
			
		||||
            ConfigAccountType::ValidatorInfo(UiConfig {
 | 
			
		||||
                keys: vec![
 | 
			
		||||
                    UiConfigKey {
 | 
			
		||||
                        pubkey: validator_info::id().to_string(),
 | 
			
		||||
                        signer: false,
 | 
			
		||||
                    },
 | 
			
		||||
                    UiConfigKey {
 | 
			
		||||
                        pubkey: info_pubkey.to_string(),
 | 
			
		||||
                        signer: true,
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                config_data: serde_json::from_str(r#"{"name":"Solana"}"#).unwrap(),
 | 
			
		||||
            }),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let bad_data = vec![0; 4];
 | 
			
		||||
        assert!(parse_config(&bad_data, &info_pubkey).is_err());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										67
									
								
								account-decoder/src/parse_nonce.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								account-decoder/src/parse_nonce.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
use crate::{parse_account_data::ParseAccountError, UiFeeCalculator};
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    instruction::InstructionError,
 | 
			
		||||
    nonce::{state::Versions, State},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn parse_nonce(data: &[u8]) -> Result<UiNonceState, ParseAccountError> {
 | 
			
		||||
    let nonce_state: Versions = bincode::deserialize(data)
 | 
			
		||||
        .map_err(|_| ParseAccountError::from(InstructionError::InvalidAccountData))?;
 | 
			
		||||
    let nonce_state = nonce_state.convert_to_current();
 | 
			
		||||
    match nonce_state {
 | 
			
		||||
        State::Uninitialized => Ok(UiNonceState::Uninitialized),
 | 
			
		||||
        State::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData {
 | 
			
		||||
            authority: data.authority.to_string(),
 | 
			
		||||
            blockhash: data.blockhash.to_string(),
 | 
			
		||||
            fee_calculator: data.fee_calculator.into(),
 | 
			
		||||
        })),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A duplicate representation of NonceState for pretty JSON serialization
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
 | 
			
		||||
pub enum UiNonceState {
 | 
			
		||||
    Uninitialized,
 | 
			
		||||
    Initialized(UiNonceData),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiNonceData {
 | 
			
		||||
    pub authority: String,
 | 
			
		||||
    pub blockhash: String,
 | 
			
		||||
    pub fee_calculator: UiFeeCalculator,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use solana_sdk::{
 | 
			
		||||
        hash::Hash,
 | 
			
		||||
        nonce::{
 | 
			
		||||
            state::{Data, Versions},
 | 
			
		||||
            State,
 | 
			
		||||
        },
 | 
			
		||||
        pubkey::Pubkey,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_nonce() {
 | 
			
		||||
        let nonce_data = Versions::new_current(State::Initialized(Data::default()));
 | 
			
		||||
        let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_nonce(&nonce_account_data).unwrap(),
 | 
			
		||||
            UiNonceState::Initialized(UiNonceData {
 | 
			
		||||
                authority: Pubkey::default().to_string(),
 | 
			
		||||
                blockhash: Hash::default().to_string(),
 | 
			
		||||
                fee_calculator: UiFeeCalculator {
 | 
			
		||||
                    lamports_per_signature: 0.to_string(),
 | 
			
		||||
                },
 | 
			
		||||
            }),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let bad_data = vec![0; 4];
 | 
			
		||||
        assert!(parse_nonce(&bad_data).is_err());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										236
									
								
								account-decoder/src/parse_stake.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								account-decoder/src/parse_stake.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,236 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    parse_account_data::{ParsableAccount, ParseAccountError},
 | 
			
		||||
    StringAmount,
 | 
			
		||||
};
 | 
			
		||||
use bincode::deserialize;
 | 
			
		||||
use solana_sdk::clock::{Epoch, UnixTimestamp};
 | 
			
		||||
use solana_stake_program::stake_state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState};
 | 
			
		||||
 | 
			
		||||
pub fn parse_stake(data: &[u8]) -> Result<StakeAccountType, ParseAccountError> {
 | 
			
		||||
    let stake_state: StakeState = deserialize(data)
 | 
			
		||||
        .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Stake))?;
 | 
			
		||||
    let parsed_account = match stake_state {
 | 
			
		||||
        StakeState::Uninitialized => StakeAccountType::Uninitialized,
 | 
			
		||||
        StakeState::Initialized(meta) => StakeAccountType::Initialized(UiStakeAccount {
 | 
			
		||||
            meta: meta.into(),
 | 
			
		||||
            stake: None,
 | 
			
		||||
        }),
 | 
			
		||||
        StakeState::Stake(meta, stake) => StakeAccountType::Delegated(UiStakeAccount {
 | 
			
		||||
            meta: meta.into(),
 | 
			
		||||
            stake: Some(stake.into()),
 | 
			
		||||
        }),
 | 
			
		||||
        StakeState::RewardsPool => StakeAccountType::RewardsPool,
 | 
			
		||||
    };
 | 
			
		||||
    Ok(parsed_account)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
 | 
			
		||||
#[allow(clippy::large_enum_variant)]
 | 
			
		||||
pub enum StakeAccountType {
 | 
			
		||||
    Uninitialized,
 | 
			
		||||
    Initialized(UiStakeAccount),
 | 
			
		||||
    Delegated(UiStakeAccount),
 | 
			
		||||
    RewardsPool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiStakeAccount {
 | 
			
		||||
    pub meta: UiMeta,
 | 
			
		||||
    pub stake: Option<UiStake>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiMeta {
 | 
			
		||||
    pub rent_exempt_reserve: StringAmount,
 | 
			
		||||
    pub authorized: UiAuthorized,
 | 
			
		||||
    pub lockup: UiLockup,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<Meta> for UiMeta {
 | 
			
		||||
    fn from(meta: Meta) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            rent_exempt_reserve: meta.rent_exempt_reserve.to_string(),
 | 
			
		||||
            authorized: meta.authorized.into(),
 | 
			
		||||
            lockup: meta.lockup.into(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiLockup {
 | 
			
		||||
    pub unix_timestamp: UnixTimestamp,
 | 
			
		||||
    pub epoch: Epoch,
 | 
			
		||||
    pub custodian: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<Lockup> for UiLockup {
 | 
			
		||||
    fn from(lockup: Lockup) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            unix_timestamp: lockup.unix_timestamp,
 | 
			
		||||
            epoch: lockup.epoch,
 | 
			
		||||
            custodian: lockup.custodian.to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiAuthorized {
 | 
			
		||||
    pub staker: String,
 | 
			
		||||
    pub withdrawer: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<Authorized> for UiAuthorized {
 | 
			
		||||
    fn from(authorized: Authorized) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            staker: authorized.staker.to_string(),
 | 
			
		||||
            withdrawer: authorized.withdrawer.to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiStake {
 | 
			
		||||
    pub delegation: UiDelegation,
 | 
			
		||||
    pub credits_observed: u64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<Stake> for UiStake {
 | 
			
		||||
    fn from(stake: Stake) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            delegation: stake.delegation.into(),
 | 
			
		||||
            credits_observed: stake.credits_observed,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiDelegation {
 | 
			
		||||
    pub voter: String,
 | 
			
		||||
    pub stake: StringAmount,
 | 
			
		||||
    pub activation_epoch: StringAmount,
 | 
			
		||||
    pub deactivation_epoch: StringAmount,
 | 
			
		||||
    pub warmup_cooldown_rate: f64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<Delegation> for UiDelegation {
 | 
			
		||||
    fn from(delegation: Delegation) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            voter: delegation.voter_pubkey.to_string(),
 | 
			
		||||
            stake: delegation.stake.to_string(),
 | 
			
		||||
            activation_epoch: delegation.activation_epoch.to_string(),
 | 
			
		||||
            deactivation_epoch: delegation.deactivation_epoch.to_string(),
 | 
			
		||||
            warmup_cooldown_rate: delegation.warmup_cooldown_rate,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use bincode::serialize;
 | 
			
		||||
    use solana_sdk::pubkey::Pubkey;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_stake() {
 | 
			
		||||
        let stake_state = StakeState::Uninitialized;
 | 
			
		||||
        let stake_data = serialize(&stake_state).unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_stake(&stake_data).unwrap(),
 | 
			
		||||
            StakeAccountType::Uninitialized
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let pubkey = Pubkey::new_rand();
 | 
			
		||||
        let custodian = Pubkey::new_rand();
 | 
			
		||||
        let authorized = Authorized::auto(&pubkey);
 | 
			
		||||
        let lockup = Lockup {
 | 
			
		||||
            unix_timestamp: 0,
 | 
			
		||||
            epoch: 1,
 | 
			
		||||
            custodian,
 | 
			
		||||
        };
 | 
			
		||||
        let meta = Meta {
 | 
			
		||||
            rent_exempt_reserve: 42,
 | 
			
		||||
            authorized,
 | 
			
		||||
            lockup,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let stake_state = StakeState::Initialized(meta);
 | 
			
		||||
        let stake_data = serialize(&stake_state).unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_stake(&stake_data).unwrap(),
 | 
			
		||||
            StakeAccountType::Initialized(UiStakeAccount {
 | 
			
		||||
                meta: UiMeta {
 | 
			
		||||
                    rent_exempt_reserve: 42.to_string(),
 | 
			
		||||
                    authorized: UiAuthorized {
 | 
			
		||||
                        staker: pubkey.to_string(),
 | 
			
		||||
                        withdrawer: pubkey.to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                    lockup: UiLockup {
 | 
			
		||||
                        unix_timestamp: 0,
 | 
			
		||||
                        epoch: 1,
 | 
			
		||||
                        custodian: custodian.to_string(),
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                stake: None,
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let voter_pubkey = Pubkey::new_rand();
 | 
			
		||||
        let stake = Stake {
 | 
			
		||||
            delegation: Delegation {
 | 
			
		||||
                voter_pubkey,
 | 
			
		||||
                stake: 20,
 | 
			
		||||
                activation_epoch: 2,
 | 
			
		||||
                deactivation_epoch: std::u64::MAX,
 | 
			
		||||
                warmup_cooldown_rate: 0.25,
 | 
			
		||||
            },
 | 
			
		||||
            credits_observed: 10,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let stake_state = StakeState::Stake(meta, stake);
 | 
			
		||||
        let stake_data = serialize(&stake_state).unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_stake(&stake_data).unwrap(),
 | 
			
		||||
            StakeAccountType::Delegated(UiStakeAccount {
 | 
			
		||||
                meta: UiMeta {
 | 
			
		||||
                    rent_exempt_reserve: 42.to_string(),
 | 
			
		||||
                    authorized: UiAuthorized {
 | 
			
		||||
                        staker: pubkey.to_string(),
 | 
			
		||||
                        withdrawer: pubkey.to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                    lockup: UiLockup {
 | 
			
		||||
                        unix_timestamp: 0,
 | 
			
		||||
                        epoch: 1,
 | 
			
		||||
                        custodian: custodian.to_string(),
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                stake: Some(UiStake {
 | 
			
		||||
                    delegation: UiDelegation {
 | 
			
		||||
                        voter: voter_pubkey.to_string(),
 | 
			
		||||
                        stake: 20.to_string(),
 | 
			
		||||
                        activation_epoch: 2.to_string(),
 | 
			
		||||
                        deactivation_epoch: std::u64::MAX.to_string(),
 | 
			
		||||
                        warmup_cooldown_rate: 0.25,
 | 
			
		||||
                    },
 | 
			
		||||
                    credits_observed: 10,
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let stake_state = StakeState::RewardsPool;
 | 
			
		||||
        let stake_data = serialize(&stake_state).unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_stake(&stake_data).unwrap(),
 | 
			
		||||
            StakeAccountType::RewardsPool
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let bad_data = vec![1, 2, 3, 4];
 | 
			
		||||
        assert!(parse_stake(&bad_data).is_err());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										328
									
								
								account-decoder/src/parse_sysvar.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										328
									
								
								account-decoder/src/parse_sysvar.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,328 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    parse_account_data::{ParsableAccount, ParseAccountError},
 | 
			
		||||
    StringAmount, UiFeeCalculator,
 | 
			
		||||
};
 | 
			
		||||
use bincode::deserialize;
 | 
			
		||||
use bv::BitVec;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    clock::{Clock, Epoch, Slot, UnixTimestamp},
 | 
			
		||||
    epoch_schedule::EpochSchedule,
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    rent::Rent,
 | 
			
		||||
    slot_hashes::SlotHashes,
 | 
			
		||||
    slot_history::{self, SlotHistory},
 | 
			
		||||
    stake_history::{StakeHistory, StakeHistoryEntry},
 | 
			
		||||
    sysvar::{self, fees::Fees, recent_blockhashes::RecentBlockhashes, rewards::Rewards},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn parse_sysvar(data: &[u8], pubkey: &Pubkey) -> Result<SysvarAccountType, ParseAccountError> {
 | 
			
		||||
    let parsed_account = {
 | 
			
		||||
        if pubkey == &sysvar::clock::id() {
 | 
			
		||||
            deserialize::<Clock>(data)
 | 
			
		||||
                .ok()
 | 
			
		||||
                .map(|clock| SysvarAccountType::Clock(clock.into()))
 | 
			
		||||
        } else if pubkey == &sysvar::epoch_schedule::id() {
 | 
			
		||||
            deserialize(data).ok().map(SysvarAccountType::EpochSchedule)
 | 
			
		||||
        } else if pubkey == &sysvar::fees::id() {
 | 
			
		||||
            deserialize::<Fees>(data)
 | 
			
		||||
                .ok()
 | 
			
		||||
                .map(|fees| SysvarAccountType::Fees(fees.into()))
 | 
			
		||||
        } else if pubkey == &sysvar::recent_blockhashes::id() {
 | 
			
		||||
            deserialize::<RecentBlockhashes>(data)
 | 
			
		||||
                .ok()
 | 
			
		||||
                .map(|recent_blockhashes| {
 | 
			
		||||
                    let recent_blockhashes = recent_blockhashes
 | 
			
		||||
                        .iter()
 | 
			
		||||
                        .map(|entry| UiRecentBlockhashesEntry {
 | 
			
		||||
                            blockhash: entry.blockhash.to_string(),
 | 
			
		||||
                            fee_calculator: entry.fee_calculator.clone().into(),
 | 
			
		||||
                        })
 | 
			
		||||
                        .collect();
 | 
			
		||||
                    SysvarAccountType::RecentBlockhashes(recent_blockhashes)
 | 
			
		||||
                })
 | 
			
		||||
        } else if pubkey == &sysvar::rent::id() {
 | 
			
		||||
            deserialize::<Rent>(data)
 | 
			
		||||
                .ok()
 | 
			
		||||
                .map(|rent| SysvarAccountType::Rent(rent.into()))
 | 
			
		||||
        } else if pubkey == &sysvar::rewards::id() {
 | 
			
		||||
            deserialize::<Rewards>(data)
 | 
			
		||||
                .ok()
 | 
			
		||||
                .map(|rewards| SysvarAccountType::Rewards(rewards.into()))
 | 
			
		||||
        } else if pubkey == &sysvar::slot_hashes::id() {
 | 
			
		||||
            deserialize::<SlotHashes>(data).ok().map(|slot_hashes| {
 | 
			
		||||
                let slot_hashes = slot_hashes
 | 
			
		||||
                    .iter()
 | 
			
		||||
                    .map(|slot_hash| UiSlotHashEntry {
 | 
			
		||||
                        slot: slot_hash.0,
 | 
			
		||||
                        hash: slot_hash.1.to_string(),
 | 
			
		||||
                    })
 | 
			
		||||
                    .collect();
 | 
			
		||||
                SysvarAccountType::SlotHashes(slot_hashes)
 | 
			
		||||
            })
 | 
			
		||||
        } else if pubkey == &sysvar::slot_history::id() {
 | 
			
		||||
            deserialize::<SlotHistory>(data).ok().map(|slot_history| {
 | 
			
		||||
                SysvarAccountType::SlotHistory(UiSlotHistory {
 | 
			
		||||
                    next_slot: slot_history.next_slot,
 | 
			
		||||
                    bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
        } else if pubkey == &sysvar::stake_history::id() {
 | 
			
		||||
            deserialize::<StakeHistory>(data).ok().map(|stake_history| {
 | 
			
		||||
                let stake_history = stake_history
 | 
			
		||||
                    .iter()
 | 
			
		||||
                    .map(|entry| UiStakeHistoryEntry {
 | 
			
		||||
                        epoch: entry.0,
 | 
			
		||||
                        stake_history: entry.1.clone(),
 | 
			
		||||
                    })
 | 
			
		||||
                    .collect();
 | 
			
		||||
                SysvarAccountType::StakeHistory(stake_history)
 | 
			
		||||
            })
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    parsed_account.ok_or(ParseAccountError::AccountNotParsable(
 | 
			
		||||
        ParsableAccount::Sysvar,
 | 
			
		||||
    ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
 | 
			
		||||
pub enum SysvarAccountType {
 | 
			
		||||
    Clock(UiClock),
 | 
			
		||||
    EpochSchedule(EpochSchedule),
 | 
			
		||||
    Fees(UiFees),
 | 
			
		||||
    RecentBlockhashes(Vec<UiRecentBlockhashesEntry>),
 | 
			
		||||
    Rent(UiRent),
 | 
			
		||||
    Rewards(UiRewards),
 | 
			
		||||
    SlotHashes(Vec<UiSlotHashEntry>),
 | 
			
		||||
    SlotHistory(UiSlotHistory),
 | 
			
		||||
    StakeHistory(Vec<UiStakeHistoryEntry>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiClock {
 | 
			
		||||
    pub slot: Slot,
 | 
			
		||||
    pub epoch: Epoch,
 | 
			
		||||
    pub leader_schedule_epoch: Epoch,
 | 
			
		||||
    pub unix_timestamp: UnixTimestamp,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<Clock> for UiClock {
 | 
			
		||||
    fn from(clock: Clock) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            slot: clock.slot,
 | 
			
		||||
            epoch: clock.epoch,
 | 
			
		||||
            leader_schedule_epoch: clock.leader_schedule_epoch,
 | 
			
		||||
            unix_timestamp: clock.unix_timestamp,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiFees {
 | 
			
		||||
    pub fee_calculator: UiFeeCalculator,
 | 
			
		||||
}
 | 
			
		||||
impl From<Fees> for UiFees {
 | 
			
		||||
    fn from(fees: Fees) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            fee_calculator: fees.fee_calculator.into(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiRent {
 | 
			
		||||
    pub lamports_per_byte_year: StringAmount,
 | 
			
		||||
    pub exemption_threshold: f64,
 | 
			
		||||
    pub burn_percent: u8,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<Rent> for UiRent {
 | 
			
		||||
    fn from(rent: Rent) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            lamports_per_byte_year: rent.lamports_per_byte_year.to_string(),
 | 
			
		||||
            exemption_threshold: rent.exemption_threshold,
 | 
			
		||||
            burn_percent: rent.burn_percent,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiRewards {
 | 
			
		||||
    pub validator_point_value: f64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<Rewards> for UiRewards {
 | 
			
		||||
    fn from(rewards: Rewards) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            validator_point_value: rewards.validator_point_value,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiRecentBlockhashesEntry {
 | 
			
		||||
    pub blockhash: String,
 | 
			
		||||
    pub fee_calculator: UiFeeCalculator,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiSlotHashEntry {
 | 
			
		||||
    pub slot: Slot,
 | 
			
		||||
    pub hash: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiSlotHistory {
 | 
			
		||||
    pub next_slot: Slot,
 | 
			
		||||
    pub bits: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct SlotHistoryBits(BitVec<u64>);
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Debug for SlotHistoryBits {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        for i in 0..slot_history::MAX_ENTRIES {
 | 
			
		||||
            if self.0.get(i) {
 | 
			
		||||
                write!(f, "1")?;
 | 
			
		||||
            } else {
 | 
			
		||||
                write!(f, "0")?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiStakeHistoryEntry {
 | 
			
		||||
    pub epoch: Epoch,
 | 
			
		||||
    pub stake_history: StakeHistoryEntry,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use solana_sdk::{
 | 
			
		||||
        fee_calculator::FeeCalculator,
 | 
			
		||||
        hash::Hash,
 | 
			
		||||
        sysvar::{recent_blockhashes::IterItem, Sysvar},
 | 
			
		||||
    };
 | 
			
		||||
    use std::iter::FromIterator;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_sysvars() {
 | 
			
		||||
        let clock_sysvar = Clock::default().create_account(1);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(),
 | 
			
		||||
            SysvarAccountType::Clock(UiClock::default()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let epoch_schedule = EpochSchedule {
 | 
			
		||||
            slots_per_epoch: 12,
 | 
			
		||||
            leader_schedule_slot_offset: 0,
 | 
			
		||||
            warmup: false,
 | 
			
		||||
            first_normal_epoch: 1,
 | 
			
		||||
            first_normal_slot: 12,
 | 
			
		||||
        };
 | 
			
		||||
        let epoch_schedule_sysvar = epoch_schedule.create_account(1);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(),
 | 
			
		||||
            SysvarAccountType::EpochSchedule(epoch_schedule),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let fees_sysvar = Fees::default().create_account(1);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(),
 | 
			
		||||
            SysvarAccountType::Fees(UiFees::default()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let hash = Hash::new(&[1; 32]);
 | 
			
		||||
        let fee_calculator = FeeCalculator {
 | 
			
		||||
            lamports_per_signature: 10,
 | 
			
		||||
        };
 | 
			
		||||
        let recent_blockhashes =
 | 
			
		||||
            RecentBlockhashes::from_iter(vec![IterItem(0, &hash, &fee_calculator)].into_iter());
 | 
			
		||||
        let recent_blockhashes_sysvar = recent_blockhashes.create_account(1);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_sysvar(
 | 
			
		||||
                &recent_blockhashes_sysvar.data,
 | 
			
		||||
                &sysvar::recent_blockhashes::id()
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap(),
 | 
			
		||||
            SysvarAccountType::RecentBlockhashes(vec![UiRecentBlockhashesEntry {
 | 
			
		||||
                blockhash: hash.to_string(),
 | 
			
		||||
                fee_calculator: fee_calculator.into(),
 | 
			
		||||
            }]),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let rent = Rent {
 | 
			
		||||
            lamports_per_byte_year: 10,
 | 
			
		||||
            exemption_threshold: 2.0,
 | 
			
		||||
            burn_percent: 5,
 | 
			
		||||
        };
 | 
			
		||||
        let rent_sysvar = rent.create_account(1);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(),
 | 
			
		||||
            SysvarAccountType::Rent(rent.into()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let rewards_sysvar = Rewards::default().create_account(1);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_sysvar(&rewards_sysvar.data, &sysvar::rewards::id()).unwrap(),
 | 
			
		||||
            SysvarAccountType::Rewards(UiRewards::default()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let mut slot_hashes = SlotHashes::default();
 | 
			
		||||
        slot_hashes.add(1, hash);
 | 
			
		||||
        let slot_hashes_sysvar = slot_hashes.create_account(1);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_sysvar(&slot_hashes_sysvar.data, &sysvar::slot_hashes::id()).unwrap(),
 | 
			
		||||
            SysvarAccountType::SlotHashes(vec![UiSlotHashEntry {
 | 
			
		||||
                slot: 1,
 | 
			
		||||
                hash: hash.to_string(),
 | 
			
		||||
            }]),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let mut slot_history = SlotHistory::default();
 | 
			
		||||
        slot_history.add(42);
 | 
			
		||||
        let slot_history_sysvar = slot_history.create_account(1);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_sysvar(&slot_history_sysvar.data, &sysvar::slot_history::id()).unwrap(),
 | 
			
		||||
            SysvarAccountType::SlotHistory(UiSlotHistory {
 | 
			
		||||
                next_slot: slot_history.next_slot,
 | 
			
		||||
                bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
 | 
			
		||||
            }),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let mut stake_history = StakeHistory::default();
 | 
			
		||||
        let stake_history_entry = StakeHistoryEntry {
 | 
			
		||||
            effective: 10,
 | 
			
		||||
            activating: 2,
 | 
			
		||||
            deactivating: 3,
 | 
			
		||||
        };
 | 
			
		||||
        stake_history.add(1, stake_history_entry.clone());
 | 
			
		||||
        let stake_history_sysvar = stake_history.create_account(1);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_sysvar(&stake_history_sysvar.data, &sysvar::stake_history::id()).unwrap(),
 | 
			
		||||
            SysvarAccountType::StakeHistory(vec![UiStakeHistoryEntry {
 | 
			
		||||
                epoch: 1,
 | 
			
		||||
                stake_history: stake_history_entry,
 | 
			
		||||
            }]),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let bad_pubkey = Pubkey::new_rand();
 | 
			
		||||
        assert!(parse_sysvar(&stake_history_sysvar.data, &bad_pubkey).is_err());
 | 
			
		||||
 | 
			
		||||
        let bad_data = vec![0; 4];
 | 
			
		||||
        assert!(parse_sysvar(&bad_data, &sysvar::stake_history::id()).is_err());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										299
									
								
								account-decoder/src/parse_token.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								account-decoder/src/parse_token.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,299 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    parse_account_data::{ParsableAccount, ParseAccountError},
 | 
			
		||||
    StringAmount,
 | 
			
		||||
};
 | 
			
		||||
use solana_sdk::pubkey::Pubkey;
 | 
			
		||||
use spl_token_v2_0::{
 | 
			
		||||
    solana_sdk::{program_option::COption, program_pack::Pack, pubkey::Pubkey as SplTokenPubkey},
 | 
			
		||||
    state::{Account, AccountState, Mint, Multisig},
 | 
			
		||||
};
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
// A helper function to convert spl_token_v2_0::id() as spl_sdk::pubkey::Pubkey to
 | 
			
		||||
// solana_sdk::pubkey::Pubkey
 | 
			
		||||
pub fn spl_token_id_v2_0() -> Pubkey {
 | 
			
		||||
    Pubkey::from_str(&spl_token_v2_0::id().to_string()).unwrap()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A helper function to convert spl_token_v2_0::native_mint::id() as spl_sdk::pubkey::Pubkey to
 | 
			
		||||
// solana_sdk::pubkey::Pubkey
 | 
			
		||||
pub fn spl_token_v2_0_native_mint() -> Pubkey {
 | 
			
		||||
    Pubkey::from_str(&spl_token_v2_0::native_mint::id().to_string()).unwrap()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse_token(
 | 
			
		||||
    data: &[u8],
 | 
			
		||||
    mint_decimals: Option<u8>,
 | 
			
		||||
) -> Result<TokenAccountType, ParseAccountError> {
 | 
			
		||||
    if data.len() == Account::get_packed_len() {
 | 
			
		||||
        let account = Account::unpack(data)
 | 
			
		||||
            .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
 | 
			
		||||
        let decimals = mint_decimals.ok_or_else(|| {
 | 
			
		||||
            ParseAccountError::AdditionalDataMissing(
 | 
			
		||||
                "no mint_decimals provided to parse spl-token account".to_string(),
 | 
			
		||||
            )
 | 
			
		||||
        })?;
 | 
			
		||||
        Ok(TokenAccountType::Account(UiTokenAccount {
 | 
			
		||||
            mint: account.mint.to_string(),
 | 
			
		||||
            owner: account.owner.to_string(),
 | 
			
		||||
            token_amount: token_amount_to_ui_amount(account.amount, decimals),
 | 
			
		||||
            delegate: match account.delegate {
 | 
			
		||||
                COption::Some(pubkey) => Some(pubkey.to_string()),
 | 
			
		||||
                COption::None => None,
 | 
			
		||||
            },
 | 
			
		||||
            state: account.state.into(),
 | 
			
		||||
            is_native: account.is_native(),
 | 
			
		||||
            rent_exempt_reserve: match account.is_native {
 | 
			
		||||
                COption::Some(reserve) => Some(token_amount_to_ui_amount(reserve, decimals)),
 | 
			
		||||
                COption::None => None,
 | 
			
		||||
            },
 | 
			
		||||
            delegated_amount: if account.delegate.is_none() {
 | 
			
		||||
                None
 | 
			
		||||
            } else {
 | 
			
		||||
                Some(token_amount_to_ui_amount(
 | 
			
		||||
                    account.delegated_amount,
 | 
			
		||||
                    decimals,
 | 
			
		||||
                ))
 | 
			
		||||
            },
 | 
			
		||||
            close_authority: match account.close_authority {
 | 
			
		||||
                COption::Some(pubkey) => Some(pubkey.to_string()),
 | 
			
		||||
                COption::None => None,
 | 
			
		||||
            },
 | 
			
		||||
        }))
 | 
			
		||||
    } else if data.len() == Mint::get_packed_len() {
 | 
			
		||||
        let mint = Mint::unpack(data)
 | 
			
		||||
            .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
 | 
			
		||||
        Ok(TokenAccountType::Mint(UiMint {
 | 
			
		||||
            mint_authority: match mint.mint_authority {
 | 
			
		||||
                COption::Some(pubkey) => Some(pubkey.to_string()),
 | 
			
		||||
                COption::None => None,
 | 
			
		||||
            },
 | 
			
		||||
            supply: mint.supply.to_string(),
 | 
			
		||||
            decimals: mint.decimals,
 | 
			
		||||
            is_initialized: mint.is_initialized,
 | 
			
		||||
            freeze_authority: match mint.freeze_authority {
 | 
			
		||||
                COption::Some(pubkey) => Some(pubkey.to_string()),
 | 
			
		||||
                COption::None => None,
 | 
			
		||||
            },
 | 
			
		||||
        }))
 | 
			
		||||
    } else if data.len() == Multisig::get_packed_len() {
 | 
			
		||||
        let multisig = Multisig::unpack(data)
 | 
			
		||||
            .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
 | 
			
		||||
        Ok(TokenAccountType::Multisig(UiMultisig {
 | 
			
		||||
            num_required_signers: multisig.m,
 | 
			
		||||
            num_valid_signers: multisig.n,
 | 
			
		||||
            is_initialized: multisig.is_initialized,
 | 
			
		||||
            signers: multisig
 | 
			
		||||
                .signers
 | 
			
		||||
                .iter()
 | 
			
		||||
                .filter_map(|pubkey| {
 | 
			
		||||
                    if pubkey != &SplTokenPubkey::default() {
 | 
			
		||||
                        Some(pubkey.to_string())
 | 
			
		||||
                    } else {
 | 
			
		||||
                        None
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
                .collect(),
 | 
			
		||||
        }))
 | 
			
		||||
    } else {
 | 
			
		||||
        Err(ParseAccountError::AccountNotParsable(
 | 
			
		||||
            ParsableAccount::SplToken,
 | 
			
		||||
        ))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
 | 
			
		||||
pub enum TokenAccountType {
 | 
			
		||||
    Account(UiTokenAccount),
 | 
			
		||||
    Mint(UiMint),
 | 
			
		||||
    Multisig(UiMultisig),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiTokenAccount {
 | 
			
		||||
    pub mint: String,
 | 
			
		||||
    pub owner: String,
 | 
			
		||||
    pub token_amount: UiTokenAmount,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub delegate: Option<String>,
 | 
			
		||||
    pub state: UiAccountState,
 | 
			
		||||
    pub is_native: bool,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub rent_exempt_reserve: Option<UiTokenAmount>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub delegated_amount: Option<UiTokenAmount>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub close_authority: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum UiAccountState {
 | 
			
		||||
    Uninitialized,
 | 
			
		||||
    Initialized,
 | 
			
		||||
    Frozen,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<AccountState> for UiAccountState {
 | 
			
		||||
    fn from(state: AccountState) -> Self {
 | 
			
		||||
        match state {
 | 
			
		||||
            AccountState::Uninitialized => UiAccountState::Uninitialized,
 | 
			
		||||
            AccountState::Initialized => UiAccountState::Initialized,
 | 
			
		||||
            AccountState::Frozen => UiAccountState::Frozen,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiTokenAmount {
 | 
			
		||||
    pub ui_amount: f64,
 | 
			
		||||
    pub decimals: u8,
 | 
			
		||||
    pub amount: StringAmount,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
 | 
			
		||||
    // Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211
 | 
			
		||||
    let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64;
 | 
			
		||||
    UiTokenAmount {
 | 
			
		||||
        ui_amount: amount_decimals,
 | 
			
		||||
        decimals,
 | 
			
		||||
        amount: amount.to_string(),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiMint {
 | 
			
		||||
    pub mint_authority: Option<String>,
 | 
			
		||||
    pub supply: StringAmount,
 | 
			
		||||
    pub decimals: u8,
 | 
			
		||||
    pub is_initialized: bool,
 | 
			
		||||
    pub freeze_authority: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiMultisig {
 | 
			
		||||
    pub num_required_signers: u8,
 | 
			
		||||
    pub num_valid_signers: u8,
 | 
			
		||||
    pub is_initialized: bool,
 | 
			
		||||
    pub signers: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> {
 | 
			
		||||
    if data.len() == Account::get_packed_len() {
 | 
			
		||||
        Some(Pubkey::new(&data[0..32]))
 | 
			
		||||
    } else {
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_token() {
 | 
			
		||||
        let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
 | 
			
		||||
        let owner_pubkey = SplTokenPubkey::new(&[3; 32]);
 | 
			
		||||
        let mut account_data = vec![0; Account::get_packed_len()];
 | 
			
		||||
        let mut account = Account::unpack_unchecked(&account_data).unwrap();
 | 
			
		||||
        account.mint = mint_pubkey;
 | 
			
		||||
        account.owner = owner_pubkey;
 | 
			
		||||
        account.amount = 42;
 | 
			
		||||
        account.state = AccountState::Initialized;
 | 
			
		||||
        account.is_native = COption::None;
 | 
			
		||||
        account.close_authority = COption::Some(owner_pubkey);
 | 
			
		||||
        Account::pack(account, &mut account_data).unwrap();
 | 
			
		||||
 | 
			
		||||
        assert!(parse_token(&account_data, None).is_err());
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_token(&account_data, Some(2)).unwrap(),
 | 
			
		||||
            TokenAccountType::Account(UiTokenAccount {
 | 
			
		||||
                mint: mint_pubkey.to_string(),
 | 
			
		||||
                owner: owner_pubkey.to_string(),
 | 
			
		||||
                token_amount: UiTokenAmount {
 | 
			
		||||
                    ui_amount: 0.42,
 | 
			
		||||
                    decimals: 2,
 | 
			
		||||
                    amount: "42".to_string()
 | 
			
		||||
                },
 | 
			
		||||
                delegate: None,
 | 
			
		||||
                state: UiAccountState::Initialized,
 | 
			
		||||
                is_native: false,
 | 
			
		||||
                rent_exempt_reserve: None,
 | 
			
		||||
                delegated_amount: None,
 | 
			
		||||
                close_authority: Some(owner_pubkey.to_string()),
 | 
			
		||||
            }),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let mut mint_data = vec![0; Mint::get_packed_len()];
 | 
			
		||||
        let mut mint = Mint::unpack_unchecked(&mint_data).unwrap();
 | 
			
		||||
        mint.mint_authority = COption::Some(owner_pubkey);
 | 
			
		||||
        mint.supply = 42;
 | 
			
		||||
        mint.decimals = 3;
 | 
			
		||||
        mint.is_initialized = true;
 | 
			
		||||
        mint.freeze_authority = COption::Some(owner_pubkey);
 | 
			
		||||
        Mint::pack(mint, &mut mint_data).unwrap();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_token(&mint_data, None).unwrap(),
 | 
			
		||||
            TokenAccountType::Mint(UiMint {
 | 
			
		||||
                mint_authority: Some(owner_pubkey.to_string()),
 | 
			
		||||
                supply: 42.to_string(),
 | 
			
		||||
                decimals: 3,
 | 
			
		||||
                is_initialized: true,
 | 
			
		||||
                freeze_authority: Some(owner_pubkey.to_string()),
 | 
			
		||||
            }),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let signer1 = SplTokenPubkey::new(&[1; 32]);
 | 
			
		||||
        let signer2 = SplTokenPubkey::new(&[2; 32]);
 | 
			
		||||
        let signer3 = SplTokenPubkey::new(&[3; 32]);
 | 
			
		||||
        let mut multisig_data = vec![0; Multisig::get_packed_len()];
 | 
			
		||||
        let mut signers = [SplTokenPubkey::default(); 11];
 | 
			
		||||
        signers[0] = signer1;
 | 
			
		||||
        signers[1] = signer2;
 | 
			
		||||
        signers[2] = signer3;
 | 
			
		||||
        let mut multisig = Multisig::unpack_unchecked(&multisig_data).unwrap();
 | 
			
		||||
        multisig.m = 2;
 | 
			
		||||
        multisig.n = 3;
 | 
			
		||||
        multisig.is_initialized = true;
 | 
			
		||||
        multisig.signers = signers;
 | 
			
		||||
        Multisig::pack(multisig, &mut multisig_data).unwrap();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_token(&multisig_data, None).unwrap(),
 | 
			
		||||
            TokenAccountType::Multisig(UiMultisig {
 | 
			
		||||
                num_required_signers: 2,
 | 
			
		||||
                num_valid_signers: 3,
 | 
			
		||||
                is_initialized: true,
 | 
			
		||||
                signers: vec![
 | 
			
		||||
                    signer1.to_string(),
 | 
			
		||||
                    signer2.to_string(),
 | 
			
		||||
                    signer3.to_string()
 | 
			
		||||
                ],
 | 
			
		||||
            }),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let bad_data = vec![0; 4];
 | 
			
		||||
        assert!(parse_token(&bad_data, None).is_err());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_get_token_account_mint() {
 | 
			
		||||
        let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
 | 
			
		||||
        let mut account_data = vec![0; Account::get_packed_len()];
 | 
			
		||||
        let mut account = Account::unpack_unchecked(&account_data).unwrap();
 | 
			
		||||
        account.mint = mint_pubkey;
 | 
			
		||||
        Account::pack(account, &mut account_data).unwrap();
 | 
			
		||||
 | 
			
		||||
        let expected_mint_pubkey = Pubkey::new(&[2; 32]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            get_token_account_mint(&account_data),
 | 
			
		||||
            Some(expected_mint_pubkey)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										144
									
								
								account-decoder/src/parse_vote.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								account-decoder/src/parse_vote.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
use crate::{parse_account_data::ParseAccountError, StringAmount};
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    clock::{Epoch, Slot},
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
};
 | 
			
		||||
use solana_vote_program::vote_state::{BlockTimestamp, Lockout, VoteState};
 | 
			
		||||
 | 
			
		||||
pub fn parse_vote(data: &[u8]) -> Result<VoteAccountType, ParseAccountError> {
 | 
			
		||||
    let mut vote_state = VoteState::deserialize(data).map_err(ParseAccountError::from)?;
 | 
			
		||||
    let epoch_credits = vote_state
 | 
			
		||||
        .epoch_credits()
 | 
			
		||||
        .iter()
 | 
			
		||||
        .map(|(epoch, credits, previous_credits)| UiEpochCredits {
 | 
			
		||||
            epoch: *epoch,
 | 
			
		||||
            credits: credits.to_string(),
 | 
			
		||||
            previous_credits: previous_credits.to_string(),
 | 
			
		||||
        })
 | 
			
		||||
        .collect();
 | 
			
		||||
    let votes = vote_state
 | 
			
		||||
        .votes
 | 
			
		||||
        .iter()
 | 
			
		||||
        .map(|lockout| UiLockout {
 | 
			
		||||
            slot: lockout.slot,
 | 
			
		||||
            confirmation_count: lockout.confirmation_count,
 | 
			
		||||
        })
 | 
			
		||||
        .collect();
 | 
			
		||||
    let authorized_voters = vote_state
 | 
			
		||||
        .authorized_voters()
 | 
			
		||||
        .iter()
 | 
			
		||||
        .map(|(epoch, authorized_voter)| UiAuthorizedVoters {
 | 
			
		||||
            epoch: *epoch,
 | 
			
		||||
            authorized_voter: authorized_voter.to_string(),
 | 
			
		||||
        })
 | 
			
		||||
        .collect();
 | 
			
		||||
    let prior_voters = vote_state
 | 
			
		||||
        .prior_voters()
 | 
			
		||||
        .buf()
 | 
			
		||||
        .iter()
 | 
			
		||||
        .filter(|(pubkey, _, _)| pubkey != &Pubkey::default())
 | 
			
		||||
        .map(
 | 
			
		||||
            |(authorized_pubkey, epoch_of_last_authorized_switch, target_epoch)| UiPriorVoters {
 | 
			
		||||
                authorized_pubkey: authorized_pubkey.to_string(),
 | 
			
		||||
                epoch_of_last_authorized_switch: *epoch_of_last_authorized_switch,
 | 
			
		||||
                target_epoch: *target_epoch,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        .collect();
 | 
			
		||||
    Ok(VoteAccountType::Vote(UiVoteState {
 | 
			
		||||
        node_pubkey: vote_state.node_pubkey.to_string(),
 | 
			
		||||
        authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
 | 
			
		||||
        commission: vote_state.commission,
 | 
			
		||||
        votes,
 | 
			
		||||
        root_slot: vote_state.root_slot,
 | 
			
		||||
        authorized_voters,
 | 
			
		||||
        prior_voters,
 | 
			
		||||
        epoch_credits,
 | 
			
		||||
        last_timestamp: vote_state.last_timestamp,
 | 
			
		||||
    }))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A wrapper enum for consistency across programs
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
 | 
			
		||||
pub enum VoteAccountType {
 | 
			
		||||
    Vote(UiVoteState),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A duplicate representation of VoteState for pretty JSON serialization
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct UiVoteState {
 | 
			
		||||
    node_pubkey: String,
 | 
			
		||||
    authorized_withdrawer: String,
 | 
			
		||||
    commission: u8,
 | 
			
		||||
    votes: Vec<UiLockout>,
 | 
			
		||||
    root_slot: Option<Slot>,
 | 
			
		||||
    authorized_voters: Vec<UiAuthorizedVoters>,
 | 
			
		||||
    prior_voters: Vec<UiPriorVoters>,
 | 
			
		||||
    epoch_credits: Vec<UiEpochCredits>,
 | 
			
		||||
    last_timestamp: BlockTimestamp,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
struct UiLockout {
 | 
			
		||||
    slot: Slot,
 | 
			
		||||
    confirmation_count: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&Lockout> for UiLockout {
 | 
			
		||||
    fn from(lockout: &Lockout) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            slot: lockout.slot,
 | 
			
		||||
            confirmation_count: lockout.confirmation_count,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
struct UiAuthorizedVoters {
 | 
			
		||||
    epoch: Epoch,
 | 
			
		||||
    authorized_voter: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
struct UiPriorVoters {
 | 
			
		||||
    authorized_pubkey: String,
 | 
			
		||||
    epoch_of_last_authorized_switch: Epoch,
 | 
			
		||||
    target_epoch: Epoch,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
struct UiEpochCredits {
 | 
			
		||||
    epoch: Epoch,
 | 
			
		||||
    credits: StringAmount,
 | 
			
		||||
    previous_credits: StringAmount,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use solana_vote_program::vote_state::VoteStateVersions;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_vote() {
 | 
			
		||||
        let vote_state = VoteState::default();
 | 
			
		||||
        let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
 | 
			
		||||
        let versioned = VoteStateVersions::Current(Box::new(vote_state));
 | 
			
		||||
        VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
 | 
			
		||||
        let mut expected_vote_state = UiVoteState::default();
 | 
			
		||||
        expected_vote_state.node_pubkey = Pubkey::default().to_string();
 | 
			
		||||
        expected_vote_state.authorized_withdrawer = Pubkey::default().to_string();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_vote(&vote_account_data).unwrap(),
 | 
			
		||||
            VoteAccountType::Vote(expected_vote_state)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let bad_data = vec![0; 4];
 | 
			
		||||
        assert!(parse_vote(&bad_data).is_err());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								account-decoder/src/validator_info.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								account-decoder/src/validator_info.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
use solana_config_program::ConfigState;
 | 
			
		||||
 | 
			
		||||
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
 | 
			
		||||
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
 | 
			
		||||
pub const MAX_VALIDATOR_INFO: u64 = 576;
 | 
			
		||||
 | 
			
		||||
solana_sdk::declare_id!("Va1idator1nfo111111111111111111111111111111");
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, PartialEq, Serialize, Default)]
 | 
			
		||||
pub struct ValidatorInfo {
 | 
			
		||||
    pub info: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ConfigState for ValidatorInfo {
 | 
			
		||||
    fn max_space() -> u64 {
 | 
			
		||||
        MAX_VALIDATOR_INFO
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,18 +2,18 @@
 | 
			
		||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
 | 
			
		||||
edition = "2018"
 | 
			
		||||
name = "solana-accounts-bench"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
repository = "https://github.com/solana-labs/solana"
 | 
			
		||||
license = "Apache-2.0"
 | 
			
		||||
homepage = "https://solana.com/"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
log = "0.4.6"
 | 
			
		||||
rayon = "1.3.0"
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.0" }
 | 
			
		||||
solana-runtime = { path = "../runtime", version = "1.2.0" }
 | 
			
		||||
solana-measure = { path = "../measure", version = "1.2.0" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.0" }
 | 
			
		||||
rayon = "1.4.0"
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.32" }
 | 
			
		||||
solana-runtime = { path = "../runtime", version = "1.2.32" }
 | 
			
		||||
solana-measure = { path = "../measure", version = "1.2.32" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.32" }
 | 
			
		||||
rand = "0.7.0"
 | 
			
		||||
clap = "2.33.1"
 | 
			
		||||
crossbeam-channel = "0.4"
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ use solana_runtime::{
 | 
			
		||||
    accounts::{create_test_accounts, update_accounts, Accounts},
 | 
			
		||||
    accounts_index::Ancestors,
 | 
			
		||||
};
 | 
			
		||||
use solana_sdk::pubkey::Pubkey;
 | 
			
		||||
use solana_sdk::{genesis_config::OperatingMode, pubkey::Pubkey};
 | 
			
		||||
use std::fs;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
@@ -54,7 +54,7 @@ fn main() {
 | 
			
		||||
    if fs::remove_dir_all(path.clone()).is_err() {
 | 
			
		||||
        println!("Warning: Couldn't remove {:?}", path);
 | 
			
		||||
    }
 | 
			
		||||
    let accounts = Accounts::new(vec![path]);
 | 
			
		||||
    let accounts = Accounts::new(vec![path], OperatingMode::Preview);
 | 
			
		||||
    println!("Creating {} accounts", num_accounts);
 | 
			
		||||
    let mut create_time = Measure::start("create accounts");
 | 
			
		||||
    let pubkeys: Vec<_> = (0..num_slots)
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
 | 
			
		||||
edition = "2018"
 | 
			
		||||
name = "solana-banking-bench"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
repository = "https://github.com/solana-labs/solana"
 | 
			
		||||
license = "Apache-2.0"
 | 
			
		||||
homepage = "https://solana.com/"
 | 
			
		||||
@@ -12,17 +12,17 @@ clap = "2.33.1"
 | 
			
		||||
crossbeam-channel = "0.4"
 | 
			
		||||
log = "0.4.6"
 | 
			
		||||
rand = "0.7.0"
 | 
			
		||||
rayon = "1.3.0"
 | 
			
		||||
solana-core = { path = "../core", version = "1.2.0" }
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.0" }
 | 
			
		||||
solana-streamer = { path = "../streamer", version = "1.2.0" }
 | 
			
		||||
solana-perf = { path = "../perf", version = "1.2.0" }
 | 
			
		||||
solana-ledger = { path = "../ledger", version = "1.2.0" }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.0" }
 | 
			
		||||
solana-runtime = { path = "../runtime", version = "1.2.0" }
 | 
			
		||||
solana-measure = { path = "../measure", version = "1.2.0" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.0" }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.0" }
 | 
			
		||||
rayon = "1.4.0"
 | 
			
		||||
solana-core = { path = "../core", version = "1.2.32" }
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.32" }
 | 
			
		||||
solana-streamer = { path = "../streamer", version = "1.2.32" }
 | 
			
		||||
solana-perf = { path = "../perf", version = "1.2.32" }
 | 
			
		||||
solana-ledger = { path = "../ledger", version = "1.2.32" }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.32" }
 | 
			
		||||
solana-runtime = { path = "../runtime", version = "1.2.32" }
 | 
			
		||||
solana-measure = { path = "../measure", version = "1.2.32" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.32" }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.32" }
 | 
			
		||||
 | 
			
		||||
[package.metadata.docs.rs]
 | 
			
		||||
targets = ["x86_64-unknown-linux-gnu"]
 | 
			
		||||
 
 | 
			
		||||
@@ -169,7 +169,7 @@ fn main() {
 | 
			
		||||
    let (verified_sender, verified_receiver) = unbounded();
 | 
			
		||||
    let (vote_sender, vote_receiver) = unbounded();
 | 
			
		||||
    let bank0 = Bank::new(&genesis_config);
 | 
			
		||||
    let mut bank_forks = BankForks::new(0, bank0);
 | 
			
		||||
    let mut bank_forks = BankForks::new(bank0);
 | 
			
		||||
    let mut bank = bank_forks.working_bank();
 | 
			
		||||
 | 
			
		||||
    info!("threads: {} txs: {}", num_threads, total_num_transactions);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
 | 
			
		||||
edition = "2018"
 | 
			
		||||
name = "solana-bench-exchange"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
repository = "https://github.com/solana-labs/solana"
 | 
			
		||||
license = "Apache-2.0"
 | 
			
		||||
homepage = "https://solana.com/"
 | 
			
		||||
@@ -15,24 +15,24 @@ log = "0.4.8"
 | 
			
		||||
num-derive = "0.3"
 | 
			
		||||
num-traits = "0.2"
 | 
			
		||||
rand = "0.7.0"
 | 
			
		||||
rayon = "1.3.0"
 | 
			
		||||
rayon = "1.4.0"
 | 
			
		||||
serde_json = "1.0.53"
 | 
			
		||||
serde_yaml = "0.8.12"
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.0" }
 | 
			
		||||
solana-core = { path = "../core", version = "1.2.0" }
 | 
			
		||||
solana-genesis = { path = "../genesis", version = "1.2.0" }
 | 
			
		||||
solana-client = { path = "../client", version = "1.2.0" }
 | 
			
		||||
solana-faucet = { path = "../faucet", version = "1.2.0" }
 | 
			
		||||
solana-exchange-program = { path = "../programs/exchange", version = "1.2.0" }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.0" }
 | 
			
		||||
solana-metrics = { path = "../metrics", version = "1.2.0" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.0" }
 | 
			
		||||
solana-runtime = { path = "../runtime", version = "1.2.0" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.0" }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.0" }
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.32" }
 | 
			
		||||
solana-core = { path = "../core", version = "1.2.32" }
 | 
			
		||||
solana-genesis = { path = "../genesis", version = "1.2.32" }
 | 
			
		||||
solana-client = { path = "../client", version = "1.2.32" }
 | 
			
		||||
solana-faucet = { path = "../faucet", version = "1.2.32" }
 | 
			
		||||
solana-exchange-program = { path = "../programs/exchange", version = "1.2.32" }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.32" }
 | 
			
		||||
solana-metrics = { path = "../metrics", version = "1.2.32" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.32" }
 | 
			
		||||
solana-runtime = { path = "../runtime", version = "1.2.32" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.32" }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.32" }
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
solana-local-cluster = { path = "../local-cluster", version = "1.2.0" }
 | 
			
		||||
solana-local-cluster = { path = "../local-cluster", version = "1.2.32" }
 | 
			
		||||
 | 
			
		||||
[package.metadata.docs.rs]
 | 
			
		||||
targets = ["x86_64-unknown-linux-gnu"]
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ use solana_metrics::datapoint_info;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    client::{Client, SyncClient},
 | 
			
		||||
    commitment_config::CommitmentConfig,
 | 
			
		||||
    message::Message,
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    signature::{Keypair, Signer},
 | 
			
		||||
    timing::{duration_as_ms, duration_as_s},
 | 
			
		||||
@@ -449,7 +450,7 @@ fn swapper<T>(
 | 
			
		||||
            }
 | 
			
		||||
            account_group = (account_group + 1) % account_groups as usize;
 | 
			
		||||
 | 
			
		||||
            let (blockhash, _fee_calculator) = client
 | 
			
		||||
            let (blockhash, _fee_calculator, _last_valid_slot) = client
 | 
			
		||||
                .get_recent_blockhash_with_commitment(CommitmentConfig::recent())
 | 
			
		||||
                .expect("Failed to get blockhash");
 | 
			
		||||
            let to_swap_txs: Vec<_> = to_swap
 | 
			
		||||
@@ -457,16 +458,14 @@ fn swapper<T>(
 | 
			
		||||
                .map(|(signer, swap, profit)| {
 | 
			
		||||
                    let s: &Keypair = &signer;
 | 
			
		||||
                    let owner = &signer.pubkey();
 | 
			
		||||
                    Transaction::new_signed_instructions(
 | 
			
		||||
                        &[s],
 | 
			
		||||
                        &[exchange_instruction::swap_request(
 | 
			
		||||
                            owner,
 | 
			
		||||
                            &swap.0.pubkey,
 | 
			
		||||
                            &swap.1.pubkey,
 | 
			
		||||
                            &profit,
 | 
			
		||||
                        )],
 | 
			
		||||
                        blockhash,
 | 
			
		||||
                    )
 | 
			
		||||
                    let instruction = exchange_instruction::swap_request(
 | 
			
		||||
                        owner,
 | 
			
		||||
                        &swap.0.pubkey,
 | 
			
		||||
                        &swap.1.pubkey,
 | 
			
		||||
                        &profit,
 | 
			
		||||
                    );
 | 
			
		||||
                    let message = Message::new(&[instruction], Some(&s.pubkey()));
 | 
			
		||||
                    Transaction::new(&[s], message, blockhash)
 | 
			
		||||
                })
 | 
			
		||||
                .collect();
 | 
			
		||||
 | 
			
		||||
@@ -577,7 +576,7 @@ fn trader<T>(
 | 
			
		||||
        }
 | 
			
		||||
        account_group = (account_group + 1) % account_groups as usize;
 | 
			
		||||
 | 
			
		||||
        let (blockhash, _fee_calculator) = client
 | 
			
		||||
        let (blockhash, _fee_calculator, _last_valid_slot) = client
 | 
			
		||||
            .get_recent_blockhash_with_commitment(CommitmentConfig::recent())
 | 
			
		||||
            .expect("Failed to get blockhash");
 | 
			
		||||
 | 
			
		||||
@@ -588,28 +587,26 @@ fn trader<T>(
 | 
			
		||||
                    let owner_pubkey = &owner.pubkey();
 | 
			
		||||
                    let trade_pubkey = &trade.pubkey();
 | 
			
		||||
                    let space = mem::size_of::<ExchangeState>() as u64;
 | 
			
		||||
                    Transaction::new_signed_instructions(
 | 
			
		||||
                        &[owner.as_ref(), trade],
 | 
			
		||||
                        &[
 | 
			
		||||
                            system_instruction::create_account(
 | 
			
		||||
                                owner_pubkey,
 | 
			
		||||
                                trade_pubkey,
 | 
			
		||||
                                1,
 | 
			
		||||
                                space,
 | 
			
		||||
                                &id(),
 | 
			
		||||
                            ),
 | 
			
		||||
                            exchange_instruction::trade_request(
 | 
			
		||||
                                owner_pubkey,
 | 
			
		||||
                                trade_pubkey,
 | 
			
		||||
                                *side,
 | 
			
		||||
                                pair,
 | 
			
		||||
                                tokens,
 | 
			
		||||
                                price,
 | 
			
		||||
                                src,
 | 
			
		||||
                            ),
 | 
			
		||||
                        ],
 | 
			
		||||
                        blockhash,
 | 
			
		||||
                    )
 | 
			
		||||
                    let instructions = [
 | 
			
		||||
                        system_instruction::create_account(
 | 
			
		||||
                            owner_pubkey,
 | 
			
		||||
                            trade_pubkey,
 | 
			
		||||
                            1,
 | 
			
		||||
                            space,
 | 
			
		||||
                            &id(),
 | 
			
		||||
                        ),
 | 
			
		||||
                        exchange_instruction::trade_request(
 | 
			
		||||
                            owner_pubkey,
 | 
			
		||||
                            trade_pubkey,
 | 
			
		||||
                            *side,
 | 
			
		||||
                            pair,
 | 
			
		||||
                            tokens,
 | 
			
		||||
                            price,
 | 
			
		||||
                            src,
 | 
			
		||||
                        ),
 | 
			
		||||
                    ];
 | 
			
		||||
                    let message = Message::new(&instructions, Some(&owner_pubkey));
 | 
			
		||||
                    Transaction::new(&[owner.as_ref(), trade], message, blockhash)
 | 
			
		||||
                })
 | 
			
		||||
                .collect();
 | 
			
		||||
 | 
			
		||||
@@ -747,13 +744,9 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
 | 
			
		||||
            let mut to_fund_txs: Vec<_> = chunk
 | 
			
		||||
                .par_iter()
 | 
			
		||||
                .map(|(k, m)| {
 | 
			
		||||
                    (
 | 
			
		||||
                        k.clone(),
 | 
			
		||||
                        Transaction::new_unsigned_instructions(&system_instruction::transfer_many(
 | 
			
		||||
                            &k.pubkey(),
 | 
			
		||||
                            &m,
 | 
			
		||||
                        )),
 | 
			
		||||
                    )
 | 
			
		||||
                    let instructions = system_instruction::transfer_many(&k.pubkey(), &m);
 | 
			
		||||
                    let message = Message::new(&instructions, Some(&k.pubkey()));
 | 
			
		||||
                    (k.clone(), Transaction::new_unsigned(message))
 | 
			
		||||
                })
 | 
			
		||||
                .collect();
 | 
			
		||||
 | 
			
		||||
@@ -776,7 +769,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
 | 
			
		||||
                    to_fund_txs.len(),
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                let (blockhash, _fee_calculator) = client
 | 
			
		||||
                let (blockhash, _fee_calculator, _last_valid_slot) = client
 | 
			
		||||
                    .get_recent_blockhash_with_commitment(CommitmentConfig::recent())
 | 
			
		||||
                    .expect("blockhash");
 | 
			
		||||
                to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
 | 
			
		||||
@@ -848,9 +841,10 @@ pub fn create_token_accounts<T: Client>(
 | 
			
		||||
                    );
 | 
			
		||||
                    let request_ix =
 | 
			
		||||
                        exchange_instruction::account_request(owner_pubkey, &new_keypair.pubkey());
 | 
			
		||||
                    let message = Message::new(&[create_ix, request_ix], Some(&owner_pubkey));
 | 
			
		||||
                    (
 | 
			
		||||
                        (from_keypair, new_keypair),
 | 
			
		||||
                        Transaction::new_unsigned_instructions(&[create_ix, request_ix]),
 | 
			
		||||
                        Transaction::new_unsigned(message),
 | 
			
		||||
                    )
 | 
			
		||||
                })
 | 
			
		||||
                .collect();
 | 
			
		||||
@@ -868,7 +862,7 @@ pub fn create_token_accounts<T: Client>(
 | 
			
		||||
 | 
			
		||||
            let mut retries = 0;
 | 
			
		||||
            while !to_create_txs.is_empty() {
 | 
			
		||||
                let (blockhash, _fee_calculator) = client
 | 
			
		||||
                let (blockhash, _fee_calculator, _last_valid_slot) = client
 | 
			
		||||
                    .get_recent_blockhash_with_commitment(CommitmentConfig::recent())
 | 
			
		||||
                    .expect("Failed to get blockhash");
 | 
			
		||||
                to_create_txs
 | 
			
		||||
@@ -997,7 +991,7 @@ pub fn airdrop_lamports<T: Client>(
 | 
			
		||||
 | 
			
		||||
    let mut tries = 0;
 | 
			
		||||
    loop {
 | 
			
		||||
        let (blockhash, _fee_calculator) = client
 | 
			
		||||
        let (blockhash, _fee_calculator, _last_valid_slot) = client
 | 
			
		||||
            .get_recent_blockhash_with_commitment(CommitmentConfig::recent())
 | 
			
		||||
            .expect("Failed to get blockhash");
 | 
			
		||||
        match request_airdrop_transaction(&faucet_addr, &id.pubkey(), amount_to_drop, blockhash) {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,18 +2,18 @@
 | 
			
		||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
 | 
			
		||||
edition = "2018"
 | 
			
		||||
name = "solana-bench-streamer"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
repository = "https://github.com/solana-labs/solana"
 | 
			
		||||
license = "Apache-2.0"
 | 
			
		||||
homepage = "https://solana.com/"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
clap = "2.33.1"
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.0" }
 | 
			
		||||
solana-streamer = { path = "../streamer", version = "1.2.0" }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.0" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.0" }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.0" }
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.32" }
 | 
			
		||||
solana-streamer = { path = "../streamer", version = "1.2.32" }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.32" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.32" }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.32" }
 | 
			
		||||
 | 
			
		||||
[package.metadata.docs.rs]
 | 
			
		||||
targets = ["x86_64-unknown-linux-gnu"]
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
 | 
			
		||||
        let mut num = 0;
 | 
			
		||||
        for p in &msgs.packets {
 | 
			
		||||
            let a = p.meta.addr();
 | 
			
		||||
            assert!(p.meta.size < PACKET_DATA_SIZE);
 | 
			
		||||
            assert!(p.meta.size <= PACKET_DATA_SIZE);
 | 
			
		||||
            send.send_to(&p.data[..p.meta.size], &a).unwrap();
 | 
			
		||||
            num += 1;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,40 +2,35 @@
 | 
			
		||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
 | 
			
		||||
edition = "2018"
 | 
			
		||||
name = "solana-bench-tps"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
repository = "https://github.com/solana-labs/solana"
 | 
			
		||||
license = "Apache-2.0"
 | 
			
		||||
homepage = "https://solana.com/"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
bincode = "1.2.1"
 | 
			
		||||
bincode = "1.3.1"
 | 
			
		||||
clap = "2.33.1"
 | 
			
		||||
log = "0.4.8"
 | 
			
		||||
rayon = "1.3.0"
 | 
			
		||||
rayon = "1.4.0"
 | 
			
		||||
serde_json = "1.0.53"
 | 
			
		||||
serde_yaml = "0.8.12"
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.0" }
 | 
			
		||||
solana-core = { path = "../core", version = "1.2.0" }
 | 
			
		||||
solana-genesis = { path = "../genesis", version = "1.2.0" }
 | 
			
		||||
solana-client = { path = "../client", version = "1.2.0" }
 | 
			
		||||
solana-faucet = { path = "../faucet", version = "1.2.0" }
 | 
			
		||||
solana-librapay = { path = "../programs/librapay", version = "1.2.0", optional = true }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.0" }
 | 
			
		||||
solana-metrics = { path = "../metrics", version = "1.2.0" }
 | 
			
		||||
solana-measure = { path = "../measure", version = "1.2.0" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.0" }
 | 
			
		||||
solana-runtime = { path = "../runtime", version = "1.2.0" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.0" }
 | 
			
		||||
solana-move-loader-program = { path = "../programs/move_loader", version = "1.2.0", optional = true }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.0" }
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.32" }
 | 
			
		||||
solana-core = { path = "../core", version = "1.2.32" }
 | 
			
		||||
solana-genesis = { path = "../genesis", version = "1.2.32" }
 | 
			
		||||
solana-client = { path = "../client", version = "1.2.32" }
 | 
			
		||||
solana-faucet = { path = "../faucet", version = "1.2.32" }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.32" }
 | 
			
		||||
solana-metrics = { path = "../metrics", version = "1.2.32" }
 | 
			
		||||
solana-measure = { path = "../measure", version = "1.2.32" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.32" }
 | 
			
		||||
solana-runtime = { path = "../runtime", version = "1.2.32" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.32" }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.32" }
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
serial_test = "0.4.0"
 | 
			
		||||
serial_test_derive = "0.4.0"
 | 
			
		||||
solana-local-cluster = { path = "../local-cluster", version = "1.2.0" }
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
move = ["solana-librapay", "solana-move-loader-program"]
 | 
			
		||||
solana-local-cluster = { path = "../local-cluster", version = "1.2.32" }
 | 
			
		||||
 | 
			
		||||
[package.metadata.docs.rs]
 | 
			
		||||
targets = ["x86_64-unknown-linux-gnu"]
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ use rayon::prelude::*;
 | 
			
		||||
use solana_client::perf_utils::{sample_txs, SampleStats};
 | 
			
		||||
use solana_core::gen_keys::GenKeys;
 | 
			
		||||
use solana_faucet::faucet::request_airdrop_transaction;
 | 
			
		||||
#[cfg(feature = "move")]
 | 
			
		||||
use solana_librapay::{create_genesis, upload_mint_script, upload_payment_script};
 | 
			
		||||
use solana_measure::measure::Measure;
 | 
			
		||||
use solana_metrics::{self, datapoint_info};
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
@@ -14,6 +12,7 @@ use solana_sdk::{
 | 
			
		||||
    commitment_config::CommitmentConfig,
 | 
			
		||||
    fee_calculator::FeeCalculator,
 | 
			
		||||
    hash::Hash,
 | 
			
		||||
    message::Message,
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    signature::{Keypair, Signer},
 | 
			
		||||
    system_instruction, system_transaction,
 | 
			
		||||
@@ -36,9 +35,6 @@ use std::{
 | 
			
		||||
const MAX_TX_QUEUE_AGE: u64 =
 | 
			
		||||
    MAX_PROCESSING_AGE as u64 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "move")]
 | 
			
		||||
use solana_librapay::librapay_transaction;
 | 
			
		||||
 | 
			
		||||
pub const MAX_SPENDS_PER_TX: u64 = 4;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
@@ -50,12 +46,12 @@ pub type Result<T> = std::result::Result<T, BenchTpsError>;
 | 
			
		||||
 | 
			
		||||
pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<(Transaction, u64)>>>>;
 | 
			
		||||
 | 
			
		||||
type LibraKeys = (Keypair, Pubkey, Pubkey, Vec<Keypair>);
 | 
			
		||||
 | 
			
		||||
fn get_recent_blockhash<T: Client>(client: &T) -> (Hash, FeeCalculator) {
 | 
			
		||||
    loop {
 | 
			
		||||
        match client.get_recent_blockhash_with_commitment(CommitmentConfig::recent()) {
 | 
			
		||||
            Ok((blockhash, fee_calculator)) => return (blockhash, fee_calculator),
 | 
			
		||||
            Ok((blockhash, fee_calculator, _last_valid_slot)) => {
 | 
			
		||||
                return (blockhash, fee_calculator)
 | 
			
		||||
            }
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                info!("Couldn't get recent blockhash: {:?}", err);
 | 
			
		||||
                sleep(Duration::from_secs(1));
 | 
			
		||||
@@ -119,7 +115,6 @@ fn generate_chunked_transfers(
 | 
			
		||||
    threads: usize,
 | 
			
		||||
    duration: Duration,
 | 
			
		||||
    sustained: bool,
 | 
			
		||||
    libra_args: Option<LibraKeys>,
 | 
			
		||||
) {
 | 
			
		||||
    // generate and send transactions for the specified duration
 | 
			
		||||
    let start = Instant::now();
 | 
			
		||||
@@ -134,7 +129,6 @@ fn generate_chunked_transfers(
 | 
			
		||||
            &dest_keypair_chunks[chunk_index],
 | 
			
		||||
            threads,
 | 
			
		||||
            reclaim_lamports_back_to_source_account,
 | 
			
		||||
            &libra_args,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // In sustained mode, overlap the transfers with generation. This has higher average
 | 
			
		||||
@@ -202,12 +196,7 @@ where
 | 
			
		||||
        .collect()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn do_bench_tps<T>(
 | 
			
		||||
    client: Arc<T>,
 | 
			
		||||
    config: Config,
 | 
			
		||||
    gen_keypairs: Vec<Keypair>,
 | 
			
		||||
    libra_args: Option<LibraKeys>,
 | 
			
		||||
) -> u64
 | 
			
		||||
pub fn do_bench_tps<T>(client: Arc<T>, config: Config, gen_keypairs: Vec<Keypair>) -> u64
 | 
			
		||||
where
 | 
			
		||||
    T: 'static + Client + Send + Sync,
 | 
			
		||||
{
 | 
			
		||||
@@ -291,7 +280,6 @@ where
 | 
			
		||||
        threads,
 | 
			
		||||
        duration,
 | 
			
		||||
        sustained,
 | 
			
		||||
        libra_args,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Stop the sampling threads so it will collect the stats
 | 
			
		||||
@@ -337,52 +325,6 @@ fn metrics_submit_lamport_balance(lamport_balance: u64) {
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "move")]
 | 
			
		||||
fn generate_move_txs(
 | 
			
		||||
    source: &[&Keypair],
 | 
			
		||||
    dest: &VecDeque<&Keypair>,
 | 
			
		||||
    reclaim: bool,
 | 
			
		||||
    move_keypairs: &[Keypair],
 | 
			
		||||
    libra_pay_program_id: &Pubkey,
 | 
			
		||||
    libra_mint_id: &Pubkey,
 | 
			
		||||
    blockhash: &Hash,
 | 
			
		||||
) -> Vec<(Transaction, u64)> {
 | 
			
		||||
    let count = move_keypairs.len() / 2;
 | 
			
		||||
    let source_move = &move_keypairs[..count];
 | 
			
		||||
    let dest_move = &move_keypairs[count..];
 | 
			
		||||
    let pairs: Vec<_> = if !reclaim {
 | 
			
		||||
        source_move
 | 
			
		||||
            .iter()
 | 
			
		||||
            .zip(dest_move.iter())
 | 
			
		||||
            .zip(source.iter())
 | 
			
		||||
            .collect()
 | 
			
		||||
    } else {
 | 
			
		||||
        dest_move
 | 
			
		||||
            .iter()
 | 
			
		||||
            .zip(source_move.iter())
 | 
			
		||||
            .zip(dest.iter())
 | 
			
		||||
            .collect()
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    pairs
 | 
			
		||||
        .par_iter()
 | 
			
		||||
        .map(|((from, to), payer)| {
 | 
			
		||||
            (
 | 
			
		||||
                librapay_transaction::transfer(
 | 
			
		||||
                    libra_pay_program_id,
 | 
			
		||||
                    libra_mint_id,
 | 
			
		||||
                    &payer,
 | 
			
		||||
                    &from,
 | 
			
		||||
                    &to.pubkey(),
 | 
			
		||||
                    1,
 | 
			
		||||
                    *blockhash,
 | 
			
		||||
                ),
 | 
			
		||||
                timestamp(),
 | 
			
		||||
            )
 | 
			
		||||
        })
 | 
			
		||||
        .collect()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn generate_system_txs(
 | 
			
		||||
    source: &[&Keypair],
 | 
			
		||||
    dest: &VecDeque<&Keypair>,
 | 
			
		||||
@@ -413,7 +355,6 @@ fn generate_txs(
 | 
			
		||||
    dest: &VecDeque<&Keypair>,
 | 
			
		||||
    threads: usize,
 | 
			
		||||
    reclaim: bool,
 | 
			
		||||
    libra_args: &Option<LibraKeys>,
 | 
			
		||||
) {
 | 
			
		||||
    let blockhash = *blockhash.read().unwrap();
 | 
			
		||||
    let tx_count = source.len();
 | 
			
		||||
@@ -423,33 +364,7 @@ fn generate_txs(
 | 
			
		||||
    );
 | 
			
		||||
    let signing_start = Instant::now();
 | 
			
		||||
 | 
			
		||||
    let transactions = if let Some((
 | 
			
		||||
        _libra_genesis_keypair,
 | 
			
		||||
        _libra_pay_program_id,
 | 
			
		||||
        _libra_mint_program_id,
 | 
			
		||||
        _libra_keys,
 | 
			
		||||
    )) = libra_args
 | 
			
		||||
    {
 | 
			
		||||
        #[cfg(not(feature = "move"))]
 | 
			
		||||
        {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[cfg(feature = "move")]
 | 
			
		||||
        {
 | 
			
		||||
            generate_move_txs(
 | 
			
		||||
                source,
 | 
			
		||||
                dest,
 | 
			
		||||
                reclaim,
 | 
			
		||||
                &_libra_keys,
 | 
			
		||||
                _libra_pay_program_id,
 | 
			
		||||
                &_libra_genesis_keypair.pubkey(),
 | 
			
		||||
                &blockhash,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        generate_system_txs(source, dest, reclaim, &blockhash)
 | 
			
		||||
    };
 | 
			
		||||
    let transactions = generate_system_txs(source, dest, reclaim, &blockhash);
 | 
			
		||||
 | 
			
		||||
    let duration = signing_start.elapsed();
 | 
			
		||||
    let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos());
 | 
			
		||||
@@ -650,10 +565,9 @@ impl<'a> FundingTransactions<'a> for Vec<(&'a Keypair, Transaction)> {
 | 
			
		||||
        let to_fund_txs: Vec<(&Keypair, Transaction)> = to_fund
 | 
			
		||||
            .par_iter()
 | 
			
		||||
            .map(|(k, t)| {
 | 
			
		||||
                let tx = Transaction::new_unsigned_instructions(
 | 
			
		||||
                    &system_instruction::transfer_many(&k.pubkey(), &t),
 | 
			
		||||
                );
 | 
			
		||||
                (*k, tx)
 | 
			
		||||
                let instructions = system_instruction::transfer_many(&k.pubkey(), &t);
 | 
			
		||||
                let message = Message::new(&instructions, Some(&k.pubkey()));
 | 
			
		||||
                (*k, Transaction::new_unsigned(message))
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
        make_txs.stop();
 | 
			
		||||
@@ -952,181 +866,13 @@ pub fn generate_keypairs(seed_keypair: &Keypair, count: u64) -> (Vec<Keypair>, u
 | 
			
		||||
    (rnd.gen_n_keypairs(total_keys), extra)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "move")]
 | 
			
		||||
fn fund_move_keys<T: Client>(
 | 
			
		||||
    client: &T,
 | 
			
		||||
    funding_key: &Keypair,
 | 
			
		||||
    keypairs: &[Keypair],
 | 
			
		||||
    total: u64,
 | 
			
		||||
    libra_pay_program_id: &Pubkey,
 | 
			
		||||
    libra_mint_program_id: &Pubkey,
 | 
			
		||||
    libra_genesis_key: &Keypair,
 | 
			
		||||
) {
 | 
			
		||||
    let (mut blockhash, _fee_calculator) = get_recent_blockhash(client);
 | 
			
		||||
 | 
			
		||||
    info!("creating the libra funding account..");
 | 
			
		||||
    let libra_funding_key = Keypair::new();
 | 
			
		||||
    let tx = librapay_transaction::create_account(funding_key, &libra_funding_key, 1, blockhash);
 | 
			
		||||
    client
 | 
			
		||||
        .send_message(&[funding_key, &libra_funding_key], tx.message)
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    info!("minting to funding keypair");
 | 
			
		||||
    let tx = librapay_transaction::mint_tokens(
 | 
			
		||||
        &libra_mint_program_id,
 | 
			
		||||
        funding_key,
 | 
			
		||||
        libra_genesis_key,
 | 
			
		||||
        &libra_funding_key.pubkey(),
 | 
			
		||||
        total,
 | 
			
		||||
        blockhash,
 | 
			
		||||
    );
 | 
			
		||||
    client
 | 
			
		||||
        .send_message(&[funding_key, libra_genesis_key], tx.message)
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    info!("creating {} move accounts...", keypairs.len());
 | 
			
		||||
    let total_len = keypairs.len();
 | 
			
		||||
    let create_len = 5;
 | 
			
		||||
    let mut funding_time = Measure::start("funding_time");
 | 
			
		||||
    for (i, keys) in keypairs.chunks(create_len).enumerate() {
 | 
			
		||||
        if client
 | 
			
		||||
            .get_balance_with_commitment(&keys[0].pubkey(), CommitmentConfig::recent())
 | 
			
		||||
            .unwrap_or(0)
 | 
			
		||||
            > 0
 | 
			
		||||
        {
 | 
			
		||||
            // already created these accounts.
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let keypairs: Vec<_> = keys.iter().map(|k| k).collect();
 | 
			
		||||
        let tx = librapay_transaction::create_accounts(funding_key, &keypairs, 1, blockhash);
 | 
			
		||||
        let ser_size = bincode::serialized_size(&tx).unwrap();
 | 
			
		||||
        let mut keys = vec![funding_key];
 | 
			
		||||
        keys.extend(&keypairs);
 | 
			
		||||
        client.send_message(&keys, tx.message).unwrap();
 | 
			
		||||
 | 
			
		||||
        if i % 10 == 0 {
 | 
			
		||||
            info!(
 | 
			
		||||
                "created {} accounts of {} (size {})",
 | 
			
		||||
                i,
 | 
			
		||||
                total_len / create_len,
 | 
			
		||||
                ser_size,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const NUM_FUNDING_KEYS: usize = 10;
 | 
			
		||||
    let funding_keys: Vec<_> = (0..NUM_FUNDING_KEYS).map(|_| Keypair::new()).collect();
 | 
			
		||||
    let pubkey_amounts: Vec<_> = funding_keys
 | 
			
		||||
        .iter()
 | 
			
		||||
        .map(|key| (key.pubkey(), total / NUM_FUNDING_KEYS as u64))
 | 
			
		||||
        .collect();
 | 
			
		||||
    let tx = Transaction::new_signed_instructions(
 | 
			
		||||
        &[funding_key],
 | 
			
		||||
        &system_instruction::transfer_many(&funding_key.pubkey(), &pubkey_amounts),
 | 
			
		||||
        blockhash,
 | 
			
		||||
    );
 | 
			
		||||
    client.send_message(&[funding_key], tx.message).unwrap();
 | 
			
		||||
    let mut balance = 0;
 | 
			
		||||
    for _ in 0..20 {
 | 
			
		||||
        if let Ok(balance_) = client
 | 
			
		||||
            .get_balance_with_commitment(&funding_keys[0].pubkey(), CommitmentConfig::recent())
 | 
			
		||||
        {
 | 
			
		||||
            if balance_ > 0 {
 | 
			
		||||
                balance = balance_;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        sleep(Duration::from_millis(100));
 | 
			
		||||
    }
 | 
			
		||||
    assert!(balance > 0);
 | 
			
		||||
    info!(
 | 
			
		||||
        "funded multiple funding accounts with {:?} lanports",
 | 
			
		||||
        balance
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let libra_funding_keys: Vec<_> = (0..NUM_FUNDING_KEYS).map(|_| Keypair::new()).collect();
 | 
			
		||||
    for (i, key) in libra_funding_keys.iter().enumerate() {
 | 
			
		||||
        let tx = librapay_transaction::create_account(&funding_keys[i], &key, 1, blockhash);
 | 
			
		||||
        client
 | 
			
		||||
            .send_message(&[&funding_keys[i], &key], tx.message)
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        let tx = librapay_transaction::transfer(
 | 
			
		||||
            libra_pay_program_id,
 | 
			
		||||
            &libra_genesis_key.pubkey(),
 | 
			
		||||
            &funding_keys[i],
 | 
			
		||||
            &libra_funding_key,
 | 
			
		||||
            &key.pubkey(),
 | 
			
		||||
            total / NUM_FUNDING_KEYS as u64,
 | 
			
		||||
            blockhash,
 | 
			
		||||
        );
 | 
			
		||||
        client
 | 
			
		||||
            .send_message(&[&funding_keys[i], &libra_funding_key], tx.message)
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        info!("funded libra funding key {}", i);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let keypair_count = keypairs.len();
 | 
			
		||||
    let amount = total / (keypair_count as u64);
 | 
			
		||||
    for (i, keys) in keypairs[..keypair_count]
 | 
			
		||||
        .chunks(NUM_FUNDING_KEYS)
 | 
			
		||||
        .enumerate()
 | 
			
		||||
    {
 | 
			
		||||
        for (j, key) in keys.iter().enumerate() {
 | 
			
		||||
            let tx = librapay_transaction::transfer(
 | 
			
		||||
                libra_pay_program_id,
 | 
			
		||||
                &libra_genesis_key.pubkey(),
 | 
			
		||||
                &funding_keys[j],
 | 
			
		||||
                &libra_funding_keys[j],
 | 
			
		||||
                &key.pubkey(),
 | 
			
		||||
                amount,
 | 
			
		||||
                blockhash,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            let _sig = client
 | 
			
		||||
                .async_send_transaction(tx.clone())
 | 
			
		||||
                .expect("create_account in generate_and_fund_keypairs");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (j, key) in keys.iter().enumerate() {
 | 
			
		||||
            let mut times = 0;
 | 
			
		||||
            loop {
 | 
			
		||||
                let balance =
 | 
			
		||||
                    librapay_transaction::get_libra_balance(client, &key.pubkey()).unwrap();
 | 
			
		||||
                if balance >= amount {
 | 
			
		||||
                    break;
 | 
			
		||||
                } else if times > 20 {
 | 
			
		||||
                    info!("timed out.. {} key: {} balance: {}", i, j, balance);
 | 
			
		||||
                    break;
 | 
			
		||||
                } else {
 | 
			
		||||
                    times += 1;
 | 
			
		||||
                    sleep(Duration::from_millis(100));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        info!(
 | 
			
		||||
            "funded group {} of {}",
 | 
			
		||||
            i + 1,
 | 
			
		||||
            keypairs.len() / NUM_FUNDING_KEYS
 | 
			
		||||
        );
 | 
			
		||||
        blockhash = get_recent_blockhash(client).0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    funding_time.stop();
 | 
			
		||||
    info!("done funding keys, took {} ms", funding_time.as_ms());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
 | 
			
		||||
    client: Arc<T>,
 | 
			
		||||
    faucet_addr: Option<SocketAddr>,
 | 
			
		||||
    funding_key: &Keypair,
 | 
			
		||||
    keypair_count: usize,
 | 
			
		||||
    lamports_per_account: u64,
 | 
			
		||||
    use_move: bool,
 | 
			
		||||
) -> Result<(Vec<Keypair>, Option<LibraKeys>)> {
 | 
			
		||||
) -> Result<Vec<Keypair>> {
 | 
			
		||||
    info!("Creating {} keypairs...", keypair_count);
 | 
			
		||||
    let (mut keypairs, extra) = generate_keypairs(funding_key, keypair_count as u64);
 | 
			
		||||
    info!("Get lamports...");
 | 
			
		||||
@@ -1139,12 +885,6 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
 | 
			
		||||
    let last_key = keypairs[keypair_count - 1].pubkey();
 | 
			
		||||
    let last_keypair_balance = client.get_balance(&last_key).unwrap_or(0);
 | 
			
		||||
 | 
			
		||||
    #[cfg(feature = "move")]
 | 
			
		||||
    let mut move_keypairs_ret = None;
 | 
			
		||||
 | 
			
		||||
    #[cfg(not(feature = "move"))]
 | 
			
		||||
    let move_keypairs_ret = None;
 | 
			
		||||
 | 
			
		||||
    // Repeated runs will eat up keypair balances from transaction fees. In order to quickly
 | 
			
		||||
    //   start another bench-tps run without re-funding all of the keypairs, check if the
 | 
			
		||||
    //   keypairs still have at least 80% of the expected funds. That should be enough to
 | 
			
		||||
@@ -1155,10 +895,7 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
 | 
			
		||||
        let max_fee = fee_rate_governor.max_lamports_per_signature;
 | 
			
		||||
        let extra_fees = extra * max_fee;
 | 
			
		||||
        let total_keypairs = keypairs.len() as u64 + 1; // Add one for funding keypair
 | 
			
		||||
        let mut total = lamports_per_account * total_keypairs + extra_fees;
 | 
			
		||||
        if use_move {
 | 
			
		||||
            total *= 3;
 | 
			
		||||
        }
 | 
			
		||||
        let total = lamports_per_account * total_keypairs + extra_fees;
 | 
			
		||||
 | 
			
		||||
        let funding_key_balance = client.get_balance(&funding_key.pubkey()).unwrap_or(0);
 | 
			
		||||
        info!(
 | 
			
		||||
@@ -1170,40 +907,6 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
 | 
			
		||||
            airdrop_lamports(client.as_ref(), &faucet_addr.unwrap(), funding_key, total)?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[cfg(feature = "move")]
 | 
			
		||||
        {
 | 
			
		||||
            if use_move {
 | 
			
		||||
                let libra_genesis_keypair =
 | 
			
		||||
                    create_genesis(&funding_key, client.as_ref(), 10_000_000);
 | 
			
		||||
                let libra_mint_program_id = upload_mint_script(&funding_key, client.as_ref());
 | 
			
		||||
                let libra_pay_program_id = upload_payment_script(&funding_key, client.as_ref());
 | 
			
		||||
 | 
			
		||||
                // Generate another set of keypairs for move accounts.
 | 
			
		||||
                // Still fund the solana ones which will be used for fees.
 | 
			
		||||
                let seed = [0u8; 32];
 | 
			
		||||
                let mut rnd = GenKeys::new(seed);
 | 
			
		||||
                let move_keypairs = rnd.gen_n_keypairs(keypair_count as u64);
 | 
			
		||||
                fund_move_keys(
 | 
			
		||||
                    client.as_ref(),
 | 
			
		||||
                    funding_key,
 | 
			
		||||
                    &move_keypairs,
 | 
			
		||||
                    total / 3,
 | 
			
		||||
                    &libra_pay_program_id,
 | 
			
		||||
                    &libra_mint_program_id,
 | 
			
		||||
                    &libra_genesis_keypair,
 | 
			
		||||
                );
 | 
			
		||||
                move_keypairs_ret = Some((
 | 
			
		||||
                    libra_genesis_keypair,
 | 
			
		||||
                    libra_pay_program_id,
 | 
			
		||||
                    libra_mint_program_id,
 | 
			
		||||
                    move_keypairs,
 | 
			
		||||
                ));
 | 
			
		||||
 | 
			
		||||
                // Give solana keys 1/3 and move keys 1/3 the lamports. Keep 1/3 for fees.
 | 
			
		||||
                total /= 3;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fund_keys(
 | 
			
		||||
            client,
 | 
			
		||||
            funding_key,
 | 
			
		||||
@@ -1217,7 +920,7 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
 | 
			
		||||
    // 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys.
 | 
			
		||||
    keypairs.truncate(keypair_count);
 | 
			
		||||
 | 
			
		||||
    Ok((keypairs, move_keypairs_ret))
 | 
			
		||||
    Ok(keypairs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
@@ -1241,11 +944,11 @@ mod tests {
 | 
			
		||||
        config.duration = Duration::from_secs(5);
 | 
			
		||||
 | 
			
		||||
        let keypair_count = config.tx_count * config.keypair_multiplier;
 | 
			
		||||
        let (keypairs, _move_keypairs) =
 | 
			
		||||
            generate_and_fund_keypairs(client.clone(), None, &config.id, keypair_count, 20, false)
 | 
			
		||||
        let keypairs =
 | 
			
		||||
            generate_and_fund_keypairs(client.clone(), None, &config.id, keypair_count, 20)
 | 
			
		||||
                .unwrap();
 | 
			
		||||
 | 
			
		||||
        do_bench_tps(client, config, keypairs, None);
 | 
			
		||||
        do_bench_tps(client, config, keypairs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
@@ -1256,9 +959,8 @@ mod tests {
 | 
			
		||||
        let keypair_count = 20;
 | 
			
		||||
        let lamports = 20;
 | 
			
		||||
 | 
			
		||||
        let (keypairs, _move_keypairs) =
 | 
			
		||||
            generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports, false)
 | 
			
		||||
                .unwrap();
 | 
			
		||||
        let keypairs =
 | 
			
		||||
            generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports).unwrap();
 | 
			
		||||
 | 
			
		||||
        for kp in &keypairs {
 | 
			
		||||
            assert_eq!(
 | 
			
		||||
@@ -1280,9 +982,8 @@ mod tests {
 | 
			
		||||
        let keypair_count = 20;
 | 
			
		||||
        let lamports = 20;
 | 
			
		||||
 | 
			
		||||
        let (keypairs, _move_keypairs) =
 | 
			
		||||
            generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports, false)
 | 
			
		||||
                .unwrap();
 | 
			
		||||
        let keypairs =
 | 
			
		||||
            generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports).unwrap();
 | 
			
		||||
 | 
			
		||||
        for kp in &keypairs {
 | 
			
		||||
            assert_eq!(client.get_balance(&kp.pubkey()).unwrap(), lamports);
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@ pub struct Config {
 | 
			
		||||
    pub read_from_client_file: bool,
 | 
			
		||||
    pub target_lamports_per_signature: u64,
 | 
			
		||||
    pub multi_client: bool,
 | 
			
		||||
    pub use_move: bool,
 | 
			
		||||
    pub num_lamports_per_account: u64,
 | 
			
		||||
    pub target_slots_per_epoch: u64,
 | 
			
		||||
}
 | 
			
		||||
@@ -46,7 +45,6 @@ impl Default for Config {
 | 
			
		||||
            read_from_client_file: false,
 | 
			
		||||
            target_lamports_per_signature: FeeRateGovernor::default().target_lamports_per_signature,
 | 
			
		||||
            multi_client: true,
 | 
			
		||||
            use_move: false,
 | 
			
		||||
            num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT,
 | 
			
		||||
            target_slots_per_epoch: 0,
 | 
			
		||||
        }
 | 
			
		||||
@@ -109,11 +107,6 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
 | 
			
		||||
                .long("sustained")
 | 
			
		||||
                .help("Use sustained performance mode vs. peak mode. This overlaps the tx generation with transfers."),
 | 
			
		||||
        )
 | 
			
		||||
        .arg(
 | 
			
		||||
            Arg::with_name("use-move")
 | 
			
		||||
                .long("use-move")
 | 
			
		||||
                .help("Use Move language transactions to perform transfers."),
 | 
			
		||||
        )
 | 
			
		||||
        .arg(
 | 
			
		||||
            Arg::with_name("no-multi-client")
 | 
			
		||||
                .long("no-multi-client")
 | 
			
		||||
@@ -263,7 +256,6 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
 | 
			
		||||
        args.target_lamports_per_signature = v.to_string().parse().expect("can't parse lamports");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    args.use_move = matches.is_present("use-move");
 | 
			
		||||
    args.multi_client = !matches.is_present("no-multi-client");
 | 
			
		||||
 | 
			
		||||
    if let Some(v) = matches.value_of("num_lamports_per_account") {
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,6 @@ fn main() {
 | 
			
		||||
        write_to_client_file,
 | 
			
		||||
        read_from_client_file,
 | 
			
		||||
        target_lamports_per_signature,
 | 
			
		||||
        use_move,
 | 
			
		||||
        multi_client,
 | 
			
		||||
        num_lamports_per_account,
 | 
			
		||||
        ..
 | 
			
		||||
@@ -86,7 +85,7 @@ fn main() {
 | 
			
		||||
        Arc::new(get_client(&nodes))
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let (keypairs, move_keypairs) = if *read_from_client_file && !use_move {
 | 
			
		||||
    let keypairs = if *read_from_client_file {
 | 
			
		||||
        let path = Path::new(&client_ids_and_stake_file);
 | 
			
		||||
        let file = File::open(path).unwrap();
 | 
			
		||||
 | 
			
		||||
@@ -115,8 +114,8 @@ fn main() {
 | 
			
		||||
        // Sort keypairs so that do_bench_tps() uses the same subset of accounts for each run.
 | 
			
		||||
        // This prevents the amount of storage needed for bench-tps accounts from creeping up
 | 
			
		||||
        // across multiple runs.
 | 
			
		||||
        keypairs.sort_by(|x, y| x.pubkey().to_string().cmp(&y.pubkey().to_string()));
 | 
			
		||||
        (keypairs, None)
 | 
			
		||||
        keypairs.sort_by_key(|x| x.pubkey().to_string());
 | 
			
		||||
        keypairs
 | 
			
		||||
    } else {
 | 
			
		||||
        generate_and_fund_keypairs(
 | 
			
		||||
            client.clone(),
 | 
			
		||||
@@ -124,7 +123,6 @@ fn main() {
 | 
			
		||||
            &id,
 | 
			
		||||
            keypair_count,
 | 
			
		||||
            *num_lamports_per_account,
 | 
			
		||||
            *use_move,
 | 
			
		||||
        )
 | 
			
		||||
        .unwrap_or_else(|e| {
 | 
			
		||||
            eprintln!("Error could not fund keys: {:?}", e);
 | 
			
		||||
@@ -132,5 +130,5 @@ fn main() {
 | 
			
		||||
        })
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    do_bench_tps(client, cli_config, keypairs, move_keypairs);
 | 
			
		||||
    do_bench_tps(client, cli_config, keypairs);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,17 +6,11 @@ use solana_core::cluster_info::VALIDATOR_PORT_RANGE;
 | 
			
		||||
use solana_core::validator::ValidatorConfig;
 | 
			
		||||
use solana_faucet::faucet::run_local_faucet;
 | 
			
		||||
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
 | 
			
		||||
#[cfg(feature = "move")]
 | 
			
		||||
use solana_sdk::move_loader::solana_move_loader_program;
 | 
			
		||||
use solana_sdk::signature::{Keypair, Signer};
 | 
			
		||||
use std::sync::{mpsc::channel, Arc};
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
fn test_bench_tps_local_cluster(config: Config) {
 | 
			
		||||
    #[cfg(feature = "move")]
 | 
			
		||||
    let native_instruction_processors = vec![solana_move_loader_program()];
 | 
			
		||||
 | 
			
		||||
    #[cfg(not(feature = "move"))]
 | 
			
		||||
    let native_instruction_processors = vec![];
 | 
			
		||||
 | 
			
		||||
    solana_logger::setup();
 | 
			
		||||
@@ -48,17 +42,16 @@ fn test_bench_tps_local_cluster(config: Config) {
 | 
			
		||||
    let lamports_per_account = 100;
 | 
			
		||||
 | 
			
		||||
    let keypair_count = config.tx_count * config.keypair_multiplier;
 | 
			
		||||
    let (keypairs, move_keypairs) = generate_and_fund_keypairs(
 | 
			
		||||
    let keypairs = generate_and_fund_keypairs(
 | 
			
		||||
        client.clone(),
 | 
			
		||||
        Some(faucet_addr),
 | 
			
		||||
        &config.id,
 | 
			
		||||
        keypair_count,
 | 
			
		||||
        lamports_per_account,
 | 
			
		||||
        config.use_move,
 | 
			
		||||
    )
 | 
			
		||||
    .unwrap();
 | 
			
		||||
 | 
			
		||||
    let _total = do_bench_tps(client, config, keypairs, move_keypairs);
 | 
			
		||||
    let _total = do_bench_tps(client, config, keypairs);
 | 
			
		||||
 | 
			
		||||
    #[cfg(not(debug_assertions))]
 | 
			
		||||
    assert!(_total > 100);
 | 
			
		||||
@@ -73,14 +66,3 @@ fn test_bench_tps_local_cluster_solana() {
 | 
			
		||||
 | 
			
		||||
    test_bench_tps_local_cluster(config);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
#[serial]
 | 
			
		||||
fn test_bench_tps_local_cluster_move() {
 | 
			
		||||
    let mut config = Config::default();
 | 
			
		||||
    config.tx_count = 100;
 | 
			
		||||
    config.duration = Duration::from_secs(10);
 | 
			
		||||
    config.use_move = true;
 | 
			
		||||
 | 
			
		||||
    test_bench_tps_local_cluster(config);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										254
									
								
								ci/buildkite-pipeline.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										254
									
								
								ci/buildkite-pipeline.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,254 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
#
 | 
			
		||||
# Builds a buildkite pipeline based on the environment variables
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
set -e
 | 
			
		||||
cd "$(dirname "$0")"/..
 | 
			
		||||
 | 
			
		||||
output_file=${1:-/dev/stderr}
 | 
			
		||||
 | 
			
		||||
if [[ -n $CI_PULL_REQUEST ]]; then
 | 
			
		||||
  IFS=':' read -ra affected_files <<< "$(buildkite-agent meta-data get affected_files)"
 | 
			
		||||
  if [[ ${#affected_files[*]} -eq 0 ]]; then
 | 
			
		||||
    echo "Unable to determine the files affected by this PR"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
else
 | 
			
		||||
  affected_files=()
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
annotate() {
 | 
			
		||||
  if [[ -n $BUILDKITE ]]; then
 | 
			
		||||
    buildkite-agent annotate "$@"
 | 
			
		||||
  fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Checks if a CI pull request affects one or more path patterns.  Each
 | 
			
		||||
# pattern argument is checked in series. If one of them found to be affected,
 | 
			
		||||
# return immediately as such.
 | 
			
		||||
#
 | 
			
		||||
# Bash regular expressions are permitted in the pattern:
 | 
			
		||||
#     affects .rs$    -- any file or directory ending in .rs
 | 
			
		||||
#     affects .rs     -- also matches foo.rs.bar
 | 
			
		||||
#     affects ^snap/  -- anything under the snap/ subdirectory
 | 
			
		||||
#     affects snap/   -- also matches foo/snap/
 | 
			
		||||
# Any pattern starting with the ! character will be negated:
 | 
			
		||||
#     affects !^docs/  -- anything *not* under the docs/ subdirectory
 | 
			
		||||
#
 | 
			
		||||
affects() {
 | 
			
		||||
  if [[ -z $CI_PULL_REQUEST ]]; then
 | 
			
		||||
    # affected_files metadata is not currently available for non-PR builds so assume
 | 
			
		||||
    # the worse (affected)
 | 
			
		||||
    return 0
 | 
			
		||||
  fi
 | 
			
		||||
  # Assume everyting needs to be tested when any Dockerfile changes
 | 
			
		||||
  for pattern in ^ci/docker-rust/Dockerfile ^ci/docker-rust-nightly/Dockerfile "$@"; do
 | 
			
		||||
    if [[ ${pattern:0:1} = "!" ]]; then
 | 
			
		||||
      for file in "${affected_files[@]}"; do
 | 
			
		||||
        if [[ ! $file =~ ${pattern:1} ]]; then
 | 
			
		||||
          return 0 # affected
 | 
			
		||||
        fi
 | 
			
		||||
      done
 | 
			
		||||
    else
 | 
			
		||||
      for file in "${affected_files[@]}"; do
 | 
			
		||||
        if [[ $file =~ $pattern ]]; then
 | 
			
		||||
          return 0 # affected
 | 
			
		||||
        fi
 | 
			
		||||
      done
 | 
			
		||||
    fi
 | 
			
		||||
  done
 | 
			
		||||
 | 
			
		||||
  return 1 # not affected
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Checks if a CI pull request affects anything other than the provided path patterns
 | 
			
		||||
#
 | 
			
		||||
# Syntax is the same as `affects()` except that the negation prefix is not
 | 
			
		||||
# supported
 | 
			
		||||
#
 | 
			
		||||
affects_other_than() {
 | 
			
		||||
  if [[ -z $CI_PULL_REQUEST ]]; then
 | 
			
		||||
    # affected_files metadata is not currently available for non-PR builds so assume
 | 
			
		||||
    # the worse (affected)
 | 
			
		||||
    return 0
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  for file in "${affected_files[@]}"; do
 | 
			
		||||
    declare matched=false
 | 
			
		||||
    for pattern in "$@"; do
 | 
			
		||||
        if [[ $file =~ $pattern ]]; then
 | 
			
		||||
          matched=true
 | 
			
		||||
        fi
 | 
			
		||||
    done
 | 
			
		||||
    if ! $matched; then
 | 
			
		||||
      return 0 # affected
 | 
			
		||||
    fi
 | 
			
		||||
  done
 | 
			
		||||
 | 
			
		||||
  return 1 # not affected
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
start_pipeline() {
 | 
			
		||||
  echo "# $*" > "$output_file"
 | 
			
		||||
  echo "steps:" >> "$output_file"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
command_step() {
 | 
			
		||||
  cat >> "$output_file" <<EOF
 | 
			
		||||
  - name: "$1"
 | 
			
		||||
    command: "$2"
 | 
			
		||||
    timeout_in_minutes: $3
 | 
			
		||||
    artifact_paths: "log-*.txt"
 | 
			
		||||
EOF
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
trigger_secondary_step() {
 | 
			
		||||
  cat  >> "$output_file" <<"EOF"
 | 
			
		||||
  - trigger: "solana-secondary"
 | 
			
		||||
    branches: "!pull/*"
 | 
			
		||||
    async: true
 | 
			
		||||
    build:
 | 
			
		||||
      message: "${BUILDKITE_MESSAGE}"
 | 
			
		||||
      commit: "${BUILDKITE_COMMIT}"
 | 
			
		||||
      branch: "${BUILDKITE_BRANCH}"
 | 
			
		||||
      env:
 | 
			
		||||
        TRIGGERED_BUILDKITE_TAG: "${BUILDKITE_TAG}"
 | 
			
		||||
EOF
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
wait_step() {
 | 
			
		||||
  echo "  - wait" >> "$output_file"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
all_test_steps() {
 | 
			
		||||
  command_step checks ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_nightly_docker_image ci/test-checks.sh" 20
 | 
			
		||||
  wait_step
 | 
			
		||||
 | 
			
		||||
  # Coverage...
 | 
			
		||||
  if affects \
 | 
			
		||||
             .rs$ \
 | 
			
		||||
             Cargo.lock$ \
 | 
			
		||||
             Cargo.toml$ \
 | 
			
		||||
             ^ci/rust-version.sh \
 | 
			
		||||
             ^ci/test-coverage.sh \
 | 
			
		||||
             ^scripts/coverage.sh \
 | 
			
		||||
      ; then
 | 
			
		||||
    command_step coverage ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_nightly_docker_image ci/test-coverage.sh" 30
 | 
			
		||||
    wait_step
 | 
			
		||||
  else
 | 
			
		||||
    annotate --style info --context test-coverage \
 | 
			
		||||
      "Coverage skipped as no .rs files were modified"
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  # Full test suite
 | 
			
		||||
  command_step stable ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-stable.sh" 60
 | 
			
		||||
  wait_step
 | 
			
		||||
 | 
			
		||||
  # Perf test suite
 | 
			
		||||
  if affects \
 | 
			
		||||
             .rs$ \
 | 
			
		||||
             Cargo.lock$ \
 | 
			
		||||
             Cargo.toml$ \
 | 
			
		||||
             ^ci/rust-version.sh \
 | 
			
		||||
             ^ci/test-stable-perf.sh \
 | 
			
		||||
             ^ci/test-stable.sh \
 | 
			
		||||
             ^ci/test-local-cluster.sh \
 | 
			
		||||
             ^core/build.rs \
 | 
			
		||||
             ^fetch-perf-libs.sh \
 | 
			
		||||
             ^programs/ \
 | 
			
		||||
             ^sdk/ \
 | 
			
		||||
      ; then
 | 
			
		||||
    cat >> "$output_file" <<"EOF"
 | 
			
		||||
  - command: "ci/test-stable-perf.sh"
 | 
			
		||||
    name: "stable-perf"
 | 
			
		||||
    timeout_in_minutes: 40
 | 
			
		||||
    artifact_paths: "log-*.txt"
 | 
			
		||||
    agents:
 | 
			
		||||
      - "queue=cuda"
 | 
			
		||||
EOF
 | 
			
		||||
  else
 | 
			
		||||
    annotate --style info \
 | 
			
		||||
      "Stable-perf skipped as no relevant files were modified"
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  # Benches...
 | 
			
		||||
  if affects \
 | 
			
		||||
             .rs$ \
 | 
			
		||||
             Cargo.lock$ \
 | 
			
		||||
             Cargo.toml$ \
 | 
			
		||||
             ^ci/rust-version.sh \
 | 
			
		||||
             ^ci/test-coverage.sh \
 | 
			
		||||
             ^ci/test-bench.sh \
 | 
			
		||||
      ; then
 | 
			
		||||
    command_step bench "ci/test-bench.sh" 30
 | 
			
		||||
  else
 | 
			
		||||
    annotate --style info --context test-bench \
 | 
			
		||||
      "Bench skipped as no .rs files were modified"
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  command_step "local-cluster" \
 | 
			
		||||
    ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-local-cluster.sh" \
 | 
			
		||||
    45
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pull_or_push_steps() {
 | 
			
		||||
  command_step sanity "ci/test-sanity.sh" 5
 | 
			
		||||
  wait_step
 | 
			
		||||
 | 
			
		||||
  # Check for any .sh file changes
 | 
			
		||||
  if affects .sh$; then
 | 
			
		||||
    command_step shellcheck "ci/shellcheck.sh" 5
 | 
			
		||||
    wait_step
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  # Run the full test suite by default, skipping only if modifications are local
 | 
			
		||||
  # to some particular areas of the tree
 | 
			
		||||
  if affects_other_than ^.buildkite ^.travis .md$ ^docs/ ^web3.js/ ^explorer/ ^.gitbook; then
 | 
			
		||||
    all_test_steps
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  # web3.js, explorer and docs changes run on Travis...
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if [[ -n $BUILDKITE_TAG ]]; then
 | 
			
		||||
  start_pipeline "Tag pipeline for $BUILDKITE_TAG"
 | 
			
		||||
 | 
			
		||||
  annotate --style info --context release-tag \
 | 
			
		||||
    "https://github.com/solana-labs/solana/releases/$BUILDKITE_TAG"
 | 
			
		||||
 | 
			
		||||
  # Jump directly to the secondary build to publish release artifacts quickly
 | 
			
		||||
  trigger_secondary_step
 | 
			
		||||
  exit 0
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
 | 
			
		||||
  echo "+++ Affected files in this PR"
 | 
			
		||||
  for file in "${affected_files[@]}"; do
 | 
			
		||||
    echo "- $file"
 | 
			
		||||
  done
 | 
			
		||||
 | 
			
		||||
  start_pipeline "Pull request pipeline for $BUILDKITE_BRANCH"
 | 
			
		||||
 | 
			
		||||
  # Add helpful link back to the corresponding Github Pull Request
 | 
			
		||||
  annotate --style info --context pr-backlink \
 | 
			
		||||
    "Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH"
 | 
			
		||||
 | 
			
		||||
  if [[ $GITHUB_USER = "dependabot-preview[bot]" ]]; then
 | 
			
		||||
    command_step dependabot "ci/dependabot-pr.sh" 5
 | 
			
		||||
    wait_step
 | 
			
		||||
  fi
 | 
			
		||||
  pull_or_push_steps
 | 
			
		||||
  exit 0
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
start_pipeline "Push pipeline for ${BUILDKITE_BRANCH:-?unknown branch?}"
 | 
			
		||||
pull_or_push_steps
 | 
			
		||||
wait_step
 | 
			
		||||
trigger_secondary_step
 | 
			
		||||
exit 0
 | 
			
		||||
@@ -5,9 +5,6 @@ steps:
 | 
			
		||||
  - command: "ci/publish-tarball.sh"
 | 
			
		||||
    timeout_in_minutes: 60
 | 
			
		||||
    name: "publish tarball"
 | 
			
		||||
  - command: "ci/publish-docs.sh"
 | 
			
		||||
    timeout_in_minutes: 15
 | 
			
		||||
    name: "publish docs"
 | 
			
		||||
  - command: "ci/publish-bpf-sdk.sh"
 | 
			
		||||
    timeout_in_minutes: 5
 | 
			
		||||
    name: "publish bpf sdk"
 | 
			
		||||
@@ -19,6 +16,3 @@ steps:
 | 
			
		||||
    timeout_in_minutes: 240
 | 
			
		||||
    name: "publish crate"
 | 
			
		||||
    branches: "!master"
 | 
			
		||||
    #  - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh"
 | 
			
		||||
    #    name: "move"
 | 
			
		||||
    #    timeout_in_minutes: 20
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,9 @@
 | 
			
		||||
# Release tags use buildkite-release.yml instead
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
  - command: "ci/test-sanity.sh"
 | 
			
		||||
    name: "sanity"
 | 
			
		||||
    timeout_in_minutes: 5
 | 
			
		||||
  - command: "ci/dependabot-pr.sh"
 | 
			
		||||
    name: "dependabot"
 | 
			
		||||
    timeout_in_minutes: 5
 | 
			
		||||
 
 | 
			
		||||
@@ -89,11 +89,17 @@ BETA_CHANNEL_LATEST_TAG=${beta_tag:+v$beta_tag}
 | 
			
		||||
STABLE_CHANNEL_LATEST_TAG=${stable_tag:+v$stable_tag}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if [[ $CI_BRANCH = "$STABLE_CHANNEL" ]]; then
 | 
			
		||||
if [[ -n $CI_BASE_BRANCH ]]; then
 | 
			
		||||
  BRANCH="$CI_BASE_BRANCH"
 | 
			
		||||
elif [[ -n $CI_BRANCH ]]; then
 | 
			
		||||
  BRANCH="$CI_BRANCH"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [[ $BRANCH = "$STABLE_CHANNEL" ]]; then
 | 
			
		||||
  CHANNEL=stable
 | 
			
		||||
elif [[ $CI_BRANCH = "$EDGE_CHANNEL" ]]; then
 | 
			
		||||
elif [[ $BRANCH = "$EDGE_CHANNEL" ]]; then
 | 
			
		||||
  CHANNEL=edge
 | 
			
		||||
elif [[ $CI_BRANCH = "$BETA_CHANNEL" ]]; then
 | 
			
		||||
elif [[ $BRANCH = "$BETA_CHANNEL" ]]; then
 | 
			
		||||
  CHANNEL=beta
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,7 @@ ARGS+=(
 | 
			
		||||
  --env BUILDKITE_JOB_ID
 | 
			
		||||
  --env CI
 | 
			
		||||
  --env CI_BRANCH
 | 
			
		||||
  --env CI_BASE_BRANCH
 | 
			
		||||
  --env CI_TAG
 | 
			
		||||
  --env CI_BUILD_ID
 | 
			
		||||
  --env CI_COMMIT
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,11 @@ if [[ -n $CI ]]; then
 | 
			
		||||
  export CI=1
 | 
			
		||||
  if [[ -n $TRAVIS ]]; then
 | 
			
		||||
    export CI_BRANCH=$TRAVIS_BRANCH
 | 
			
		||||
    export CI_BASE_BRANCH=$TRAVIS_BRANCH
 | 
			
		||||
    export CI_BUILD_ID=$TRAVIS_BUILD_ID
 | 
			
		||||
    export CI_COMMIT=$TRAVIS_COMMIT
 | 
			
		||||
    export CI_JOB_ID=$TRAVIS_JOB_ID
 | 
			
		||||
    if $TRAVIS_PULL_REQUEST; then
 | 
			
		||||
    if [[ $TRAVIS_PULL_REQUEST != false ]]; then
 | 
			
		||||
      export CI_PULL_REQUEST=true
 | 
			
		||||
    else
 | 
			
		||||
      export CI_PULL_REQUEST=
 | 
			
		||||
@@ -28,8 +29,10 @@ if [[ -n $CI ]]; then
 | 
			
		||||
    # to how solana-ci-gate is used to trigger PR builds rather than using the
 | 
			
		||||
    # standard Buildkite PR trigger.
 | 
			
		||||
    if [[ $CI_BRANCH =~ pull/* ]]; then
 | 
			
		||||
      export CI_BASE_BRANCH=$BUILDKITE_PULL_REQUEST_BASE_BRANCH
 | 
			
		||||
      export CI_PULL_REQUEST=true
 | 
			
		||||
    else
 | 
			
		||||
      export CI_BASE_BRANCH=$BUILDKITE_BRANCH
 | 
			
		||||
      export CI_PULL_REQUEST=
 | 
			
		||||
    fi
 | 
			
		||||
    export CI_OS_NAME=linux
 | 
			
		||||
 
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/.."
 | 
			
		||||
 | 
			
		||||
echo --- build docs
 | 
			
		||||
(
 | 
			
		||||
  set -x
 | 
			
		||||
  . ci/rust-version.sh stable
 | 
			
		||||
  ci/docker-run.sh "$rust_stable_docker_image" docs/build.sh
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
echo --- update gitbook-cage
 | 
			
		||||
if [[ -n $CI_BRANCH ]]; then
 | 
			
		||||
  (
 | 
			
		||||
    # make a local commit for the svgs and generated/updated markdown
 | 
			
		||||
    set -x
 | 
			
		||||
    git add -f docs/src
 | 
			
		||||
    if ! git diff-index --quiet HEAD; then
 | 
			
		||||
      git config user.email maintainers@solana.com
 | 
			
		||||
      git config user.name "$(basename "$0")"
 | 
			
		||||
      git commit -m "gitbook-cage update $(date -Is)"
 | 
			
		||||
      git push -f git@github.com:solana-labs/solana-gitbook-cage.git HEAD:refs/heads/"$CI_BRANCH"
 | 
			
		||||
      # pop off the local commit
 | 
			
		||||
      git reset --hard HEAD~
 | 
			
		||||
    fi
 | 
			
		||||
  )
 | 
			
		||||
else
 | 
			
		||||
  echo CI_BRANCH not set
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
exit 0
 | 
			
		||||
@@ -45,7 +45,7 @@ linux)
 | 
			
		||||
  TARGET=x86_64-unknown-linux-gnu
 | 
			
		||||
  ;;
 | 
			
		||||
windows)
 | 
			
		||||
  TARGET=x86_64-pc-windows-gnu
 | 
			
		||||
  TARGET=x86_64-pc-windows-msvc
 | 
			
		||||
  ;;
 | 
			
		||||
*)
 | 
			
		||||
  echo CI_OS_NAME unset
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ source multinode-demo/common.sh
 | 
			
		||||
 | 
			
		||||
rm -rf config/run/init-completed config/ledger config/snapshot-ledger
 | 
			
		||||
 | 
			
		||||
timeout 15 ./run.sh &
 | 
			
		||||
timeout 120 ./run.sh &
 | 
			
		||||
pid=$!
 | 
			
		||||
 | 
			
		||||
attempts=20
 | 
			
		||||
@@ -19,10 +19,16 @@ while [[ ! -f config/run/init-completed ]]; do
 | 
			
		||||
  fi
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
snapshot_slot=1
 | 
			
		||||
 | 
			
		||||
# wait a bit longer than snapshot_slot
 | 
			
		||||
while [[ $($solana_cli --url http://localhost:8899 slot --commitment recent) -le $((snapshot_slot + 1)) ]]; do
 | 
			
		||||
  sleep 1
 | 
			
		||||
done
 | 
			
		||||
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' http://localhost:8899
 | 
			
		||||
 | 
			
		||||
wait $pid
 | 
			
		||||
 | 
			
		||||
$solana_ledger_tool create-snapshot --ledger config/ledger 1 config/snapshot-ledger
 | 
			
		||||
$solana_ledger_tool create-snapshot --ledger config/ledger "$snapshot_slot" config/snapshot-ledger
 | 
			
		||||
cp config/ledger/genesis.tar.bz2 config/snapshot-ledger
 | 
			
		||||
$solana_ledger_tool verify --ledger config/snapshot-ledger
 | 
			
		||||
 
 | 
			
		||||
@@ -27,5 +27,5 @@ Alternatively, you can source it from within a script:
 | 
			
		||||
    local PATCH=0  
 | 
			
		||||
    local SPECIAL=""
 | 
			
		||||
    
 | 
			
		||||
    semverParseInto "1.2.3" MAJOR MINOR PATCH SPECIAL  
 | 
			
		||||
    semverParseInto "1.2.32" MAJOR MINOR PATCH SPECIAL
 | 
			
		||||
    semverParseInto "3.2.1" MAJOR MINOR PATCH SPECIAL
 | 
			
		||||
 
 | 
			
		||||
@@ -76,7 +76,7 @@ RestartForceExitStatus=SIGPIPE
 | 
			
		||||
TimeoutStartSec=10
 | 
			
		||||
TimeoutStopSec=0
 | 
			
		||||
KillMode=process
 | 
			
		||||
LimitNOFILE=65536
 | 
			
		||||
LimitNOFILE=500000
 | 
			
		||||
 | 
			
		||||
[Install]
 | 
			
		||||
WantedBy=multi-user.target
 | 
			
		||||
 
 | 
			
		||||
@@ -8,5 +8,5 @@ source "$HERE"/utils.sh
 | 
			
		||||
ensure_env || exit 1
 | 
			
		||||
 | 
			
		||||
# Allow more files to be opened by a user
 | 
			
		||||
sed -i 's/^\(# End of file\)/* soft nofile 65535\n\n\1/' /etc/security/limits.conf
 | 
			
		||||
echo "* - nofile 500000" > /etc/security/limits.d/90-solana-nofiles.conf
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,20 +6,24 @@ cd "$(dirname "$0")/.."
 | 
			
		||||
source ci/_
 | 
			
		||||
source ci/rust-version.sh stable
 | 
			
		||||
source ci/rust-version.sh nightly
 | 
			
		||||
eval "$(ci/channel-info.sh)"
 | 
			
		||||
 | 
			
		||||
export RUST_BACKTRACE=1
 | 
			
		||||
export RUSTFLAGS="-D warnings"
 | 
			
		||||
 | 
			
		||||
# Look for failed mergify.io backports
 | 
			
		||||
_ git show HEAD --check --oneline
 | 
			
		||||
 | 
			
		||||
if _ scripts/cargo-for-all-lock-files.sh +"$rust_nightly" check --locked --all-targets; then
 | 
			
		||||
  true
 | 
			
		||||
# Only force up-to-date lock files on edge
 | 
			
		||||
if [[ $CI_BASE_BRANCH = "$EDGE_CHANNEL" ]]; then
 | 
			
		||||
  if _ scripts/cargo-for-all-lock-files.sh +"$rust_nightly" check --locked --all-targets; then
 | 
			
		||||
    true
 | 
			
		||||
  else
 | 
			
		||||
    check_status=$?
 | 
			
		||||
    echo "Some Cargo.lock is outdated; please update them as well"
 | 
			
		||||
    echo "protip: you can use ./scripts/cargo-for-all-lock-files.sh update ..."
 | 
			
		||||
    exit "$check_status"
 | 
			
		||||
  fi
 | 
			
		||||
else
 | 
			
		||||
  check_status=$?
 | 
			
		||||
  echo "Some Cargo.lock is outdated; please update them as well"
 | 
			
		||||
  echo "protip: you can use ./scripts/cargo-for-all-lock-files.sh update ..."
 | 
			
		||||
  exit "$check_status"
 | 
			
		||||
  echo "Note: cargo-for-all-lock-files.sh skipped because $CI_BASE_BRANCH != $EDGE_CHANNEL"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
_ cargo +"$rust_stable" fmt --all -- --check
 | 
			
		||||
@@ -27,16 +31,13 @@ _ cargo +"$rust_stable" fmt --all -- --check
 | 
			
		||||
_ cargo +"$rust_stable" clippy --version
 | 
			
		||||
_ cargo +"$rust_stable" clippy --workspace -- --deny=warnings
 | 
			
		||||
 | 
			
		||||
_ cargo +"$rust_stable" audit --version
 | 
			
		||||
_ scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit --ignore RUSTSEC-2020-0002 --ignore RUSTSEC-2020-0008
 | 
			
		||||
_ ci/nits.sh
 | 
			
		||||
#_ cargo +"$rust_stable" audit --version
 | 
			
		||||
#_ scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit --ignore RUSTSEC-2020-0002 --ignore RUSTSEC-2020-0008
 | 
			
		||||
_ ci/order-crates-for-publishing.py
 | 
			
		||||
_ docs/build.sh
 | 
			
		||||
_ ci/check-ssh-keys.sh
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
  cd programs/bpf
 | 
			
		||||
  _ cargo +"$rust_stable" audit
 | 
			
		||||
  #_ cargo +"$rust_stable" audit
 | 
			
		||||
  for project in rust/*/ ; do
 | 
			
		||||
    echo "+++ do_bpf_checks $project"
 | 
			
		||||
    (
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
test-stable.sh
 | 
			
		||||
							
								
								
									
										22
									
								
								ci/test-sanity.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										22
									
								
								ci/test-sanity.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/.."
 | 
			
		||||
 | 
			
		||||
source ci/_
 | 
			
		||||
 | 
			
		||||
(
 | 
			
		||||
  echo --- git diff --check
 | 
			
		||||
  set -x
 | 
			
		||||
  # Look for failed mergify.io backports by searching leftover conflict markers
 | 
			
		||||
  # Also check for any trailing whitespaces!
 | 
			
		||||
  git fetch origin "$CI_BASE_BRANCH"
 | 
			
		||||
  git diff "$(git merge-base HEAD "origin/$CI_BASE_BRANCH")..HEAD" --check --oneline
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
echo
 | 
			
		||||
 | 
			
		||||
_ ci/nits.sh
 | 
			
		||||
_ ci/check-ssh-keys.sh
 | 
			
		||||
 | 
			
		||||
echo --- ok
 | 
			
		||||
@@ -39,15 +39,14 @@ test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
 | 
			
		||||
rm -rf target/xargo # Issue #3105
 | 
			
		||||
 | 
			
		||||
# Limit compiler jobs to reduce memory usage
 | 
			
		||||
# on machines with 1gb/thread of memory
 | 
			
		||||
# on machines with 2gb/thread of memory
 | 
			
		||||
NPROC=$(nproc)
 | 
			
		||||
NPROC=$((NPROC>16 ? 16 : NPROC))
 | 
			
		||||
NPROC=$((NPROC>14 ? 14 : NPROC))
 | 
			
		||||
 | 
			
		||||
echo "Executing $testName"
 | 
			
		||||
case $testName in
 | 
			
		||||
test-stable)
 | 
			
		||||
  _ cargo +"$rust_stable" test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
 | 
			
		||||
  _ cargo +"$rust_stable" test --manifest-path bench-tps/Cargo.toml --features=move ${V:+--verbose} test_bench_tps_local_cluster_move -- --nocapture
 | 
			
		||||
  ;;
 | 
			
		||||
test-stable-perf)
 | 
			
		||||
  ci/affects-files.sh \
 | 
			
		||||
@@ -93,27 +92,6 @@ test-stable-perf)
 | 
			
		||||
  _ cargo +"$rust_stable" build --bins ${V:+--verbose}
 | 
			
		||||
  _ cargo +"$rust_stable" test --package solana-perf --package solana-ledger --package solana-core --lib ${V:+--verbose} -- --nocapture
 | 
			
		||||
  ;;
 | 
			
		||||
test-move)
 | 
			
		||||
  ci/affects-files.sh \
 | 
			
		||||
    Cargo.lock$ \
 | 
			
		||||
    Cargo.toml$ \
 | 
			
		||||
    ^ci/rust-version.sh \
 | 
			
		||||
    ^ci/test-stable.sh \
 | 
			
		||||
    ^ci/test-move.sh \
 | 
			
		||||
    ^programs/move_loader \
 | 
			
		||||
    ^programs/librapay \
 | 
			
		||||
    ^logger/ \
 | 
			
		||||
    ^runtime/ \
 | 
			
		||||
    ^sdk/ \
 | 
			
		||||
  || {
 | 
			
		||||
    annotate --style info \
 | 
			
		||||
      "Skipped $testName as no relevant files were modified"
 | 
			
		||||
    exit 0
 | 
			
		||||
  }
 | 
			
		||||
  _ cargo +"$rust_stable" test --manifest-path programs/move_loader/Cargo.toml ${V:+--verbose} -- --nocapture
 | 
			
		||||
  _ cargo +"$rust_stable" test --manifest-path programs/librapay/Cargo.toml ${V:+--verbose} -- --nocapture
 | 
			
		||||
  exit 0
 | 
			
		||||
  ;;
 | 
			
		||||
test-local-cluster)
 | 
			
		||||
  _ cargo +"$rust_stable" build --release --bins ${V:+--verbose}
 | 
			
		||||
  _ cargo +"$rust_stable" test --release --package solana-local-cluster ${V:+--verbose} -- --nocapture --test-threads=1
 | 
			
		||||
 
 | 
			
		||||
@@ -23,10 +23,14 @@ if [[ -z $CI_TAG ]]; then
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [[ -z $CI_REPO_SLUG ]]; then
 | 
			
		||||
  echo Error: CI_REPO_SLUG not defined
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
# Force CI_REPO_SLUG since sometimes
 | 
			
		||||
# BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG is not set correctly, causing the
 | 
			
		||||
# artifact upload to fail
 | 
			
		||||
CI_REPO_SLUG=solana-labs/solana
 | 
			
		||||
#if [[ -z $CI_REPO_SLUG ]]; then
 | 
			
		||||
#  echo Error: CI_REPO_SLUG not defined
 | 
			
		||||
#  exit 1
 | 
			
		||||
#fi
 | 
			
		||||
 | 
			
		||||
releaseId=$( \
 | 
			
		||||
  curl -s "https://api.github.com/repos/$CI_REPO_SLUG/releases/tags/$CI_TAG" \
 | 
			
		||||
@@ -38,6 +42,7 @@ echo "Github release id for $CI_TAG is $releaseId"
 | 
			
		||||
for file in "$@"; do
 | 
			
		||||
  echo "--- Uploading $file to tag $CI_TAG of $CI_REPO_SLUG"
 | 
			
		||||
  curl \
 | 
			
		||||
    --verbose \
 | 
			
		||||
    --data-binary @"$file" \
 | 
			
		||||
    -H "Authorization: token $GITHUB_TOKEN" \
 | 
			
		||||
    -H "Content-Type: application/octet-stream" \
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "solana-clap-utils"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
description = "Solana utilities for the clap"
 | 
			
		||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
 | 
			
		||||
repository = "https://github.com/solana-labs/solana"
 | 
			
		||||
@@ -11,8 +11,8 @@ edition = "2018"
 | 
			
		||||
[dependencies]
 | 
			
		||||
clap = "2.33.0"
 | 
			
		||||
rpassword = "4.0"
 | 
			
		||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.2.0" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.0" }
 | 
			
		||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.2.32" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.32" }
 | 
			
		||||
thiserror = "1.0.11"
 | 
			
		||||
tiny-bip39 = "0.7.0"
 | 
			
		||||
url = "2.1.0"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								clap-utils/src/fee_payer.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								clap-utils/src/fee_payer.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
use crate::{input_validators, ArgConstant};
 | 
			
		||||
use clap::Arg;
 | 
			
		||||
 | 
			
		||||
pub const FEE_PAYER_ARG: ArgConstant<'static> = ArgConstant {
 | 
			
		||||
    name: "fee_payer",
 | 
			
		||||
    long: "fee-payer",
 | 
			
		||||
    help: "Specify the fee-payer account. This may be a keypair file, the ASK keyword \n\
 | 
			
		||||
           or the pubkey of an offline signer, provided an appropriate --signer argument \n\
 | 
			
		||||
           is also passed. Defaults to the client keypair.",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn fee_payer_arg<'a, 'b>() -> Arg<'a, 'b> {
 | 
			
		||||
    Arg::with_name(FEE_PAYER_ARG.name)
 | 
			
		||||
        .long(FEE_PAYER_ARG.long)
 | 
			
		||||
        .takes_value(true)
 | 
			
		||||
        .value_name("KEYPAIR")
 | 
			
		||||
        .validator(input_validators::is_valid_signer)
 | 
			
		||||
        .help(FEE_PAYER_ARG.help)
 | 
			
		||||
}
 | 
			
		||||
@@ -6,50 +6,86 @@ use solana_sdk::{
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    signature::{read_keypair_file, Signature},
 | 
			
		||||
};
 | 
			
		||||
use std::fmt::Display;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
fn is_parsable_generic<U, T>(string: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
    U: FromStr,
 | 
			
		||||
    U::Err: Display,
 | 
			
		||||
{
 | 
			
		||||
    string
 | 
			
		||||
        .as_ref()
 | 
			
		||||
        .parse::<U>()
 | 
			
		||||
        .map(|_| ())
 | 
			
		||||
        .map_err(|err| format!("error parsing '{}': {}", string, err))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return an error if string cannot be parsed as type T.
 | 
			
		||||
// Takes a String to avoid second type parameter when used as a clap validator
 | 
			
		||||
pub fn is_parsable<T>(string: String) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: FromStr,
 | 
			
		||||
    T::Err: Display,
 | 
			
		||||
{
 | 
			
		||||
    is_parsable_generic::<T, String>(string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return an error if a pubkey cannot be parsed.
 | 
			
		||||
pub fn is_pubkey(string: String) -> Result<(), String> {
 | 
			
		||||
    match string.parse::<Pubkey>() {
 | 
			
		||||
        Ok(_) => Ok(()),
 | 
			
		||||
        Err(err) => Err(format!("{}", err)),
 | 
			
		||||
    }
 | 
			
		||||
pub fn is_pubkey<T>(string: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    is_parsable_generic::<Pubkey, _>(string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return an error if a hash cannot be parsed.
 | 
			
		||||
pub fn is_hash(string: String) -> Result<(), String> {
 | 
			
		||||
    match string.parse::<Hash>() {
 | 
			
		||||
        Ok(_) => Ok(()),
 | 
			
		||||
        Err(err) => Err(format!("{}", err)),
 | 
			
		||||
    }
 | 
			
		||||
pub fn is_hash<T>(string: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    is_parsable_generic::<Hash, _>(string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return an error if a keypair file cannot be parsed.
 | 
			
		||||
pub fn is_keypair(string: String) -> Result<(), String> {
 | 
			
		||||
    read_keypair_file(&string)
 | 
			
		||||
pub fn is_keypair<T>(string: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    read_keypair_file(string.as_ref())
 | 
			
		||||
        .map(|_| ())
 | 
			
		||||
        .map_err(|err| format!("{}", err))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return an error if a keypair file cannot be parsed
 | 
			
		||||
pub fn is_keypair_or_ask_keyword(string: String) -> Result<(), String> {
 | 
			
		||||
    if string.as_str() == ASK_KEYWORD {
 | 
			
		||||
pub fn is_keypair_or_ask_keyword<T>(string: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    if string.as_ref() == ASK_KEYWORD {
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    }
 | 
			
		||||
    read_keypair_file(&string)
 | 
			
		||||
    read_keypair_file(string.as_ref())
 | 
			
		||||
        .map(|_| ())
 | 
			
		||||
        .map_err(|err| format!("{}", err))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return an error if string cannot be parsed as pubkey string or keypair file location
 | 
			
		||||
pub fn is_pubkey_or_keypair(string: String) -> Result<(), String> {
 | 
			
		||||
    is_pubkey(string.clone()).or_else(|_| is_keypair(string))
 | 
			
		||||
pub fn is_pubkey_or_keypair<T>(string: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    is_pubkey(string.as_ref()).or_else(|_| is_keypair(string))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return an error if string cannot be parsed as a pubkey string, or a valid Signer that can
 | 
			
		||||
// produce a pubkey()
 | 
			
		||||
pub fn is_valid_pubkey(string: String) -> Result<(), String> {
 | 
			
		||||
    match parse_keypair_path(&string) {
 | 
			
		||||
pub fn is_valid_pubkey<T>(string: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    match parse_keypair_path(string.as_ref()) {
 | 
			
		||||
        KeypairUrl::Filepath(path) => is_keypair(path),
 | 
			
		||||
        _ => Ok(()),
 | 
			
		||||
    }
 | 
			
		||||
@@ -63,13 +99,19 @@ pub fn is_valid_pubkey(string: String) -> Result<(), String> {
 | 
			
		||||
// when paired with an offline `--signer` argument to provide a Presigner (pubkey + signature).
 | 
			
		||||
// Clap validators can't check multiple fields at once, so the verification that a `--signer` is
 | 
			
		||||
// also provided and correct happens in parsing, not in validation.
 | 
			
		||||
pub fn is_valid_signer(string: String) -> Result<(), String> {
 | 
			
		||||
pub fn is_valid_signer<T>(string: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    is_valid_pubkey(string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return an error if string cannot be parsed as pubkey=signature string
 | 
			
		||||
pub fn is_pubkey_sig(string: String) -> Result<(), String> {
 | 
			
		||||
    let mut signer = string.split('=');
 | 
			
		||||
pub fn is_pubkey_sig<T>(string: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    let mut signer = string.as_ref().split('=');
 | 
			
		||||
    match Pubkey::from_str(
 | 
			
		||||
        signer
 | 
			
		||||
            .next()
 | 
			
		||||
@@ -90,8 +132,11 @@ pub fn is_pubkey_sig(string: String) -> Result<(), String> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return an error if a url cannot be parsed.
 | 
			
		||||
pub fn is_url(string: String) -> Result<(), String> {
 | 
			
		||||
    match url::Url::parse(&string) {
 | 
			
		||||
pub fn is_url<T>(string: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    match url::Url::parse(string.as_ref()) {
 | 
			
		||||
        Ok(url) => {
 | 
			
		||||
            if url.has_host() {
 | 
			
		||||
                Ok(())
 | 
			
		||||
@@ -103,20 +148,26 @@ pub fn is_url(string: String) -> Result<(), String> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn is_slot(slot: String) -> Result<(), String> {
 | 
			
		||||
    slot.parse::<Slot>()
 | 
			
		||||
        .map(|_| ())
 | 
			
		||||
        .map_err(|e| format!("{}", e))
 | 
			
		||||
pub fn is_slot<T>(slot: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    is_parsable_generic::<Slot, _>(slot)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn is_port(port: String) -> Result<(), String> {
 | 
			
		||||
    port.parse::<u16>()
 | 
			
		||||
        .map(|_| ())
 | 
			
		||||
        .map_err(|e| format!("{}", e))
 | 
			
		||||
pub fn is_port<T>(port: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    is_parsable_generic::<u16, _>(port)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn is_valid_percentage(percentage: String) -> Result<(), String> {
 | 
			
		||||
pub fn is_valid_percentage<T>(percentage: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    percentage
 | 
			
		||||
        .as_ref()
 | 
			
		||||
        .parse::<u8>()
 | 
			
		||||
        .map_err(|e| {
 | 
			
		||||
            format!(
 | 
			
		||||
@@ -136,8 +187,11 @@ pub fn is_valid_percentage(percentage: String) -> Result<(), String> {
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn is_amount(amount: String) -> Result<(), String> {
 | 
			
		||||
    if amount.parse::<u64>().is_ok() || amount.parse::<f64>().is_ok() {
 | 
			
		||||
pub fn is_amount<T>(amount: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    if amount.as_ref().parse::<u64>().is_ok() || amount.as_ref().parse::<f64>().is_ok() {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    } else {
 | 
			
		||||
        Err(format!(
 | 
			
		||||
@@ -147,8 +201,14 @@ pub fn is_amount(amount: String) -> Result<(), String> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn is_amount_or_all(amount: String) -> Result<(), String> {
 | 
			
		||||
    if amount.parse::<u64>().is_ok() || amount.parse::<f64>().is_ok() || amount == "ALL" {
 | 
			
		||||
pub fn is_amount_or_all<T>(amount: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    if amount.as_ref().parse::<u64>().is_ok()
 | 
			
		||||
        || amount.as_ref().parse::<f64>().is_ok()
 | 
			
		||||
        || amount.as_ref() == "ALL"
 | 
			
		||||
    {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    } else {
 | 
			
		||||
        Err(format!(
 | 
			
		||||
@@ -158,14 +218,20 @@ pub fn is_amount_or_all(amount: String) -> Result<(), String> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn is_rfc3339_datetime(value: String) -> Result<(), String> {
 | 
			
		||||
    DateTime::parse_from_rfc3339(&value)
 | 
			
		||||
pub fn is_rfc3339_datetime<T>(value: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    DateTime::parse_from_rfc3339(value.as_ref())
 | 
			
		||||
        .map(|_| ())
 | 
			
		||||
        .map_err(|e| format!("{}", e))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn is_derivation(value: String) -> Result<(), String> {
 | 
			
		||||
    let value = value.replace("'", "");
 | 
			
		||||
pub fn is_derivation<T>(value: T) -> Result<(), String>
 | 
			
		||||
where
 | 
			
		||||
    T: AsRef<str> + Display,
 | 
			
		||||
{
 | 
			
		||||
    let value = value.as_ref().replace("'", "");
 | 
			
		||||
    let mut parts = value.split('/');
 | 
			
		||||
    let account = parts.next().unwrap();
 | 
			
		||||
    account
 | 
			
		||||
@@ -197,14 +263,14 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_is_derivation() {
 | 
			
		||||
        assert_eq!(is_derivation("2".to_string()), Ok(()));
 | 
			
		||||
        assert_eq!(is_derivation("0".to_string()), Ok(()));
 | 
			
		||||
        assert_eq!(is_derivation("65537".to_string()), Ok(()));
 | 
			
		||||
        assert_eq!(is_derivation("0/2".to_string()), Ok(()));
 | 
			
		||||
        assert_eq!(is_derivation("0'/2'".to_string()), Ok(()));
 | 
			
		||||
        assert!(is_derivation("a".to_string()).is_err());
 | 
			
		||||
        assert!(is_derivation("4294967296".to_string()).is_err());
 | 
			
		||||
        assert!(is_derivation("a/b".to_string()).is_err());
 | 
			
		||||
        assert!(is_derivation("0/4294967296".to_string()).is_err());
 | 
			
		||||
        assert_eq!(is_derivation("2"), Ok(()));
 | 
			
		||||
        assert_eq!(is_derivation("0"), Ok(()));
 | 
			
		||||
        assert_eq!(is_derivation("65537"), Ok(()));
 | 
			
		||||
        assert_eq!(is_derivation("0/2"), Ok(()));
 | 
			
		||||
        assert_eq!(is_derivation("0'/2'"), Ok(()));
 | 
			
		||||
        assert!(is_derivation("a").is_err());
 | 
			
		||||
        assert!(is_derivation("4294967296").is_err());
 | 
			
		||||
        assert!(is_derivation("a/b").is_err());
 | 
			
		||||
        assert!(is_derivation("0/4294967296").is_err());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ use solana_remote_wallet::{
 | 
			
		||||
    remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
 | 
			
		||||
};
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    hash::Hash,
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    signature::{
 | 
			
		||||
        keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair,
 | 
			
		||||
@@ -25,6 +26,81 @@ use std::{
 | 
			
		||||
    sync::Arc,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub struct SignOnly {
 | 
			
		||||
    pub blockhash: Hash,
 | 
			
		||||
    pub present_signers: Vec<(Pubkey, Signature)>,
 | 
			
		||||
    pub absent_signers: Vec<Pubkey>,
 | 
			
		||||
    pub bad_signers: Vec<Pubkey>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SignOnly {
 | 
			
		||||
    pub fn has_all_signers(&self) -> bool {
 | 
			
		||||
        self.absent_signers.is_empty() && self.bad_signers.is_empty()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn presigner_of(&self, pubkey: &Pubkey) -> Option<Presigner> {
 | 
			
		||||
        presigner_from_pubkey_sigs(pubkey, &self.present_signers)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
pub type CliSigners = Vec<Box<dyn Signer>>;
 | 
			
		||||
pub type SignerIndex = usize;
 | 
			
		||||
pub struct CliSignerInfo {
 | 
			
		||||
    pub signers: CliSigners,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl CliSignerInfo {
 | 
			
		||||
    pub fn index_of(&self, pubkey: Option<Pubkey>) -> Option<usize> {
 | 
			
		||||
        if let Some(pubkey) = pubkey {
 | 
			
		||||
            self.signers
 | 
			
		||||
                .iter()
 | 
			
		||||
                .position(|signer| signer.pubkey() == pubkey)
 | 
			
		||||
        } else {
 | 
			
		||||
            Some(0)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct DefaultSigner {
 | 
			
		||||
    pub arg_name: String,
 | 
			
		||||
    pub path: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DefaultSigner {
 | 
			
		||||
    pub fn generate_unique_signers(
 | 
			
		||||
        &self,
 | 
			
		||||
        bulk_signers: Vec<Option<Box<dyn Signer>>>,
 | 
			
		||||
        matches: &ArgMatches<'_>,
 | 
			
		||||
        wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
    ) -> Result<CliSignerInfo, Box<dyn error::Error>> {
 | 
			
		||||
        let mut unique_signers = vec![];
 | 
			
		||||
 | 
			
		||||
        // Determine if the default signer is needed
 | 
			
		||||
        if bulk_signers.iter().any(|signer| signer.is_none()) {
 | 
			
		||||
            let default_signer = self.signer_from_path(matches, wallet_manager)?;
 | 
			
		||||
            unique_signers.push(default_signer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for signer in bulk_signers.into_iter() {
 | 
			
		||||
            if let Some(signer) = signer {
 | 
			
		||||
                if !unique_signers.iter().any(|s| s == &signer) {
 | 
			
		||||
                    unique_signers.push(signer);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(CliSignerInfo {
 | 
			
		||||
            signers: unique_signers,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn signer_from_path(
 | 
			
		||||
        &self,
 | 
			
		||||
        matches: &ArgMatches,
 | 
			
		||||
        wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
    ) -> Result<Box<dyn Signer>, Box<dyn std::error::Error>> {
 | 
			
		||||
        signer_from_path(matches, &self.path, &self.arg_name, wallet_manager)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum KeypairUrl {
 | 
			
		||||
    Ask,
 | 
			
		||||
    Filepath(String),
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,9 @@ impl std::fmt::Debug for DisplayError {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub mod commitment;
 | 
			
		||||
pub mod fee_payer;
 | 
			
		||||
pub mod input_parsers;
 | 
			
		||||
pub mod input_validators;
 | 
			
		||||
pub mod keypair;
 | 
			
		||||
pub mod nonce;
 | 
			
		||||
pub mod offline;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								clap-utils/src/nonce.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								clap-utils/src/nonce.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
use crate::{input_validators::*, offline::BLOCKHASH_ARG, ArgConstant};
 | 
			
		||||
use clap::{App, Arg};
 | 
			
		||||
 | 
			
		||||
pub const NONCE_ARG: ArgConstant<'static> = ArgConstant {
 | 
			
		||||
    name: "nonce",
 | 
			
		||||
    long: "nonce",
 | 
			
		||||
    help: "Provide the nonce account to use when creating a nonced \n\
 | 
			
		||||
           transaction. Nonced transactions are useful when a transaction \n\
 | 
			
		||||
           requires a lengthy signing process. Learn more about nonced \n\
 | 
			
		||||
           transactions at https://docs.solana.com/offline-signing/durable-nonce",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const NONCE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
 | 
			
		||||
    name: "nonce_authority",
 | 
			
		||||
    long: "nonce-authority",
 | 
			
		||||
    help: "Provide the nonce authority keypair to use when signing a nonced transaction",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
fn nonce_arg<'a, 'b>() -> Arg<'a, 'b> {
 | 
			
		||||
    Arg::with_name(NONCE_ARG.name)
 | 
			
		||||
        .long(NONCE_ARG.long)
 | 
			
		||||
        .takes_value(true)
 | 
			
		||||
        .value_name("PUBKEY")
 | 
			
		||||
        .requires(BLOCKHASH_ARG.name)
 | 
			
		||||
        .validator(is_valid_pubkey)
 | 
			
		||||
        .help(NONCE_ARG.help)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
 | 
			
		||||
    Arg::with_name(NONCE_AUTHORITY_ARG.name)
 | 
			
		||||
        .long(NONCE_AUTHORITY_ARG.long)
 | 
			
		||||
        .takes_value(true)
 | 
			
		||||
        .value_name("KEYPAIR")
 | 
			
		||||
        .validator(is_valid_signer)
 | 
			
		||||
        .help(NONCE_AUTHORITY_ARG.help)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait NonceArgs {
 | 
			
		||||
    fn nonce_args(self) -> Self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NonceArgs for App<'_, '_> {
 | 
			
		||||
    fn nonce_args(self) -> Self {
 | 
			
		||||
        self.arg(nonce_arg())
 | 
			
		||||
            .arg(nonce_authority_arg().requires(NONCE_ARG.name))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
use crate::ArgConstant;
 | 
			
		||||
use crate::{input_validators::*, ArgConstant};
 | 
			
		||||
use clap::{App, Arg};
 | 
			
		||||
 | 
			
		||||
pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant {
 | 
			
		||||
    name: "blockhash",
 | 
			
		||||
@@ -17,3 +18,43 @@ pub const SIGNER_ARG: ArgConstant<'static> = ArgConstant {
 | 
			
		||||
    long: "signer",
 | 
			
		||||
    help: "Provide a public-key/signature pair for the transaction",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn blockhash_arg<'a, 'b>() -> Arg<'a, 'b> {
 | 
			
		||||
    Arg::with_name(BLOCKHASH_ARG.name)
 | 
			
		||||
        .long(BLOCKHASH_ARG.long)
 | 
			
		||||
        .takes_value(true)
 | 
			
		||||
        .value_name("BLOCKHASH")
 | 
			
		||||
        .validator(is_hash)
 | 
			
		||||
        .help(BLOCKHASH_ARG.help)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn sign_only_arg<'a, 'b>() -> Arg<'a, 'b> {
 | 
			
		||||
    Arg::with_name(SIGN_ONLY_ARG.name)
 | 
			
		||||
        .long(SIGN_ONLY_ARG.long)
 | 
			
		||||
        .takes_value(false)
 | 
			
		||||
        .requires(BLOCKHASH_ARG.name)
 | 
			
		||||
        .help(SIGN_ONLY_ARG.help)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn signer_arg<'a, 'b>() -> Arg<'a, 'b> {
 | 
			
		||||
    Arg::with_name(SIGNER_ARG.name)
 | 
			
		||||
        .long(SIGNER_ARG.long)
 | 
			
		||||
        .takes_value(true)
 | 
			
		||||
        .value_name("PUBKEY=SIGNATURE")
 | 
			
		||||
        .validator(is_pubkey_sig)
 | 
			
		||||
        .requires(BLOCKHASH_ARG.name)
 | 
			
		||||
        .multiple(true)
 | 
			
		||||
        .help(SIGNER_ARG.help)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait OfflineArgs {
 | 
			
		||||
    fn offline_args(self) -> Self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl OfflineArgs for App<'_, '_> {
 | 
			
		||||
    fn offline_args(self) -> Self {
 | 
			
		||||
        self.arg(blockhash_arg())
 | 
			
		||||
            .arg(sign_only_arg())
 | 
			
		||||
            .arg(signer_arg())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
 | 
			
		||||
edition = "2018"
 | 
			
		||||
name = "solana-cli-config"
 | 
			
		||||
description = "Blockchain, Rebuilt for Scale"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
repository = "https://github.com/solana-labs/solana"
 | 
			
		||||
license = "Apache-2.0"
 | 
			
		||||
homepage = "https://solana.com/"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
// Wallet settings that can be configured for long-term use
 | 
			
		||||
use serde_derive::{Deserialize, Serialize};
 | 
			
		||||
use std::io;
 | 
			
		||||
use std::{collections::HashMap, io};
 | 
			
		||||
use url::Url;
 | 
			
		||||
 | 
			
		||||
lazy_static! {
 | 
			
		||||
@@ -17,6 +17,8 @@ pub struct Config {
 | 
			
		||||
    pub json_rpc_url: String,
 | 
			
		||||
    pub websocket_url: String,
 | 
			
		||||
    pub keypair_path: String,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub address_labels: HashMap<String, String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Config {
 | 
			
		||||
@@ -36,6 +38,7 @@ impl Default for Config {
 | 
			
		||||
            json_rpc_url,
 | 
			
		||||
            websocket_url,
 | 
			
		||||
            keypair_path,
 | 
			
		||||
            address_labels: HashMap::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								cli-output/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								cli-output/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
[package]
 | 
			
		||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
 | 
			
		||||
edition = "2018"
 | 
			
		||||
name = "solana-cli-output"
 | 
			
		||||
description = "Blockchain, Rebuilt for Scale"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
repository = "https://github.com/solana-labs/solana"
 | 
			
		||||
license = "Apache-2.0"
 | 
			
		||||
homepage = "https://solana.com/"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
chrono = { version = "0.4.11", features = ["serde"] }
 | 
			
		||||
console = "0.10.1"
 | 
			
		||||
humantime = "2.0.0"
 | 
			
		||||
Inflector = "0.11.4"
 | 
			
		||||
indicatif = "0.14.0"
 | 
			
		||||
serde = "1.0.110"
 | 
			
		||||
serde_derive = "1.0.103"
 | 
			
		||||
serde_json = "1.0.53"
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.32" }
 | 
			
		||||
solana-client = { path = "../client", version = "1.2.32" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.32" }
 | 
			
		||||
solana-stake-program = { path = "../programs/stake", version = "1.2.32" }
 | 
			
		||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.32" }
 | 
			
		||||
solana-vote-program = { path = "../programs/vote", version = "1.2.32" }
 | 
			
		||||
 | 
			
		||||
[package.metadata.docs.rs]
 | 
			
		||||
targets = ["x86_64-unknown-linux-gnu"]
 | 
			
		||||
@@ -1,24 +1,29 @@
 | 
			
		||||
use crate::{cli::build_balance_message, display::writeln_name_value};
 | 
			
		||||
use crate::display::{build_balance_message, writeln_name_value};
 | 
			
		||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
 | 
			
		||||
use console::{style, Emoji};
 | 
			
		||||
use inflector::cases::titlecase::to_title_case;
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use serde_json::{Map, Value};
 | 
			
		||||
use solana_clap_utils::keypair::SignOnly;
 | 
			
		||||
use solana_client::rpc_response::{
 | 
			
		||||
    RpcAccountBalance, RpcKeyedAccount, RpcSupply, RpcVoteAccountInfo,
 | 
			
		||||
};
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    clock::{self, Epoch, Slot, UnixTimestamp},
 | 
			
		||||
    epoch_info::EpochInfo,
 | 
			
		||||
    hash::Hash,
 | 
			
		||||
    native_token::lamports_to_sol,
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    signature::Signature,
 | 
			
		||||
    stake_history::StakeHistoryEntry,
 | 
			
		||||
    transaction::Transaction,
 | 
			
		||||
};
 | 
			
		||||
use solana_stake_program::stake_state::{Authorized, Lockup};
 | 
			
		||||
use solana_vote_program::{
 | 
			
		||||
    authorized_voters::AuthorizedVoters,
 | 
			
		||||
    vote_state::{BlockTimestamp, Lockout},
 | 
			
		||||
};
 | 
			
		||||
use std::{collections::BTreeMap, fmt, time::Duration};
 | 
			
		||||
use std::{collections::BTreeMap, fmt, str::FromStr, time::Duration};
 | 
			
		||||
 | 
			
		||||
static WARNING: Emoji = Emoji("⚠️", "!");
 | 
			
		||||
 | 
			
		||||
@@ -482,7 +487,7 @@ impl fmt::Display for CliKeyedStakeState {
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CliStakeState {
 | 
			
		||||
    pub stake_type: CliStakeType,
 | 
			
		||||
    pub total_stake: u64,
 | 
			
		||||
    pub account_balance: u64,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub delegated_stake: Option<u64>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
@@ -497,6 +502,16 @@ pub struct CliStakeState {
 | 
			
		||||
    pub lockup: Option<CliLockup>,
 | 
			
		||||
    #[serde(skip_serializing)]
 | 
			
		||||
    pub use_lamports_unit: bool,
 | 
			
		||||
    #[serde(skip_serializing)]
 | 
			
		||||
    pub current_epoch: Epoch,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub rent_exempt_reserve: Option<u64>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub active_stake: Option<u64>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub activating_stake: Option<u64>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub deactivating_stake: Option<u64>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for CliStakeState {
 | 
			
		||||
@@ -522,52 +537,122 @@ impl fmt::Display for CliStakeState {
 | 
			
		||||
            Ok(())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        writeln!(
 | 
			
		||||
            f,
 | 
			
		||||
            "Balance: {}",
 | 
			
		||||
            build_balance_message(self.account_balance, self.use_lamports_unit, true)
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        if let Some(rent_exempt_reserve) = self.rent_exempt_reserve {
 | 
			
		||||
            writeln!(
 | 
			
		||||
                f,
 | 
			
		||||
                "Rent Exempt Reserve: {}",
 | 
			
		||||
                build_balance_message(rent_exempt_reserve, self.use_lamports_unit, true)
 | 
			
		||||
            )?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        match self.stake_type {
 | 
			
		||||
            CliStakeType::RewardsPool => writeln!(f, "Stake account is a rewards pool")?,
 | 
			
		||||
            CliStakeType::Uninitialized => writeln!(f, "Stake account is uninitialized")?,
 | 
			
		||||
            CliStakeType::Initialized => {
 | 
			
		||||
                writeln!(
 | 
			
		||||
                    f,
 | 
			
		||||
                    "Total Stake: {}",
 | 
			
		||||
                    build_balance_message(self.total_stake, self.use_lamports_unit, true)
 | 
			
		||||
                )?;
 | 
			
		||||
                writeln!(f, "Stake account is undelegated")?;
 | 
			
		||||
                show_authorized(f, self.authorized.as_ref().unwrap())?;
 | 
			
		||||
                show_lockup(f, self.lockup.as_ref().unwrap())?;
 | 
			
		||||
            }
 | 
			
		||||
            CliStakeType::Stake => {
 | 
			
		||||
                writeln!(
 | 
			
		||||
                    f,
 | 
			
		||||
                    "Total Stake: {}",
 | 
			
		||||
                    build_balance_message(self.total_stake, self.use_lamports_unit, true)
 | 
			
		||||
                )?;
 | 
			
		||||
                writeln!(
 | 
			
		||||
                    f,
 | 
			
		||||
                    "Delegated Stake: {}",
 | 
			
		||||
                    build_balance_message(
 | 
			
		||||
                        self.delegated_stake.unwrap(),
 | 
			
		||||
                        self.use_lamports_unit,
 | 
			
		||||
                        true
 | 
			
		||||
                    )
 | 
			
		||||
                )?;
 | 
			
		||||
                if let Some(delegated_vote_account_address) = &self.delegated_vote_account_address {
 | 
			
		||||
                let show_delegation = {
 | 
			
		||||
                    self.active_stake.is_some()
 | 
			
		||||
                        || self.activating_stake.is_some()
 | 
			
		||||
                        || self.deactivating_stake.is_some()
 | 
			
		||||
                        || self
 | 
			
		||||
                            .deactivation_epoch
 | 
			
		||||
                            .map(|de| de > self.current_epoch)
 | 
			
		||||
                            .unwrap_or(true)
 | 
			
		||||
                };
 | 
			
		||||
                if show_delegation {
 | 
			
		||||
                    let delegated_stake = self.delegated_stake.unwrap();
 | 
			
		||||
                    writeln!(
 | 
			
		||||
                        f,
 | 
			
		||||
                        "Delegated Vote Account Address: {}",
 | 
			
		||||
                        delegated_vote_account_address
 | 
			
		||||
                    )?;
 | 
			
		||||
                }
 | 
			
		||||
                writeln!(
 | 
			
		||||
                    f,
 | 
			
		||||
                    "Stake activates starting from epoch: {}",
 | 
			
		||||
                    self.activation_epoch.unwrap()
 | 
			
		||||
                )?;
 | 
			
		||||
                if let Some(deactivation_epoch) = self.deactivation_epoch {
 | 
			
		||||
                    writeln!(
 | 
			
		||||
                        f,
 | 
			
		||||
                        "Stake deactivates starting from epoch: {}",
 | 
			
		||||
                        deactivation_epoch
 | 
			
		||||
                        "Delegated Stake: {}",
 | 
			
		||||
                        build_balance_message(delegated_stake, self.use_lamports_unit, true)
 | 
			
		||||
                    )?;
 | 
			
		||||
                    if self
 | 
			
		||||
                        .deactivation_epoch
 | 
			
		||||
                        .map(|d| self.current_epoch <= d)
 | 
			
		||||
                        .unwrap_or(true)
 | 
			
		||||
                    {
 | 
			
		||||
                        let active_stake = self.active_stake.unwrap_or(0);
 | 
			
		||||
                        writeln!(
 | 
			
		||||
                            f,
 | 
			
		||||
                            "Active Stake: {}",
 | 
			
		||||
                            build_balance_message(active_stake, self.use_lamports_unit, true),
 | 
			
		||||
                        )?;
 | 
			
		||||
                        let activating_stake = self.activating_stake.or_else(|| {
 | 
			
		||||
                            if self.active_stake.is_none() {
 | 
			
		||||
                                Some(delegated_stake)
 | 
			
		||||
                            } else {
 | 
			
		||||
                                None
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                        if let Some(activating_stake) = activating_stake {
 | 
			
		||||
                            writeln!(
 | 
			
		||||
                                f,
 | 
			
		||||
                                "Activating Stake: {}",
 | 
			
		||||
                                build_balance_message(
 | 
			
		||||
                                    activating_stake,
 | 
			
		||||
                                    self.use_lamports_unit,
 | 
			
		||||
                                    true
 | 
			
		||||
                                ),
 | 
			
		||||
                            )?;
 | 
			
		||||
                            writeln!(
 | 
			
		||||
                                f,
 | 
			
		||||
                                "Stake activates starting from epoch: {}",
 | 
			
		||||
                                self.activation_epoch.unwrap()
 | 
			
		||||
                            )?;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if let Some(deactivation_epoch) = self.deactivation_epoch {
 | 
			
		||||
                        if self.current_epoch > deactivation_epoch {
 | 
			
		||||
                            let deactivating_stake = self.deactivating_stake.or(self.active_stake);
 | 
			
		||||
                            if let Some(deactivating_stake) = deactivating_stake {
 | 
			
		||||
                                writeln!(
 | 
			
		||||
                                    f,
 | 
			
		||||
                                    "Inactive Stake: {}",
 | 
			
		||||
                                    build_balance_message(
 | 
			
		||||
                                        delegated_stake - deactivating_stake,
 | 
			
		||||
                                        self.use_lamports_unit,
 | 
			
		||||
                                        true
 | 
			
		||||
                                    ),
 | 
			
		||||
                                )?;
 | 
			
		||||
                                writeln!(
 | 
			
		||||
                                    f,
 | 
			
		||||
                                    "Deactivating Stake: {}",
 | 
			
		||||
                                    build_balance_message(
 | 
			
		||||
                                        deactivating_stake,
 | 
			
		||||
                                        self.use_lamports_unit,
 | 
			
		||||
                                        true
 | 
			
		||||
                                    ),
 | 
			
		||||
                                )?;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        writeln!(
 | 
			
		||||
                            f,
 | 
			
		||||
                            "Stake deactivates starting from epoch: {}",
 | 
			
		||||
                            deactivation_epoch
 | 
			
		||||
                        )?;
 | 
			
		||||
                    }
 | 
			
		||||
                    if let Some(delegated_vote_account_address) =
 | 
			
		||||
                        &self.delegated_vote_account_address
 | 
			
		||||
                    {
 | 
			
		||||
                        writeln!(
 | 
			
		||||
                            f,
 | 
			
		||||
                            "Delegated Vote Account Address: {}",
 | 
			
		||||
                            delegated_vote_account_address
 | 
			
		||||
                        )?;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    writeln!(f, "Stake account is undelegated")?;
 | 
			
		||||
                }
 | 
			
		||||
                show_authorized(f, self.authorized.as_ref().unwrap())?;
 | 
			
		||||
                show_lockup(f, self.lockup.as_ref().unwrap())?;
 | 
			
		||||
@@ -900,6 +985,7 @@ impl fmt::Display for CliSignOnlyData {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CliSignature {
 | 
			
		||||
    pub signature: String,
 | 
			
		||||
}
 | 
			
		||||
@@ -913,6 +999,7 @@ impl fmt::Display for CliSignature {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CliAccountBalances {
 | 
			
		||||
    pub accounts: Vec<RpcAccountBalance>,
 | 
			
		||||
}
 | 
			
		||||
@@ -937,6 +1024,7 @@ impl fmt::Display for CliAccountBalances {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CliSupply {
 | 
			
		||||
    pub total: u64,
 | 
			
		||||
    pub circulating: u64,
 | 
			
		||||
@@ -981,3 +1069,171 @@ impl fmt::Display for CliSupply {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct CliFees {
 | 
			
		||||
    pub slot: Slot,
 | 
			
		||||
    pub blockhash: String,
 | 
			
		||||
    pub lamports_per_signature: u64,
 | 
			
		||||
    pub last_valid_slot: Slot,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for CliFees {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 | 
			
		||||
        writeln_name_value(f, "Blockhash:", &self.blockhash)?;
 | 
			
		||||
        writeln_name_value(
 | 
			
		||||
            f,
 | 
			
		||||
            "Lamports per signature:",
 | 
			
		||||
            &self.lamports_per_signature.to_string(),
 | 
			
		||||
        )?;
 | 
			
		||||
        writeln_name_value(f, "Last valid slot:", &self.last_valid_slot.to_string())?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn return_signers(
 | 
			
		||||
    tx: &Transaction,
 | 
			
		||||
    output_format: &OutputFormat,
 | 
			
		||||
) -> Result<String, Box<dyn std::error::Error>> {
 | 
			
		||||
    let verify_results = tx.verify_with_results();
 | 
			
		||||
    let mut signers = Vec::new();
 | 
			
		||||
    let mut absent = Vec::new();
 | 
			
		||||
    let mut bad_sig = Vec::new();
 | 
			
		||||
    tx.signatures
 | 
			
		||||
        .iter()
 | 
			
		||||
        .zip(tx.message.account_keys.iter())
 | 
			
		||||
        .zip(verify_results.into_iter())
 | 
			
		||||
        .for_each(|((sig, key), res)| {
 | 
			
		||||
            if res {
 | 
			
		||||
                signers.push(format!("{}={}", key, sig))
 | 
			
		||||
            } else if *sig == Signature::default() {
 | 
			
		||||
                absent.push(key.to_string());
 | 
			
		||||
            } else {
 | 
			
		||||
                bad_sig.push(key.to_string());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    let cli_command = CliSignOnlyData {
 | 
			
		||||
        blockhash: tx.message.recent_blockhash.to_string(),
 | 
			
		||||
        signers,
 | 
			
		||||
        absent,
 | 
			
		||||
        bad_sig,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Ok(output_format.formatted_string(&cli_command))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
 | 
			
		||||
    let object: Value = serde_json::from_str(&reply).unwrap();
 | 
			
		||||
    let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
 | 
			
		||||
    let blockhash = blockhash_str.parse::<Hash>().unwrap();
 | 
			
		||||
    let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
 | 
			
		||||
    let signer_strings = object.get("signers");
 | 
			
		||||
    if let Some(sig_strings) = signer_strings {
 | 
			
		||||
        present_signers = sig_strings
 | 
			
		||||
            .as_array()
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|signer_string| {
 | 
			
		||||
                let mut signer = signer_string.as_str().unwrap().split('=');
 | 
			
		||||
                let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
 | 
			
		||||
                let sig = Signature::from_str(signer.next().unwrap()).unwrap();
 | 
			
		||||
                (key, sig)
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
    }
 | 
			
		||||
    let mut absent_signers: Vec<Pubkey> = Vec::new();
 | 
			
		||||
    let signer_strings = object.get("absent");
 | 
			
		||||
    if let Some(sig_strings) = signer_strings {
 | 
			
		||||
        absent_signers = sig_strings
 | 
			
		||||
            .as_array()
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|val| {
 | 
			
		||||
                let s = val.as_str().unwrap();
 | 
			
		||||
                Pubkey::from_str(s).unwrap()
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
    }
 | 
			
		||||
    let mut bad_signers: Vec<Pubkey> = Vec::new();
 | 
			
		||||
    let signer_strings = object.get("badSig");
 | 
			
		||||
    if let Some(sig_strings) = signer_strings {
 | 
			
		||||
        bad_signers = sig_strings
 | 
			
		||||
            .as_array()
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|val| {
 | 
			
		||||
                let s = val.as_str().unwrap();
 | 
			
		||||
                Pubkey::from_str(s).unwrap()
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SignOnly {
 | 
			
		||||
        blockhash,
 | 
			
		||||
        present_signers,
 | 
			
		||||
        absent_signers,
 | 
			
		||||
        bad_signers,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use solana_sdk::{
 | 
			
		||||
        message::Message,
 | 
			
		||||
        pubkey::Pubkey,
 | 
			
		||||
        signature::{keypair_from_seed, NullSigner, Signature, Signer, SignerError},
 | 
			
		||||
        system_instruction,
 | 
			
		||||
        transaction::Transaction,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_return_signers() {
 | 
			
		||||
        struct BadSigner {
 | 
			
		||||
            pubkey: Pubkey,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        impl BadSigner {
 | 
			
		||||
            pub fn new(pubkey: Pubkey) -> Self {
 | 
			
		||||
                Self { pubkey }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        impl Signer for BadSigner {
 | 
			
		||||
            fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
 | 
			
		||||
                Ok(self.pubkey)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
 | 
			
		||||
                Ok(Signature::new(&[1u8; 64]))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap());
 | 
			
		||||
        let absent: Box<dyn Signer> = Box::new(NullSigner::new(&Pubkey::new(&[3u8; 32])));
 | 
			
		||||
        let bad: Box<dyn Signer> = Box::new(BadSigner::new(Pubkey::new(&[4u8; 32])));
 | 
			
		||||
        let to = Pubkey::new(&[5u8; 32]);
 | 
			
		||||
        let nonce = Pubkey::new(&[6u8; 32]);
 | 
			
		||||
        let from = present.pubkey();
 | 
			
		||||
        let fee_payer = absent.pubkey();
 | 
			
		||||
        let nonce_auth = bad.pubkey();
 | 
			
		||||
        let mut tx = Transaction::new_unsigned(Message::new_with_nonce(
 | 
			
		||||
            vec![system_instruction::transfer(&from, &to, 42)],
 | 
			
		||||
            Some(&fee_payer),
 | 
			
		||||
            &nonce,
 | 
			
		||||
            &nonce_auth,
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()];
 | 
			
		||||
        let blockhash = Hash::new(&[7u8; 32]);
 | 
			
		||||
        tx.try_partial_sign(&signers, blockhash).unwrap();
 | 
			
		||||
        let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap();
 | 
			
		||||
        let sign_only = parse_sign_only_reply_string(&res);
 | 
			
		||||
        assert_eq!(sign_only.blockhash, blockhash);
 | 
			
		||||
        assert_eq!(sign_only.present_signers[0].0, present.pubkey());
 | 
			
		||||
        assert_eq!(sign_only.absent_signers[0], absent.pubkey());
 | 
			
		||||
        assert_eq!(sign_only.bad_signers[0], bad.pubkey());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,30 @@
 | 
			
		||||
use crate::cli::SettingType;
 | 
			
		||||
use console::style;
 | 
			
		||||
use indicatif::{ProgressBar, ProgressStyle};
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize,
 | 
			
		||||
    transaction::Transaction,
 | 
			
		||||
};
 | 
			
		||||
use solana_transaction_status::RpcTransactionStatusMeta;
 | 
			
		||||
use solana_transaction_status::UiTransactionStatusMeta;
 | 
			
		||||
use std::{fmt, io};
 | 
			
		||||
 | 
			
		||||
pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String {
 | 
			
		||||
    if use_lamports_unit {
 | 
			
		||||
        let ess = if lamports == 1 { "" } else { "s" };
 | 
			
		||||
        let unit = if show_unit {
 | 
			
		||||
            format!(" lamport{}", ess)
 | 
			
		||||
        } else {
 | 
			
		||||
            "".to_string()
 | 
			
		||||
        };
 | 
			
		||||
        format!("{:?}{}", lamports, unit)
 | 
			
		||||
    } else {
 | 
			
		||||
        let sol = lamports_to_sol(lamports);
 | 
			
		||||
        let sol_str = format!("{:.9}", sol);
 | 
			
		||||
        let pretty_sol = sol_str.trim_end_matches('0').trim_end_matches('.');
 | 
			
		||||
        let unit = if show_unit { " SOL" } else { "" };
 | 
			
		||||
        format!("{}{}", pretty_sol, unit)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Pretty print a "name value"
 | 
			
		||||
pub fn println_name_value(name: &str, value: &str) {
 | 
			
		||||
    let styled_value = if value == "" {
 | 
			
		||||
@@ -26,21 +44,6 @@ pub fn writeln_name_value(f: &mut fmt::Formatter, name: &str, value: &str) -> fm
 | 
			
		||||
    writeln!(f, "{} {}", style(name).bold(), styled_value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
 | 
			
		||||
    let description = match setting_type {
 | 
			
		||||
        SettingType::Explicit => "",
 | 
			
		||||
        SettingType::Computed => "(computed)",
 | 
			
		||||
        SettingType::SystemDefault => "(default)",
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    println!(
 | 
			
		||||
        "{} {} {}",
 | 
			
		||||
        style(name).bold(),
 | 
			
		||||
        style(value),
 | 
			
		||||
        style(description).italic(),
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn println_signers(
 | 
			
		||||
    blockhash: &Hash,
 | 
			
		||||
    signers: &[String],
 | 
			
		||||
@@ -67,7 +70,7 @@ pub fn println_signers(
 | 
			
		||||
pub fn write_transaction<W: io::Write>(
 | 
			
		||||
    w: &mut W,
 | 
			
		||||
    transaction: &Transaction,
 | 
			
		||||
    transaction_status: &Option<RpcTransactionStatusMeta>,
 | 
			
		||||
    transaction_status: &Option<UiTransactionStatusMeta>,
 | 
			
		||||
    prefix: &str,
 | 
			
		||||
) -> io::Result<()> {
 | 
			
		||||
    let message = &transaction.message;
 | 
			
		||||
@@ -190,7 +193,7 @@ pub fn write_transaction<W: io::Write>(
 | 
			
		||||
 | 
			
		||||
pub fn println_transaction(
 | 
			
		||||
    transaction: &Transaction,
 | 
			
		||||
    transaction_status: &Option<RpcTransactionStatusMeta>,
 | 
			
		||||
    transaction_status: &Option<UiTransactionStatusMeta>,
 | 
			
		||||
    prefix: &str,
 | 
			
		||||
) {
 | 
			
		||||
    let mut w = Vec::new();
 | 
			
		||||
@@ -200,3 +203,12 @@ pub fn println_transaction(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Creates a new process bar for processing that will take an unknown amount of time
 | 
			
		||||
pub fn new_spinner_progress_bar() -> ProgressBar {
 | 
			
		||||
    let progress_bar = ProgressBar::new(42);
 | 
			
		||||
    progress_bar
 | 
			
		||||
        .set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}"));
 | 
			
		||||
    progress_bar.enable_steady_tick(100);
 | 
			
		||||
    progress_bar
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								cli-output/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								cli-output/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
mod cli_output;
 | 
			
		||||
pub mod display;
 | 
			
		||||
pub use cli_output::*;
 | 
			
		||||
@@ -3,13 +3,13 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
 | 
			
		||||
edition = "2018"
 | 
			
		||||
name = "solana-cli"
 | 
			
		||||
description = "Blockchain, Rebuilt for Scale"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
repository = "https://github.com/solana-labs/solana"
 | 
			
		||||
license = "Apache-2.0"
 | 
			
		||||
homepage = "https://solana.com/"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
bincode = "1.2.1"
 | 
			
		||||
bincode = "1.3.1"
 | 
			
		||||
bs58 = "0.3.1"
 | 
			
		||||
chrono = { version = "0.4.11", features = ["serde"] }
 | 
			
		||||
clap = "2.33.1"
 | 
			
		||||
@@ -27,28 +27,29 @@ reqwest = { version = "0.10.4", default-features = false, features = ["blocking"
 | 
			
		||||
serde = "1.0.110"
 | 
			
		||||
serde_derive = "1.0.103"
 | 
			
		||||
serde_json = "1.0.53"
 | 
			
		||||
solana-budget-program = { path = "../programs/budget", version = "1.2.0" }
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.0" }
 | 
			
		||||
solana-cli-config = { path = "../cli-config", version = "1.2.0" }
 | 
			
		||||
solana-client = { path = "../client", version = "1.2.0" }
 | 
			
		||||
solana-config-program = { path = "../programs/config", version = "1.2.0" }
 | 
			
		||||
solana-faucet = { path = "../faucet", version = "1.2.0" }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.0" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.0" }
 | 
			
		||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.2.0" }
 | 
			
		||||
solana-runtime = { path = "../runtime", version = "1.2.0" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.0" }
 | 
			
		||||
solana-stake-program = { path = "../programs/stake", version = "1.2.0" }
 | 
			
		||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.0" }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.0" }
 | 
			
		||||
solana-vote-program = { path = "../programs/vote", version = "1.2.0" }
 | 
			
		||||
solana-vote-signer = { path = "../vote-signer", version = "1.2.0" }
 | 
			
		||||
solana-account-decoder = { path = "../account-decoder", version = "1.2.32" }
 | 
			
		||||
solana-budget-program = { path = "../programs/budget", version = "1.2.32" }
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.32" }
 | 
			
		||||
solana-cli-config = { path = "../cli-config", version = "1.2.32" }
 | 
			
		||||
solana-cli-output = { path = "../cli-output", version = "1.2.32" }
 | 
			
		||||
solana-client = { path = "../client", version = "1.2.32" }
 | 
			
		||||
solana-config-program = { path = "../programs/config", version = "1.2.32" }
 | 
			
		||||
solana-faucet = { path = "../faucet", version = "1.2.32" }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.32" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.32" }
 | 
			
		||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.2.32" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.32" }
 | 
			
		||||
solana-stake-program = { path = "../programs/stake", version = "1.2.32" }
 | 
			
		||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.32" }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.32" }
 | 
			
		||||
solana-vote-program = { path = "../programs/vote", version = "1.2.32" }
 | 
			
		||||
solana-vote-signer = { path = "../vote-signer", version = "1.2.32" }
 | 
			
		||||
thiserror = "1.0.19"
 | 
			
		||||
url = "2.1.1"
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
solana-core = { path = "../core", version = "1.2.0" }
 | 
			
		||||
solana-budget-program = { path = "../programs/budget", version = "1.2.0" }
 | 
			
		||||
solana-core = { path = "../core", version = "1.2.32" }
 | 
			
		||||
solana-budget-program = { path = "../programs/budget", version = "1.2.32" }
 | 
			
		||||
tempfile = "3.1.0"
 | 
			
		||||
 | 
			
		||||
[[bin]]
 | 
			
		||||
 
 | 
			
		||||
@@ -88,11 +88,11 @@ mod tests {
 | 
			
		||||
        let pubkey0 = Pubkey::new(&[0; 32]);
 | 
			
		||||
        let pubkey1 = Pubkey::new(&[1; 32]);
 | 
			
		||||
        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
 | 
			
		||||
        let message0 = Message::new(&[ix0]);
 | 
			
		||||
        let message0 = Message::new(&[ix0], Some(&pubkey0));
 | 
			
		||||
 | 
			
		||||
        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
 | 
			
		||||
        let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
 | 
			
		||||
        let message1 = Message::new(&[ix0, ix1]);
 | 
			
		||||
        let message1 = Message::new(&[ix0, ix1], Some(&pubkey0));
 | 
			
		||||
 | 
			
		||||
        let mut mocks = HashMap::new();
 | 
			
		||||
        mocks.insert(RpcRequest::GetBalance, account_balance_response.clone());
 | 
			
		||||
@@ -176,13 +176,13 @@ mod tests {
 | 
			
		||||
        let pubkey0 = Pubkey::new(&[0; 32]);
 | 
			
		||||
        let pubkey1 = Pubkey::new(&[1; 32]);
 | 
			
		||||
        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
 | 
			
		||||
        let message0 = Message::new(&[ix0]);
 | 
			
		||||
        let message0 = Message::new(&[ix0], Some(&pubkey0));
 | 
			
		||||
        assert_eq!(calculate_fee(&fee_calculator, &[&message0]), 1);
 | 
			
		||||
 | 
			
		||||
        // Two messages, additive fees.
 | 
			
		||||
        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
 | 
			
		||||
        let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
 | 
			
		||||
        let message1 = Message::new(&[ix0, ix1]);
 | 
			
		||||
        let message1 = Message::new(&[ix0, ix1], Some(&pubkey0));
 | 
			
		||||
        assert_eq!(calculate_fee(&fee_calculator, &[&message0, &message1]), 3);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										806
									
								
								cli/src/cli.rs
									
									
									
									
									
								
							
							
						
						
									
										806
									
								
								cli/src/cli.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,23 +1,23 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
 | 
			
		||||
    cli_output::*,
 | 
			
		||||
    display::println_name_value,
 | 
			
		||||
    spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
 | 
			
		||||
};
 | 
			
		||||
use clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
 | 
			
		||||
use console::{style, Emoji};
 | 
			
		||||
use indicatif::{ProgressBar, ProgressStyle};
 | 
			
		||||
use solana_clap_utils::{
 | 
			
		||||
    commitment::{commitment_arg, COMMITMENT_ARG},
 | 
			
		||||
    input_parsers::*,
 | 
			
		||||
    input_validators::*,
 | 
			
		||||
    keypair::signer_from_path,
 | 
			
		||||
    keypair::DefaultSigner,
 | 
			
		||||
};
 | 
			
		||||
use solana_cli_output::{
 | 
			
		||||
    display::{new_spinner_progress_bar, println_name_value},
 | 
			
		||||
    *,
 | 
			
		||||
};
 | 
			
		||||
use solana_client::{
 | 
			
		||||
    pubsub_client::{PubsubClient, SlotInfoMessage},
 | 
			
		||||
    rpc_client::RpcClient,
 | 
			
		||||
    rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClient},
 | 
			
		||||
    rpc_config::{RpcLargestAccountsConfig, RpcLargestAccountsFilter},
 | 
			
		||||
    rpc_request::MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE,
 | 
			
		||||
};
 | 
			
		||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
@@ -28,8 +28,13 @@ use solana_sdk::{
 | 
			
		||||
    message::Message,
 | 
			
		||||
    native_token::lamports_to_sol,
 | 
			
		||||
    pubkey::{self, Pubkey},
 | 
			
		||||
    signature::Signature,
 | 
			
		||||
    system_instruction, system_program,
 | 
			
		||||
    sysvar::{self, Sysvar},
 | 
			
		||||
    sysvar::{
 | 
			
		||||
        self,
 | 
			
		||||
        stake_history::{self, StakeHistory},
 | 
			
		||||
        Sysvar,
 | 
			
		||||
    },
 | 
			
		||||
    transaction::Transaction,
 | 
			
		||||
};
 | 
			
		||||
use std::{
 | 
			
		||||
@@ -253,9 +258,8 @@ impl ClusterQuerySubCommands for App<'_, '_> {
 | 
			
		||||
        )
 | 
			
		||||
        .subcommand(
 | 
			
		||||
            SubCommand::with_name("transaction-history")
 | 
			
		||||
                .about("Show historical transactions affecting the given address, \
 | 
			
		||||
                       ordered based on the slot in which they were confirmed in \
 | 
			
		||||
                       from lowest to highest slot")
 | 
			
		||||
                .about("Show historical transactions affecting the given address \
 | 
			
		||||
                        from newest to oldest")
 | 
			
		||||
                .arg(
 | 
			
		||||
                    pubkey!(Arg::with_name("address")
 | 
			
		||||
                        .index(1)
 | 
			
		||||
@@ -263,26 +267,22 @@ impl ClusterQuerySubCommands for App<'_, '_> {
 | 
			
		||||
                        .required(true),
 | 
			
		||||
                        "Account address"),
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::with_name("end_slot")
 | 
			
		||||
                        .takes_value(false)
 | 
			
		||||
                        .value_name("SLOT")
 | 
			
		||||
                        .index(2)
 | 
			
		||||
                        .validator(is_slot)
 | 
			
		||||
                        .help(
 | 
			
		||||
                            "Slot to start from [default: latest slot at maximum commitment]"
 | 
			
		||||
                        ),
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::with_name("limit")
 | 
			
		||||
                        .long("limit")
 | 
			
		||||
                        .takes_value(true)
 | 
			
		||||
                        .value_name("NUMBER OF SLOTS")
 | 
			
		||||
                        .value_name("LIMIT")
 | 
			
		||||
                        .validator(is_slot)
 | 
			
		||||
                        .help(
 | 
			
		||||
                            "Limit the search to this many slots"
 | 
			
		||||
                        ),
 | 
			
		||||
                ),
 | 
			
		||||
                        .default_value("1000")
 | 
			
		||||
                        .help("Maximum number of transaction signatures to return"),
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::with_name("before")
 | 
			
		||||
                        .long("before")
 | 
			
		||||
                        .value_name("TRANSACTION_SIGNATURE")
 | 
			
		||||
                        .takes_value(true)
 | 
			
		||||
                        .help("Start with the first signature older than this one"),
 | 
			
		||||
                )
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -308,7 +308,7 @@ pub fn parse_catchup(
 | 
			
		||||
 | 
			
		||||
pub fn parse_cluster_ping(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer_path: &str,
 | 
			
		||||
    default_signer: &DefaultSigner,
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
    let lamports = value_t_or_exit!(matches, "lamports", u64);
 | 
			
		||||
@@ -328,12 +328,7 @@ pub fn parse_cluster_ping(
 | 
			
		||||
            timeout,
 | 
			
		||||
            commitment_config,
 | 
			
		||||
        },
 | 
			
		||||
        signers: vec![signer_from_path(
 | 
			
		||||
            matches,
 | 
			
		||||
            default_signer_path,
 | 
			
		||||
            "keypair",
 | 
			
		||||
            wallet_manager,
 | 
			
		||||
        )?],
 | 
			
		||||
        signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -450,28 +445,36 @@ pub fn parse_transaction_history(
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
    let address = pubkey_of_signer(matches, "address", wallet_manager)?.unwrap();
 | 
			
		||||
    let end_slot = value_t!(matches, "end_slot", Slot).ok();
 | 
			
		||||
    let slot_limit = value_t!(matches, "limit", u64).ok();
 | 
			
		||||
 | 
			
		||||
    let before = match matches.value_of("before") {
 | 
			
		||||
        Some(signature) => Some(
 | 
			
		||||
            signature
 | 
			
		||||
                .parse()
 | 
			
		||||
                .map_err(|err| CliError::BadParameter(format!("Invalid signature: {}", err)))?,
 | 
			
		||||
        ),
 | 
			
		||||
        None => None,
 | 
			
		||||
    };
 | 
			
		||||
    let until = match matches.value_of("until") {
 | 
			
		||||
        Some(signature) => Some(
 | 
			
		||||
            signature
 | 
			
		||||
                .parse()
 | 
			
		||||
                .map_err(|err| CliError::BadParameter(format!("Invalid signature: {}", err)))?,
 | 
			
		||||
        ),
 | 
			
		||||
        None => None,
 | 
			
		||||
    };
 | 
			
		||||
    let limit = value_t_or_exit!(matches, "limit", usize);
 | 
			
		||||
 | 
			
		||||
    Ok(CliCommandInfo {
 | 
			
		||||
        command: CliCommand::TransactionHistory {
 | 
			
		||||
            address,
 | 
			
		||||
            end_slot,
 | 
			
		||||
            slot_limit,
 | 
			
		||||
            before,
 | 
			
		||||
            until,
 | 
			
		||||
            limit,
 | 
			
		||||
        },
 | 
			
		||||
        signers: vec![],
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Creates a new process bar for processing that will take an unknown amount of time
 | 
			
		||||
fn new_spinner_progress_bar() -> ProgressBar {
 | 
			
		||||
    let progress_bar = ProgressBar::new(42);
 | 
			
		||||
    progress_bar
 | 
			
		||||
        .set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}"));
 | 
			
		||||
    progress_bar.enable_steady_tick(100);
 | 
			
		||||
    progress_bar
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn process_catchup(
 | 
			
		||||
    rpc_client: &RpcClient,
 | 
			
		||||
    node_pubkey: &Pubkey,
 | 
			
		||||
@@ -597,13 +600,16 @@ pub fn process_cluster_version(rpc_client: &RpcClient) -> ProcessResult {
 | 
			
		||||
    Ok(remote_version.solana_core)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn process_fees(rpc_client: &RpcClient) -> ProcessResult {
 | 
			
		||||
    let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
 | 
			
		||||
 | 
			
		||||
    Ok(format!(
 | 
			
		||||
        "blockhash: {}\nlamports per signature: {}",
 | 
			
		||||
        recent_blockhash, fee_calculator.lamports_per_signature
 | 
			
		||||
    ))
 | 
			
		||||
pub fn process_fees(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
 | 
			
		||||
    let result = rpc_client.get_recent_blockhash_with_commitment(CommitmentConfig::default())?;
 | 
			
		||||
    let (recent_blockhash, fee_calculator, last_valid_slot) = result.value;
 | 
			
		||||
    let fees = CliFees {
 | 
			
		||||
        slot: result.context.slot,
 | 
			
		||||
        blockhash: recent_blockhash.to_string(),
 | 
			
		||||
        lamports_per_signature: fee_calculator.lamports_per_signature,
 | 
			
		||||
        last_valid_slot,
 | 
			
		||||
    };
 | 
			
		||||
    Ok(config.output_format.formatted_string(&fees))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn process_leader_schedule(rpc_client: &RpcClient) -> ProcessResult {
 | 
			
		||||
@@ -938,7 +944,7 @@ pub fn process_ping(
 | 
			
		||||
 | 
			
		||||
        let build_message = |lamports| {
 | 
			
		||||
            let ix = system_instruction::transfer(&config.signers[0].pubkey(), &to, lamports);
 | 
			
		||||
            Message::new(&[ix])
 | 
			
		||||
            Message::new(&[ix], Some(&config.signers[0].pubkey()))
 | 
			
		||||
        };
 | 
			
		||||
        let (message, _) = resolve_spend_tx_and_check_account_balance(
 | 
			
		||||
            rpc_client,
 | 
			
		||||
@@ -1181,8 +1187,13 @@ pub fn process_show_stakes(
 | 
			
		||||
    let progress_bar = new_spinner_progress_bar();
 | 
			
		||||
    progress_bar.set_message("Fetching stake accounts...");
 | 
			
		||||
    let all_stake_accounts = rpc_client.get_program_accounts(&solana_stake_program::id())?;
 | 
			
		||||
    let stake_history_account = rpc_client.get_account(&stake_history::id())?;
 | 
			
		||||
    progress_bar.finish_and_clear();
 | 
			
		||||
 | 
			
		||||
    let stake_history = StakeHistory::from_account(&stake_history_account).ok_or_else(|| {
 | 
			
		||||
        CliError::RpcRequestError("Failed to deserialize stake history".to_string())
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    let mut stake_accounts: Vec<CliKeyedStakeState> = vec![];
 | 
			
		||||
    for (stake_pubkey, stake_account) in all_stake_accounts {
 | 
			
		||||
        if let Ok(stake_state) = stake_account.state() {
 | 
			
		||||
@@ -1195,6 +1206,7 @@ pub fn process_show_stakes(
 | 
			
		||||
                                stake_account.lamports,
 | 
			
		||||
                                &stake_state,
 | 
			
		||||
                                use_lamports_unit,
 | 
			
		||||
                                &stake_history,
 | 
			
		||||
                            ),
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
@@ -1211,6 +1223,7 @@ pub fn process_show_stakes(
 | 
			
		||||
                                stake_account.lamports,
 | 
			
		||||
                                &stake_state,
 | 
			
		||||
                                use_lamports_unit,
 | 
			
		||||
                                &stake_history,
 | 
			
		||||
                            ),
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
@@ -1272,41 +1285,40 @@ pub fn process_show_validators(
 | 
			
		||||
 | 
			
		||||
pub fn process_transaction_history(
 | 
			
		||||
    rpc_client: &RpcClient,
 | 
			
		||||
    config: &CliConfig,
 | 
			
		||||
    address: &Pubkey,
 | 
			
		||||
    end_slot: Option<Slot>, // None == use latest slot
 | 
			
		||||
    slot_limit: Option<u64>,
 | 
			
		||||
    before: Option<Signature>,
 | 
			
		||||
    until: Option<Signature>,
 | 
			
		||||
    limit: usize,
 | 
			
		||||
) -> ProcessResult {
 | 
			
		||||
    let end_slot = {
 | 
			
		||||
        if let Some(end_slot) = end_slot {
 | 
			
		||||
            end_slot
 | 
			
		||||
    let results = rpc_client.get_confirmed_signatures_for_address2_with_config(
 | 
			
		||||
        address,
 | 
			
		||||
        GetConfirmedSignaturesForAddress2Config {
 | 
			
		||||
            before,
 | 
			
		||||
            until,
 | 
			
		||||
            limit: Some(limit),
 | 
			
		||||
        },
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
    let transactions_found = format!("{} transactions found", results.len());
 | 
			
		||||
 | 
			
		||||
    for result in results {
 | 
			
		||||
        if config.verbose {
 | 
			
		||||
            println!(
 | 
			
		||||
                "{} [slot={} status={}] {}",
 | 
			
		||||
                result.signature,
 | 
			
		||||
                result.slot,
 | 
			
		||||
                match result.err {
 | 
			
		||||
                    None => "Confirmed".to_string(),
 | 
			
		||||
                    Some(err) => format!("Failed: {:?}", err),
 | 
			
		||||
                },
 | 
			
		||||
                result.memo.unwrap_or_else(|| "".to_string()),
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            rpc_client.get_slot_with_commitment(CommitmentConfig::max())?
 | 
			
		||||
            println!("{}", result.signature);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    let mut start_slot = match slot_limit {
 | 
			
		||||
        Some(slot_limit) => end_slot.saturating_sub(slot_limit),
 | 
			
		||||
        None => rpc_client.minimum_ledger_slot()?,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    println!(
 | 
			
		||||
        "Transactions affecting {} within slots [{},{}]",
 | 
			
		||||
        address, start_slot, end_slot
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let mut transaction_count = 0;
 | 
			
		||||
    while start_slot < end_slot {
 | 
			
		||||
        let signatures = rpc_client.get_confirmed_signatures_for_address(
 | 
			
		||||
            address,
 | 
			
		||||
            start_slot,
 | 
			
		||||
            (start_slot + MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE).min(end_slot),
 | 
			
		||||
        )?;
 | 
			
		||||
        for signature in &signatures {
 | 
			
		||||
            println!("{}", signature);
 | 
			
		||||
        }
 | 
			
		||||
        transaction_count += signatures.len();
 | 
			
		||||
        start_slot += MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE;
 | 
			
		||||
    }
 | 
			
		||||
    Ok(format!("{} transactions found", transaction_count))
 | 
			
		||||
    Ok(transactions_found)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
@@ -1327,12 +1339,16 @@ mod tests {
 | 
			
		||||
        let default_keypair = Keypair::new();
 | 
			
		||||
        let (default_keypair_file, mut tmp_file) = make_tmp_file();
 | 
			
		||||
        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
 | 
			
		||||
        let default_signer = DefaultSigner {
 | 
			
		||||
            path: default_keypair_file,
 | 
			
		||||
            arg_name: String::new(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let test_cluster_version = test_commands
 | 
			
		||||
            .clone()
 | 
			
		||||
            .get_matches_from(vec!["test", "cluster-date"]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_cluster_version, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_cluster_version, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::ClusterDate,
 | 
			
		||||
                signers: vec![],
 | 
			
		||||
@@ -1343,7 +1359,7 @@ mod tests {
 | 
			
		||||
            .clone()
 | 
			
		||||
            .get_matches_from(vec!["test", "cluster-version"]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_cluster_version, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_cluster_version, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::ClusterVersion,
 | 
			
		||||
                signers: vec![],
 | 
			
		||||
@@ -1352,7 +1368,7 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
        let test_fees = test_commands.clone().get_matches_from(vec!["test", "fees"]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_fees, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_fees, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::Fees,
 | 
			
		||||
                signers: vec![],
 | 
			
		||||
@@ -1365,7 +1381,7 @@ mod tests {
 | 
			
		||||
                .clone()
 | 
			
		||||
                .get_matches_from(vec!["test", "block-time", &slot.to_string()]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_get_block_time, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_get_block_time, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::GetBlockTime { slot: Some(slot) },
 | 
			
		||||
                signers: vec![],
 | 
			
		||||
@@ -1376,7 +1392,7 @@ mod tests {
 | 
			
		||||
            .clone()
 | 
			
		||||
            .get_matches_from(vec!["test", "epoch-info"]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_get_epoch_info, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_get_epoch_info, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::GetEpochInfo {
 | 
			
		||||
                    commitment_config: CommitmentConfig::recent(),
 | 
			
		||||
@@ -1389,7 +1405,7 @@ mod tests {
 | 
			
		||||
            .clone()
 | 
			
		||||
            .get_matches_from(vec!["test", "genesis-hash"]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_get_genesis_hash, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_get_genesis_hash, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::GetGenesisHash,
 | 
			
		||||
                signers: vec![],
 | 
			
		||||
@@ -1398,7 +1414,7 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
        let test_get_slot = test_commands.clone().get_matches_from(vec!["test", "slot"]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_get_slot, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_get_slot, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::GetSlot {
 | 
			
		||||
                    commitment_config: CommitmentConfig::recent(),
 | 
			
		||||
@@ -1411,7 +1427,7 @@ mod tests {
 | 
			
		||||
            .clone()
 | 
			
		||||
            .get_matches_from(vec!["test", "epoch"]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_get_epoch, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_get_epoch, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::GetEpoch {
 | 
			
		||||
                    commitment_config: CommitmentConfig::recent(),
 | 
			
		||||
@@ -1424,7 +1440,7 @@ mod tests {
 | 
			
		||||
            .clone()
 | 
			
		||||
            .get_matches_from(vec!["test", "total-supply"]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_total_supply, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_total_supply, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::TotalSupply {
 | 
			
		||||
                    commitment_config: CommitmentConfig::recent(),
 | 
			
		||||
@@ -1437,7 +1453,7 @@ mod tests {
 | 
			
		||||
            .clone()
 | 
			
		||||
            .get_matches_from(vec!["test", "transaction-count"]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_transaction_count, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_transaction_count, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::GetTransactionCount {
 | 
			
		||||
                    commitment_config: CommitmentConfig::recent(),
 | 
			
		||||
@@ -1459,7 +1475,7 @@ mod tests {
 | 
			
		||||
            "max",
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_ping, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_ping, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::Ping {
 | 
			
		||||
                    lamports: 1,
 | 
			
		||||
 
 | 
			
		||||
@@ -18,16 +18,12 @@ macro_rules! pubkey {
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[macro_use]
 | 
			
		||||
extern crate serde_derive;
 | 
			
		||||
 | 
			
		||||
pub mod checks;
 | 
			
		||||
pub mod cli;
 | 
			
		||||
pub mod cli_output;
 | 
			
		||||
pub mod cluster_query;
 | 
			
		||||
pub mod display;
 | 
			
		||||
pub mod nonce;
 | 
			
		||||
pub mod offline;
 | 
			
		||||
pub mod spend_utils;
 | 
			
		||||
pub mod stake;
 | 
			
		||||
pub mod test_utils;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,17 +2,33 @@ use clap::{crate_description, crate_name, AppSettings, Arg, ArgGroup, ArgMatches
 | 
			
		||||
use console::style;
 | 
			
		||||
 | 
			
		||||
use solana_clap_utils::{
 | 
			
		||||
    input_validators::is_url, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, DisplayError,
 | 
			
		||||
    input_validators::is_url,
 | 
			
		||||
    keypair::{CliSigners, DefaultSigner, SKIP_SEED_PHRASE_VALIDATION_ARG},
 | 
			
		||||
    DisplayError,
 | 
			
		||||
};
 | 
			
		||||
use solana_cli::{
 | 
			
		||||
    cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners},
 | 
			
		||||
    cli_output::OutputFormat,
 | 
			
		||||
    display::{println_name_value, println_name_value_or},
 | 
			
		||||
use solana_cli::cli::{
 | 
			
		||||
    app, parse_command, process_command, CliCommandInfo, CliConfig, SettingType,
 | 
			
		||||
};
 | 
			
		||||
use solana_cli_config::{Config, CONFIG_FILE};
 | 
			
		||||
use solana_cli_output::{display::println_name_value, OutputFormat};
 | 
			
		||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
 | 
			
		||||
use std::{error, sync::Arc};
 | 
			
		||||
 | 
			
		||||
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
 | 
			
		||||
    let description = match setting_type {
 | 
			
		||||
        SettingType::Explicit => "",
 | 
			
		||||
        SettingType::Computed => "(computed)",
 | 
			
		||||
        SettingType::SystemDefault => "(default)",
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    println!(
 | 
			
		||||
        "{} {} {}",
 | 
			
		||||
        style(name).bold(),
 | 
			
		||||
        style(value),
 | 
			
		||||
        style(description).italic(),
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error>> {
 | 
			
		||||
    let parse_args = match matches.subcommand() {
 | 
			
		||||
        ("config", Some(matches)) => match matches.subcommand() {
 | 
			
		||||
@@ -119,13 +135,19 @@ pub fn parse_args<'a>(
 | 
			
		||||
        matches.value_of("json_rpc_url").unwrap_or(""),
 | 
			
		||||
        &config.json_rpc_url,
 | 
			
		||||
    );
 | 
			
		||||
    let default_signer_arg_name = "keypair".to_string();
 | 
			
		||||
    let (_, default_signer_path) = CliConfig::compute_keypair_path_setting(
 | 
			
		||||
        matches.value_of("keypair").unwrap_or(""),
 | 
			
		||||
        matches.value_of(&default_signer_arg_name).unwrap_or(""),
 | 
			
		||||
        &config.keypair_path,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let default_signer = DefaultSigner {
 | 
			
		||||
        arg_name: default_signer_arg_name,
 | 
			
		||||
        path: default_signer_path.clone(),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let CliCommandInfo { command, signers } =
 | 
			
		||||
        parse_command(&matches, &default_signer_path, &mut wallet_manager)?;
 | 
			
		||||
        parse_command(&matches, &default_signer, &mut wallet_manager)?;
 | 
			
		||||
 | 
			
		||||
    let output_format = matches
 | 
			
		||||
        .value_of("output_format")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										206
									
								
								cli/src/nonce.rs
									
									
									
									
									
								
							
							
						
						
									
										206
									
								
								cli/src/nonce.rs
									
									
									
									
									
								
							@@ -1,28 +1,26 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    checks::{check_account_for_fee, check_unique_pubkeys},
 | 
			
		||||
    cli::{
 | 
			
		||||
        generate_unique_signers, log_instruction_custom_error, CliCommand, CliCommandInfo,
 | 
			
		||||
        CliConfig, CliError, ProcessResult, SignerIndex,
 | 
			
		||||
        log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
 | 
			
		||||
        ProcessResult,
 | 
			
		||||
    },
 | 
			
		||||
    cli_output::CliNonceAccount,
 | 
			
		||||
    spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
 | 
			
		||||
};
 | 
			
		||||
use clap::{App, Arg, ArgMatches, SubCommand};
 | 
			
		||||
use solana_clap_utils::{
 | 
			
		||||
    input_parsers::*, input_validators::*, offline::BLOCKHASH_ARG, ArgConstant,
 | 
			
		||||
    input_parsers::*,
 | 
			
		||||
    input_validators::*,
 | 
			
		||||
    keypair::{DefaultSigner, SignerIndex},
 | 
			
		||||
    nonce::*,
 | 
			
		||||
};
 | 
			
		||||
use solana_client::rpc_client::RpcClient;
 | 
			
		||||
use solana_cli_output::CliNonceAccount;
 | 
			
		||||
use solana_client::{nonce_utils::*, rpc_client::RpcClient};
 | 
			
		||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    account::Account,
 | 
			
		||||
    account_utils::StateMut,
 | 
			
		||||
    hash::Hash,
 | 
			
		||||
    message::Message,
 | 
			
		||||
    nonce::{
 | 
			
		||||
        self,
 | 
			
		||||
        state::{Data, Versions},
 | 
			
		||||
        State,
 | 
			
		||||
    },
 | 
			
		||||
    nonce::{self, State},
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    system_instruction::{
 | 
			
		||||
        advance_nonce_account, authorize_nonce_account, create_nonce_account,
 | 
			
		||||
@@ -32,64 +30,11 @@ use solana_sdk::{
 | 
			
		||||
    transaction::Transaction,
 | 
			
		||||
};
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Error, PartialEq)]
 | 
			
		||||
pub enum CliNonceError {
 | 
			
		||||
    #[error("invalid account owner")]
 | 
			
		||||
    InvalidAccountOwner,
 | 
			
		||||
    #[error("invalid account data")]
 | 
			
		||||
    InvalidAccountData,
 | 
			
		||||
    #[error("unexpected account data size")]
 | 
			
		||||
    UnexpectedDataSize,
 | 
			
		||||
    #[error("query hash does not match stored hash")]
 | 
			
		||||
    InvalidHash,
 | 
			
		||||
    #[error("query authority does not match account authority")]
 | 
			
		||||
    InvalidAuthority,
 | 
			
		||||
    #[error("invalid state for requested operation")]
 | 
			
		||||
    InvalidStateForOperation,
 | 
			
		||||
    #[error("client error: {0}")]
 | 
			
		||||
    Client(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub const NONCE_ARG: ArgConstant<'static> = ArgConstant {
 | 
			
		||||
    name: "nonce",
 | 
			
		||||
    long: "nonce",
 | 
			
		||||
    help: "Provide the nonce account to use when creating a nonced \n\
 | 
			
		||||
           transaction. Nonced transactions are useful when a transaction \n\
 | 
			
		||||
           requires a lengthy signing process. Learn more about nonced \n\
 | 
			
		||||
           transactions at https://docs.solana.com/offline-signing/durable-nonce",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const NONCE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
 | 
			
		||||
    name: "nonce_authority",
 | 
			
		||||
    long: "nonce-authority",
 | 
			
		||||
    help: "Provide the nonce authority keypair to use when signing a nonced transaction",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub trait NonceSubCommands {
 | 
			
		||||
    fn nonce_subcommands(self) -> Self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn nonce_arg<'a, 'b>() -> Arg<'a, 'b> {
 | 
			
		||||
    Arg::with_name(NONCE_ARG.name)
 | 
			
		||||
        .long(NONCE_ARG.long)
 | 
			
		||||
        .takes_value(true)
 | 
			
		||||
        .value_name("PUBKEY")
 | 
			
		||||
        .requires(BLOCKHASH_ARG.name)
 | 
			
		||||
        .validator(is_valid_pubkey)
 | 
			
		||||
        .help(NONCE_ARG.help)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
 | 
			
		||||
    Arg::with_name(NONCE_AUTHORITY_ARG.name)
 | 
			
		||||
        .long(NONCE_AUTHORITY_ARG.long)
 | 
			
		||||
        .takes_value(true)
 | 
			
		||||
        .value_name("KEYPAIR")
 | 
			
		||||
        .validator(is_valid_signer)
 | 
			
		||||
        .help(NONCE_AUTHORITY_ARG.help)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NonceSubCommands for App<'_, '_> {
 | 
			
		||||
    fn nonce_subcommands(self) -> Self {
 | 
			
		||||
        self.subcommand(
 | 
			
		||||
@@ -219,51 +164,9 @@ impl NonceSubCommands for App<'_, '_> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_account(
 | 
			
		||||
    rpc_client: &RpcClient,
 | 
			
		||||
    nonce_pubkey: &Pubkey,
 | 
			
		||||
) -> Result<Account, CliNonceError> {
 | 
			
		||||
    rpc_client
 | 
			
		||||
        .get_account(nonce_pubkey)
 | 
			
		||||
        .map_err(|e| CliNonceError::Client(format!("{}", e)))
 | 
			
		||||
        .and_then(|a| match account_identity_ok(&a) {
 | 
			
		||||
            Ok(()) => Ok(a),
 | 
			
		||||
            Err(e) => Err(e),
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn account_identity_ok(account: &Account) -> Result<(), CliNonceError> {
 | 
			
		||||
    if account.owner != system_program::id() {
 | 
			
		||||
        Err(CliNonceError::InvalidAccountOwner)
 | 
			
		||||
    } else if account.data.is_empty() {
 | 
			
		||||
        Err(CliNonceError::UnexpectedDataSize)
 | 
			
		||||
    } else {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn state_from_account(account: &Account) -> Result<State, CliNonceError> {
 | 
			
		||||
    account_identity_ok(account)?;
 | 
			
		||||
    StateMut::<Versions>::state(account)
 | 
			
		||||
        .map_err(|_| CliNonceError::InvalidAccountData)
 | 
			
		||||
        .map(|v| v.convert_to_current())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn data_from_account(account: &Account) -> Result<Data, CliNonceError> {
 | 
			
		||||
    account_identity_ok(account)?;
 | 
			
		||||
    state_from_account(account).and_then(|ref s| data_from_state(s).map(|d| d.clone()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn data_from_state(state: &State) -> Result<&Data, CliNonceError> {
 | 
			
		||||
    match state {
 | 
			
		||||
        State::Uninitialized => Err(CliNonceError::InvalidStateForOperation),
 | 
			
		||||
        State::Initialized(data) => Ok(data),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse_authorize_nonce_account(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer_path: &str,
 | 
			
		||||
    default_signer: &DefaultSigner,
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
    let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
 | 
			
		||||
@@ -272,10 +175,9 @@ pub fn parse_authorize_nonce_account(
 | 
			
		||||
        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
 | 
			
		||||
 | 
			
		||||
    let payer_provided = None;
 | 
			
		||||
    let signer_info = generate_unique_signers(
 | 
			
		||||
    let signer_info = default_signer.generate_unique_signers(
 | 
			
		||||
        vec![payer_provided, nonce_authority],
 | 
			
		||||
        matches,
 | 
			
		||||
        default_signer_path,
 | 
			
		||||
        wallet_manager,
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
@@ -291,7 +193,7 @@ pub fn parse_authorize_nonce_account(
 | 
			
		||||
 | 
			
		||||
pub fn parse_nonce_create_account(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer_path: &str,
 | 
			
		||||
    default_signer: &DefaultSigner,
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
    let (nonce_account, nonce_account_pubkey) =
 | 
			
		||||
@@ -301,10 +203,9 @@ pub fn parse_nonce_create_account(
 | 
			
		||||
    let nonce_authority = pubkey_of_signer(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
 | 
			
		||||
 | 
			
		||||
    let payer_provided = None;
 | 
			
		||||
    let signer_info = generate_unique_signers(
 | 
			
		||||
    let signer_info = default_signer.generate_unique_signers(
 | 
			
		||||
        vec![payer_provided, nonce_account],
 | 
			
		||||
        matches,
 | 
			
		||||
        default_signer_path,
 | 
			
		||||
        wallet_manager,
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
@@ -334,7 +235,7 @@ pub fn parse_get_nonce(
 | 
			
		||||
 | 
			
		||||
pub fn parse_new_nonce(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer_path: &str,
 | 
			
		||||
    default_signer: &DefaultSigner,
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
    let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
 | 
			
		||||
@@ -342,10 +243,9 @@ pub fn parse_new_nonce(
 | 
			
		||||
        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
 | 
			
		||||
 | 
			
		||||
    let payer_provided = None;
 | 
			
		||||
    let signer_info = generate_unique_signers(
 | 
			
		||||
    let signer_info = default_signer.generate_unique_signers(
 | 
			
		||||
        vec![payer_provided, nonce_authority],
 | 
			
		||||
        matches,
 | 
			
		||||
        default_signer_path,
 | 
			
		||||
        wallet_manager,
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
@@ -377,7 +277,7 @@ pub fn parse_show_nonce_account(
 | 
			
		||||
 | 
			
		||||
pub fn parse_withdraw_from_nonce_account(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer_path: &str,
 | 
			
		||||
    default_signer: &DefaultSigner,
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
    let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
 | 
			
		||||
@@ -388,10 +288,9 @@ pub fn parse_withdraw_from_nonce_account(
 | 
			
		||||
        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
 | 
			
		||||
 | 
			
		||||
    let payer_provided = None;
 | 
			
		||||
    let signer_info = generate_unique_signers(
 | 
			
		||||
    let signer_info = default_signer.generate_unique_signers(
 | 
			
		||||
        vec![payer_provided, nonce_authority],
 | 
			
		||||
        matches,
 | 
			
		||||
        default_signer_path,
 | 
			
		||||
        wallet_manager,
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
@@ -415,14 +314,14 @@ pub fn check_nonce_account(
 | 
			
		||||
    match state_from_account(nonce_account)? {
 | 
			
		||||
        State::Initialized(ref data) => {
 | 
			
		||||
            if &data.blockhash != nonce_hash {
 | 
			
		||||
                Err(CliNonceError::InvalidHash.into())
 | 
			
		||||
                Err(Error::InvalidHash.into())
 | 
			
		||||
            } else if nonce_authority != &data.authority {
 | 
			
		||||
                Err(CliNonceError::InvalidAuthority.into())
 | 
			
		||||
                Err(Error::InvalidAuthority.into())
 | 
			
		||||
            } else {
 | 
			
		||||
                Ok(())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        State::Uninitialized => Err(CliNonceError::InvalidStateForOperation.into()),
 | 
			
		||||
        State::Uninitialized => Err(Error::InvalidStateForOperation.into()),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -437,7 +336,7 @@ pub fn process_authorize_nonce_account(
 | 
			
		||||
 | 
			
		||||
    let nonce_authority = config.signers[nonce_authority];
 | 
			
		||||
    let ix = authorize_nonce_account(nonce_account, &nonce_authority.pubkey(), new_authority);
 | 
			
		||||
    let message = Message::new_with_payer(&[ix], Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let message = Message::new(&[ix], Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let mut tx = Transaction::new_unsigned(message);
 | 
			
		||||
    tx.try_sign(&config.signers, recent_blockhash)?;
 | 
			
		||||
 | 
			
		||||
@@ -491,7 +390,7 @@ pub fn process_create_nonce_account(
 | 
			
		||||
                lamports,
 | 
			
		||||
            )
 | 
			
		||||
        };
 | 
			
		||||
        Message::new_with_payer(&ixs, Some(&config.signers[0].pubkey()))
 | 
			
		||||
        Message::new(&ixs, Some(&config.signers[0].pubkey()))
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
 | 
			
		||||
@@ -560,7 +459,7 @@ pub fn process_new_nonce(
 | 
			
		||||
    let nonce_authority = config.signers[nonce_authority];
 | 
			
		||||
    let ix = advance_nonce_account(&nonce_account, &nonce_authority.pubkey());
 | 
			
		||||
    let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
 | 
			
		||||
    let message = Message::new_with_payer(&[ix], Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let message = Message::new(&[ix], Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let mut tx = Transaction::new_unsigned(message);
 | 
			
		||||
    tx.try_sign(&config.signers, recent_blockhash)?;
 | 
			
		||||
    check_account_for_fee(
 | 
			
		||||
@@ -619,7 +518,7 @@ pub fn process_withdraw_from_nonce_account(
 | 
			
		||||
        destination_account_pubkey,
 | 
			
		||||
        lamports,
 | 
			
		||||
    );
 | 
			
		||||
    let message = Message::new_with_payer(&[ix], Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let message = Message::new(&[ix], Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let mut tx = Transaction::new_unsigned(message);
 | 
			
		||||
    tx.try_sign(&config.signers, recent_blockhash)?;
 | 
			
		||||
    check_account_for_fee(
 | 
			
		||||
@@ -638,9 +537,10 @@ mod tests {
 | 
			
		||||
    use crate::cli::{app, parse_command};
 | 
			
		||||
    use solana_sdk::{
 | 
			
		||||
        account::Account,
 | 
			
		||||
        account_utils::StateMut,
 | 
			
		||||
        fee_calculator::FeeCalculator,
 | 
			
		||||
        hash::hash,
 | 
			
		||||
        nonce::{self, State},
 | 
			
		||||
        nonce::{self, state::Versions, State},
 | 
			
		||||
        signature::{read_keypair_file, write_keypair, Keypair, Signer},
 | 
			
		||||
        system_program,
 | 
			
		||||
    };
 | 
			
		||||
@@ -657,6 +557,10 @@ mod tests {
 | 
			
		||||
        let default_keypair = Keypair::new();
 | 
			
		||||
        let (default_keypair_file, mut tmp_file) = make_tmp_file();
 | 
			
		||||
        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
 | 
			
		||||
        let default_signer = DefaultSigner {
 | 
			
		||||
            path: default_keypair_file.clone(),
 | 
			
		||||
            arg_name: String::new(),
 | 
			
		||||
        };
 | 
			
		||||
        let (keypair_file, mut tmp_file) = make_tmp_file();
 | 
			
		||||
        let nonce_account_keypair = Keypair::new();
 | 
			
		||||
        write_keypair(&nonce_account_keypair, tmp_file.as_file_mut()).unwrap();
 | 
			
		||||
@@ -675,12 +579,7 @@ mod tests {
 | 
			
		||||
            &Pubkey::default().to_string(),
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(
 | 
			
		||||
                &test_authorize_nonce_account,
 | 
			
		||||
                &default_keypair_file,
 | 
			
		||||
                &mut None
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap(),
 | 
			
		||||
            parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::AuthorizeNonceAccount {
 | 
			
		||||
                    nonce_account: nonce_account_pubkey,
 | 
			
		||||
@@ -701,12 +600,7 @@ mod tests {
 | 
			
		||||
            &authority_keypair_file,
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(
 | 
			
		||||
                &test_authorize_nonce_account,
 | 
			
		||||
                &default_keypair_file,
 | 
			
		||||
                &mut None
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap(),
 | 
			
		||||
            parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::AuthorizeNonceAccount {
 | 
			
		||||
                    nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
 | 
			
		||||
@@ -728,7 +622,7 @@ mod tests {
 | 
			
		||||
            "50",
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_create_nonce_account, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::CreateNonceAccount {
 | 
			
		||||
                    nonce_account: 1,
 | 
			
		||||
@@ -753,7 +647,7 @@ mod tests {
 | 
			
		||||
            &authority_keypair_file,
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_create_nonce_account, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::CreateNonceAccount {
 | 
			
		||||
                    nonce_account: 1,
 | 
			
		||||
@@ -775,7 +669,7 @@ mod tests {
 | 
			
		||||
            &nonce_account_string,
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_get_nonce, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_get_nonce, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::GetNonce(nonce_account_keypair.pubkey()),
 | 
			
		||||
                signers: vec![],
 | 
			
		||||
@@ -789,7 +683,7 @@ mod tests {
 | 
			
		||||
                .get_matches_from(vec!["test", "new-nonce", &keypair_file]);
 | 
			
		||||
        let nonce_account = read_keypair_file(&keypair_file).unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_new_nonce, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::NewNonce {
 | 
			
		||||
                    nonce_account: nonce_account.pubkey(),
 | 
			
		||||
@@ -809,7 +703,7 @@ mod tests {
 | 
			
		||||
        ]);
 | 
			
		||||
        let nonce_account = read_keypair_file(&keypair_file).unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_new_nonce, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::NewNonce {
 | 
			
		||||
                    nonce_account: nonce_account.pubkey(),
 | 
			
		||||
@@ -829,7 +723,7 @@ mod tests {
 | 
			
		||||
            &nonce_account_string,
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_show_nonce_account, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_show_nonce_account, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::ShowNonceAccount {
 | 
			
		||||
                    nonce_account_pubkey: nonce_account_keypair.pubkey(),
 | 
			
		||||
@@ -850,7 +744,7 @@ mod tests {
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(
 | 
			
		||||
                &test_withdraw_from_nonce_account,
 | 
			
		||||
                &default_keypair_file,
 | 
			
		||||
                &default_signer,
 | 
			
		||||
                &mut None
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap(),
 | 
			
		||||
@@ -878,7 +772,7 @@ mod tests {
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(
 | 
			
		||||
                &test_withdraw_from_nonce_account,
 | 
			
		||||
                &default_keypair_file,
 | 
			
		||||
                &default_signer,
 | 
			
		||||
                &mut None
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap(),
 | 
			
		||||
@@ -913,14 +807,14 @@ mod tests {
 | 
			
		||||
        if let CliError::InvalidNonce(err) =
 | 
			
		||||
            check_nonce_account(&invalid_owner.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
 | 
			
		||||
        {
 | 
			
		||||
            assert_eq!(err, CliNonceError::InvalidAccountOwner,);
 | 
			
		||||
            assert_eq!(err, Error::InvalidAccountOwner,);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let invalid_data = Account::new_data(1, &"invalid", &system_program::ID);
 | 
			
		||||
        if let CliError::InvalidNonce(err) =
 | 
			
		||||
            check_nonce_account(&invalid_data.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
 | 
			
		||||
        {
 | 
			
		||||
            assert_eq!(err, CliNonceError::InvalidAccountData,);
 | 
			
		||||
            assert_eq!(err, Error::InvalidAccountData,);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let data = Versions::new_current(State::Initialized(nonce::state::Data {
 | 
			
		||||
@@ -932,7 +826,7 @@ mod tests {
 | 
			
		||||
        if let CliError::InvalidNonce(err) =
 | 
			
		||||
            check_nonce_account(&invalid_hash.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
 | 
			
		||||
        {
 | 
			
		||||
            assert_eq!(err, CliNonceError::InvalidHash,);
 | 
			
		||||
            assert_eq!(err, Error::InvalidHash,);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let data = Versions::new_current(State::Initialized(nonce::state::Data {
 | 
			
		||||
@@ -944,7 +838,7 @@ mod tests {
 | 
			
		||||
        if let CliError::InvalidNonce(err) =
 | 
			
		||||
            check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
 | 
			
		||||
        {
 | 
			
		||||
            assert_eq!(err, CliNonceError::InvalidAuthority,);
 | 
			
		||||
            assert_eq!(err, Error::InvalidAuthority,);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let data = Versions::new_current(State::Uninitialized);
 | 
			
		||||
@@ -952,7 +846,7 @@ mod tests {
 | 
			
		||||
        if let CliError::InvalidNonce(err) =
 | 
			
		||||
            check_nonce_account(&invalid_state.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
 | 
			
		||||
        {
 | 
			
		||||
            assert_eq!(err, CliNonceError::InvalidStateForOperation,);
 | 
			
		||||
            assert_eq!(err, Error::InvalidStateForOperation,);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -964,14 +858,14 @@ mod tests {
 | 
			
		||||
        let system_account = Account::new(1, 0, &system_program::id());
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            account_identity_ok(&system_account),
 | 
			
		||||
            Err(CliNonceError::UnexpectedDataSize),
 | 
			
		||||
            Err(Error::UnexpectedDataSize),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let other_program = Pubkey::new(&[1u8; 32]);
 | 
			
		||||
        let other_account_no_data = Account::new(1, 0, &other_program);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            account_identity_ok(&other_account_no_data),
 | 
			
		||||
            Err(CliNonceError::InvalidAccountOwner),
 | 
			
		||||
            Err(Error::InvalidAccountOwner),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -996,7 +890,7 @@ mod tests {
 | 
			
		||||
        let wrong_data_size_account = Account::new(1, 1, &system_program::id());
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            state_from_account(&wrong_data_size_account),
 | 
			
		||||
            Err(CliNonceError::InvalidAccountData),
 | 
			
		||||
            Err(Error::InvalidAccountData),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1006,11 +900,11 @@ mod tests {
 | 
			
		||||
        let state = state_from_account(&nonce_account).unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            data_from_state(&state),
 | 
			
		||||
            Err(CliNonceError::InvalidStateForOperation)
 | 
			
		||||
            Err(Error::InvalidStateForOperation)
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            data_from_account(&nonce_account),
 | 
			
		||||
            Err(CliNonceError::InvalidStateForOperation)
 | 
			
		||||
            Err(Error::InvalidStateForOperation)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let data = nonce::state::Data {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,129 +0,0 @@
 | 
			
		||||
pub mod blockhash_query;
 | 
			
		||||
 | 
			
		||||
use crate::nonce;
 | 
			
		||||
use clap::{App, Arg, ArgMatches};
 | 
			
		||||
use serde_json::Value;
 | 
			
		||||
use solana_clap_utils::{
 | 
			
		||||
    input_parsers::{pubkey_of, value_of},
 | 
			
		||||
    input_validators::{is_hash, is_pubkey_sig},
 | 
			
		||||
    keypair::presigner_from_pubkey_sigs,
 | 
			
		||||
    offline::{BLOCKHASH_ARG, SIGNER_ARG, SIGN_ONLY_ARG},
 | 
			
		||||
};
 | 
			
		||||
use solana_client::rpc_client::RpcClient;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    fee_calculator::FeeCalculator,
 | 
			
		||||
    hash::Hash,
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    signature::{Presigner, Signature},
 | 
			
		||||
};
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
fn blockhash_arg<'a, 'b>() -> Arg<'a, 'b> {
 | 
			
		||||
    Arg::with_name(BLOCKHASH_ARG.name)
 | 
			
		||||
        .long(BLOCKHASH_ARG.long)
 | 
			
		||||
        .takes_value(true)
 | 
			
		||||
        .value_name("BLOCKHASH")
 | 
			
		||||
        .validator(is_hash)
 | 
			
		||||
        .help(BLOCKHASH_ARG.help)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn sign_only_arg<'a, 'b>() -> Arg<'a, 'b> {
 | 
			
		||||
    Arg::with_name(SIGN_ONLY_ARG.name)
 | 
			
		||||
        .long(SIGN_ONLY_ARG.long)
 | 
			
		||||
        .takes_value(false)
 | 
			
		||||
        .requires(BLOCKHASH_ARG.name)
 | 
			
		||||
        .help(SIGN_ONLY_ARG.help)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn signer_arg<'a, 'b>() -> Arg<'a, 'b> {
 | 
			
		||||
    Arg::with_name(SIGNER_ARG.name)
 | 
			
		||||
        .long(SIGNER_ARG.long)
 | 
			
		||||
        .takes_value(true)
 | 
			
		||||
        .value_name("PUBKEY=SIGNATURE")
 | 
			
		||||
        .validator(is_pubkey_sig)
 | 
			
		||||
        .requires(BLOCKHASH_ARG.name)
 | 
			
		||||
        .multiple(true)
 | 
			
		||||
        .help(SIGNER_ARG.help)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait OfflineArgs {
 | 
			
		||||
    fn offline_args(self) -> Self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl OfflineArgs for App<'_, '_> {
 | 
			
		||||
    fn offline_args(self) -> Self {
 | 
			
		||||
        self.arg(blockhash_arg())
 | 
			
		||||
            .arg(sign_only_arg())
 | 
			
		||||
            .arg(signer_arg())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct SignOnly {
 | 
			
		||||
    pub blockhash: Hash,
 | 
			
		||||
    pub present_signers: Vec<(Pubkey, Signature)>,
 | 
			
		||||
    pub absent_signers: Vec<Pubkey>,
 | 
			
		||||
    pub bad_signers: Vec<Pubkey>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SignOnly {
 | 
			
		||||
    pub fn has_all_signers(&self) -> bool {
 | 
			
		||||
        self.absent_signers.is_empty() && self.bad_signers.is_empty()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn presigner_of(&self, pubkey: &Pubkey) -> Option<Presigner> {
 | 
			
		||||
        presigner_from_pubkey_sigs(pubkey, &self.present_signers)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
 | 
			
		||||
    let object: Value = serde_json::from_str(&reply).unwrap();
 | 
			
		||||
    let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
 | 
			
		||||
    let blockhash = blockhash_str.parse::<Hash>().unwrap();
 | 
			
		||||
    let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
 | 
			
		||||
    let signer_strings = object.get("signers");
 | 
			
		||||
    if let Some(sig_strings) = signer_strings {
 | 
			
		||||
        present_signers = sig_strings
 | 
			
		||||
            .as_array()
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|signer_string| {
 | 
			
		||||
                let mut signer = signer_string.as_str().unwrap().split('=');
 | 
			
		||||
                let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
 | 
			
		||||
                let sig = Signature::from_str(signer.next().unwrap()).unwrap();
 | 
			
		||||
                (key, sig)
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
    }
 | 
			
		||||
    let mut absent_signers: Vec<Pubkey> = Vec::new();
 | 
			
		||||
    let signer_strings = object.get("absent");
 | 
			
		||||
    if let Some(sig_strings) = signer_strings {
 | 
			
		||||
        absent_signers = sig_strings
 | 
			
		||||
            .as_array()
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|val| {
 | 
			
		||||
                let s = val.as_str().unwrap();
 | 
			
		||||
                Pubkey::from_str(s).unwrap()
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
    }
 | 
			
		||||
    let mut bad_signers: Vec<Pubkey> = Vec::new();
 | 
			
		||||
    let signer_strings = object.get("badSig");
 | 
			
		||||
    if let Some(sig_strings) = signer_strings {
 | 
			
		||||
        bad_signers = sig_strings
 | 
			
		||||
            .as_array()
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|val| {
 | 
			
		||||
                let s = val.as_str().unwrap();
 | 
			
		||||
                Pubkey::from_str(s).unwrap()
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
    }
 | 
			
		||||
    SignOnly {
 | 
			
		||||
        blockhash,
 | 
			
		||||
        present_signers,
 | 
			
		||||
        absent_signers,
 | 
			
		||||
        bad_signers,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										499
									
								
								cli/src/stake.rs
									
									
									
									
									
								
							
							
						
						
									
										499
									
								
								cli/src/stake.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,19 +1,20 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
 | 
			
		||||
    cli_output::{CliValidatorInfo, CliValidatorInfoVec},
 | 
			
		||||
    spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
 | 
			
		||||
};
 | 
			
		||||
use bincode::deserialize;
 | 
			
		||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
 | 
			
		||||
use reqwest::blocking::Client;
 | 
			
		||||
use serde_derive::{Deserialize, Serialize};
 | 
			
		||||
use serde_json::{Map, Value};
 | 
			
		||||
 | 
			
		||||
use solana_account_decoder::validator_info::{
 | 
			
		||||
    self, ValidatorInfo, MAX_LONG_FIELD_LENGTH, MAX_SHORT_FIELD_LENGTH,
 | 
			
		||||
};
 | 
			
		||||
use solana_clap_utils::{
 | 
			
		||||
    input_parsers::pubkey_of,
 | 
			
		||||
    input_validators::{is_pubkey, is_url},
 | 
			
		||||
    keypair::signer_from_path,
 | 
			
		||||
    keypair::DefaultSigner,
 | 
			
		||||
};
 | 
			
		||||
use solana_cli_output::{CliValidatorInfo, CliValidatorInfoVec};
 | 
			
		||||
use solana_client::rpc_client::RpcClient;
 | 
			
		||||
use solana_config_program::{config_instruction, get_config_data, ConfigKeys, ConfigState};
 | 
			
		||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
 | 
			
		||||
@@ -27,23 +28,6 @@ use solana_sdk::{
 | 
			
		||||
};
 | 
			
		||||
use std::{error, sync::Arc};
 | 
			
		||||
 | 
			
		||||
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
 | 
			
		||||
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
 | 
			
		||||
pub const MAX_VALIDATOR_INFO: u64 = 576;
 | 
			
		||||
 | 
			
		||||
solana_sdk::declare_id!("Va1idator1nfo111111111111111111111111111111");
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize, PartialEq, Serialize, Default)]
 | 
			
		||||
pub struct ValidatorInfo {
 | 
			
		||||
    info: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ConfigState for ValidatorInfo {
 | 
			
		||||
    fn max_space() -> u64 {
 | 
			
		||||
        MAX_VALIDATOR_INFO
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return an error if a validator details are longer than the max length.
 | 
			
		||||
pub fn check_details_length(string: String) -> Result<(), String> {
 | 
			
		||||
    if string.len() > MAX_LONG_FIELD_LENGTH {
 | 
			
		||||
@@ -228,7 +212,7 @@ impl ValidatorInfoSubCommands for App<'_, '_> {
 | 
			
		||||
 | 
			
		||||
pub fn parse_validator_info_command(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer_path: &str,
 | 
			
		||||
    default_signer: &DefaultSigner,
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
    let info_pubkey = pubkey_of(matches, "info_pubkey");
 | 
			
		||||
@@ -240,12 +224,7 @@ pub fn parse_validator_info_command(
 | 
			
		||||
            force_keybase: matches.is_present("force"),
 | 
			
		||||
            info_pubkey,
 | 
			
		||||
        },
 | 
			
		||||
        signers: vec![signer_from_path(
 | 
			
		||||
            matches,
 | 
			
		||||
            default_signer_path,
 | 
			
		||||
            "keypair",
 | 
			
		||||
            wallet_manager,
 | 
			
		||||
        )?],
 | 
			
		||||
        signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -287,10 +266,12 @@ pub fn process_set_validator_info(
 | 
			
		||||
    let all_config = rpc_client.get_program_accounts(&solana_config_program::id())?;
 | 
			
		||||
    let existing_account = all_config
 | 
			
		||||
        .iter()
 | 
			
		||||
        .filter(|(_, account)| {
 | 
			
		||||
            let key_list: ConfigKeys = deserialize(&account.data).map_err(|_| false).unwrap();
 | 
			
		||||
            key_list.keys.contains(&(id(), false))
 | 
			
		||||
        })
 | 
			
		||||
        .filter(
 | 
			
		||||
            |(_, account)| match deserialize::<ConfigKeys>(&account.data) {
 | 
			
		||||
                Ok(key_list) => key_list.keys.contains(&(validator_info::id(), false)),
 | 
			
		||||
                Err(_) => false,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        .find(|(pubkey, account)| {
 | 
			
		||||
            let (validator_pubkey, _) = parse_validator_info(&pubkey, &account).unwrap();
 | 
			
		||||
            validator_pubkey == config.signers[0].pubkey()
 | 
			
		||||
@@ -328,7 +309,10 @@ pub fn process_set_validator_info(
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let build_message = |lamports| {
 | 
			
		||||
        let keys = vec![(id(), false), (config.signers[0].pubkey(), true)];
 | 
			
		||||
        let keys = vec![
 | 
			
		||||
            (validator_info::id(), false),
 | 
			
		||||
            (config.signers[0].pubkey(), true),
 | 
			
		||||
        ];
 | 
			
		||||
        if balance == 0 {
 | 
			
		||||
            println!(
 | 
			
		||||
                "Publishing info for Validator {:?}",
 | 
			
		||||
@@ -346,7 +330,7 @@ pub fn process_set_validator_info(
 | 
			
		||||
                keys,
 | 
			
		||||
                &validator_info,
 | 
			
		||||
            )]);
 | 
			
		||||
            Message::new(&instructions)
 | 
			
		||||
            Message::new(&instructions, Some(&config.signers[0].pubkey()))
 | 
			
		||||
        } else {
 | 
			
		||||
            println!(
 | 
			
		||||
                "Updating Validator {:?} info at: {:?}",
 | 
			
		||||
@@ -359,7 +343,7 @@ pub fn process_set_validator_info(
 | 
			
		||||
                keys,
 | 
			
		||||
                &validator_info,
 | 
			
		||||
            )];
 | 
			
		||||
            Message::new_with_payer(&instructions, Some(&config.signers[0].pubkey()))
 | 
			
		||||
            Message::new(&instructions, Some(&config.signers[0].pubkey()))
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -397,10 +381,10 @@ pub fn process_get_validator_info(
 | 
			
		||||
        all_config
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .filter(|(_, validator_info_account)| {
 | 
			
		||||
                let key_list: ConfigKeys = deserialize(&validator_info_account.data)
 | 
			
		||||
                    .map_err(|_| false)
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                key_list.keys.contains(&(id(), false))
 | 
			
		||||
                match deserialize::<ConfigKeys>(&validator_info_account.data) {
 | 
			
		||||
                    Ok(key_list) => key_list.keys.contains(&(validator_info::id(), false)),
 | 
			
		||||
                    Err(_) => false,
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .collect()
 | 
			
		||||
    };
 | 
			
		||||
@@ -502,7 +486,7 @@ mod tests {
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_parse_validator_info() {
 | 
			
		||||
        let pubkey = Pubkey::new_rand();
 | 
			
		||||
        let keys = vec![(id(), false), (pubkey, true)];
 | 
			
		||||
        let keys = vec![(validator_info::id(), false), (pubkey, true)];
 | 
			
		||||
        let config = ConfigKeys { keys };
 | 
			
		||||
 | 
			
		||||
        let mut info = Map::new();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										249
									
								
								cli/src/vote.rs
									
									
									
									
									
								
							
							
						
						
									
										249
									
								
								cli/src/vote.rs
									
									
									
									
									
								
							@@ -1,10 +1,9 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    checks::{check_account_for_fee, check_unique_pubkeys},
 | 
			
		||||
    cli::{
 | 
			
		||||
        generate_unique_signers, log_instruction_custom_error, CliCommand, CliCommandInfo,
 | 
			
		||||
        CliConfig, CliError, ProcessResult, SignerIndex,
 | 
			
		||||
        log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
 | 
			
		||||
        ProcessResult,
 | 
			
		||||
    },
 | 
			
		||||
    cli_output::{CliEpochVotingHistory, CliLockout, CliVoteAccount},
 | 
			
		||||
    spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
 | 
			
		||||
};
 | 
			
		||||
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
 | 
			
		||||
@@ -12,12 +11,15 @@ use solana_clap_utils::{
 | 
			
		||||
    commitment::{commitment_arg, COMMITMENT_ARG},
 | 
			
		||||
    input_parsers::*,
 | 
			
		||||
    input_validators::*,
 | 
			
		||||
    keypair::{DefaultSigner, SignerIndex},
 | 
			
		||||
};
 | 
			
		||||
use solana_cli_output::{CliEpochVotingHistory, CliLockout, CliVoteAccount};
 | 
			
		||||
use solana_client::rpc_client::RpcClient;
 | 
			
		||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    account::Account, commitment_config::CommitmentConfig, message::Message, pubkey::Pubkey,
 | 
			
		||||
    system_instruction::SystemError, transaction::Transaction,
 | 
			
		||||
    account::Account, commitment_config::CommitmentConfig, message::Message,
 | 
			
		||||
    native_token::lamports_to_sol, pubkey::Pubkey, system_instruction::SystemError,
 | 
			
		||||
    transaction::Transaction,
 | 
			
		||||
};
 | 
			
		||||
use solana_vote_program::{
 | 
			
		||||
    vote_instruction::{self, withdraw, VoteError},
 | 
			
		||||
@@ -161,6 +163,35 @@ impl VoteSubCommands for App<'_, '_> {
 | 
			
		||||
                        .help("Authorized withdrawer keypair"),
 | 
			
		||||
                )
 | 
			
		||||
        )
 | 
			
		||||
        .subcommand(
 | 
			
		||||
            SubCommand::with_name("vote-update-commission")
 | 
			
		||||
                .about("Update the vote account's commission")
 | 
			
		||||
                .arg(
 | 
			
		||||
                    pubkey!(Arg::with_name("vote_account_pubkey")
 | 
			
		||||
                        .index(1)
 | 
			
		||||
                        .value_name("VOTE_ACCOUNT_ADDRESS")
 | 
			
		||||
                        .required(true),
 | 
			
		||||
                        "Vote account to update. "),
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::with_name("commission")
 | 
			
		||||
                        .index(2)
 | 
			
		||||
                        .value_name("PERCENTAGE")
 | 
			
		||||
                        .takes_value(true)
 | 
			
		||||
                        .required(true)
 | 
			
		||||
                        .validator(is_valid_percentage)
 | 
			
		||||
                        .help("The new commission")
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::with_name("authorized_withdrawer")
 | 
			
		||||
                        .index(3)
 | 
			
		||||
                        .value_name("AUTHORIZED_KEYPAIR")
 | 
			
		||||
                        .takes_value(true)
 | 
			
		||||
                        .required(true)
 | 
			
		||||
                        .validator(is_valid_signer)
 | 
			
		||||
                        .help("Authorized withdrawer keypair"),
 | 
			
		||||
                )
 | 
			
		||||
        )
 | 
			
		||||
        .subcommand(
 | 
			
		||||
            SubCommand::with_name("vote-account")
 | 
			
		||||
                .about("Show the contents of a vote account")
 | 
			
		||||
@@ -203,8 +234,8 @@ impl VoteSubCommands for App<'_, '_> {
 | 
			
		||||
                        .value_name("AMOUNT")
 | 
			
		||||
                        .takes_value(true)
 | 
			
		||||
                        .required(true)
 | 
			
		||||
                        .validator(is_amount)
 | 
			
		||||
                        .help("The amount to withdraw, in SOL"),
 | 
			
		||||
                        .validator(is_amount_or_all)
 | 
			
		||||
                        .help("The amount to withdraw, in SOL; accepts keyword ALL"),
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::with_name("authorized_withdrawer")
 | 
			
		||||
@@ -220,10 +251,10 @@ impl VoteSubCommands for App<'_, '_> {
 | 
			
		||||
 | 
			
		||||
pub fn parse_create_vote_account(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer_path: &str,
 | 
			
		||||
    default_signer: &DefaultSigner,
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
    let (vote_account, _) = signer_of(matches, "vote_account", wallet_manager)?;
 | 
			
		||||
    let (vote_account, vote_account_pubkey) = signer_of(matches, "vote_account", wallet_manager)?;
 | 
			
		||||
    let seed = matches.value_of("seed").map(|s| s.to_string());
 | 
			
		||||
    let (identity_account, identity_pubkey) =
 | 
			
		||||
        signer_of(matches, "identity_account", wallet_manager)?;
 | 
			
		||||
@@ -232,15 +263,15 @@ pub fn parse_create_vote_account(
 | 
			
		||||
    let authorized_withdrawer = pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?;
 | 
			
		||||
 | 
			
		||||
    let payer_provided = None;
 | 
			
		||||
    let signer_info = generate_unique_signers(
 | 
			
		||||
    let signer_info = default_signer.generate_unique_signers(
 | 
			
		||||
        vec![payer_provided, vote_account, identity_account],
 | 
			
		||||
        matches,
 | 
			
		||||
        default_signer_path,
 | 
			
		||||
        wallet_manager,
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
    Ok(CliCommandInfo {
 | 
			
		||||
        command: CliCommand::CreateVoteAccount {
 | 
			
		||||
            vote_account: signer_info.index_of(vote_account_pubkey).unwrap(),
 | 
			
		||||
            seed,
 | 
			
		||||
            identity_account: signer_info.index_of(identity_pubkey).unwrap(),
 | 
			
		||||
            authorized_voter,
 | 
			
		||||
@@ -253,7 +284,7 @@ pub fn parse_create_vote_account(
 | 
			
		||||
 | 
			
		||||
pub fn parse_vote_authorize(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer_path: &str,
 | 
			
		||||
    default_signer: &DefaultSigner,
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
    vote_authorize: VoteAuthorize,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
@@ -264,10 +295,9 @@ pub fn parse_vote_authorize(
 | 
			
		||||
    let (authorized, _) = signer_of(matches, "authorized", wallet_manager)?;
 | 
			
		||||
 | 
			
		||||
    let payer_provided = None;
 | 
			
		||||
    let signer_info = generate_unique_signers(
 | 
			
		||||
    let signer_info = default_signer.generate_unique_signers(
 | 
			
		||||
        vec![payer_provided, authorized],
 | 
			
		||||
        matches,
 | 
			
		||||
        default_signer_path,
 | 
			
		||||
        wallet_manager,
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
@@ -283,20 +313,20 @@ pub fn parse_vote_authorize(
 | 
			
		||||
 | 
			
		||||
pub fn parse_vote_update_validator(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer_path: &str,
 | 
			
		||||
    default_signer: &DefaultSigner,
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
    let vote_account_pubkey =
 | 
			
		||||
        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
 | 
			
		||||
    let (new_identity_account, new_identity_pubkey) =
 | 
			
		||||
        signer_of(matches, "new_identity_account", wallet_manager)?;
 | 
			
		||||
    let (authorized_withdrawer, _) = signer_of(matches, "authorized_withdrawer", wallet_manager)?;
 | 
			
		||||
    let (authorized_withdrawer, authorized_withdrawer_pubkey) =
 | 
			
		||||
        signer_of(matches, "authorized_withdrawer", wallet_manager)?;
 | 
			
		||||
 | 
			
		||||
    let payer_provided = None;
 | 
			
		||||
    let signer_info = generate_unique_signers(
 | 
			
		||||
    let signer_info = default_signer.generate_unique_signers(
 | 
			
		||||
        vec![payer_provided, authorized_withdrawer, new_identity_account],
 | 
			
		||||
        matches,
 | 
			
		||||
        default_signer_path,
 | 
			
		||||
        wallet_manager,
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
@@ -304,6 +334,35 @@ pub fn parse_vote_update_validator(
 | 
			
		||||
        command: CliCommand::VoteUpdateValidator {
 | 
			
		||||
            vote_account_pubkey,
 | 
			
		||||
            new_identity_account: signer_info.index_of(new_identity_pubkey).unwrap(),
 | 
			
		||||
            withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
 | 
			
		||||
        },
 | 
			
		||||
        signers: signer_info.signers,
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse_vote_update_commission(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer: &DefaultSigner,
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
    let vote_account_pubkey =
 | 
			
		||||
        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
 | 
			
		||||
    let (authorized_withdrawer, authorized_withdrawer_pubkey) =
 | 
			
		||||
        signer_of(matches, "authorized_withdrawer", wallet_manager)?;
 | 
			
		||||
    let commission = value_t_or_exit!(matches, "commission", u8);
 | 
			
		||||
 | 
			
		||||
    let payer_provided = None;
 | 
			
		||||
    let signer_info = default_signer.generate_unique_signers(
 | 
			
		||||
        vec![payer_provided, authorized_withdrawer],
 | 
			
		||||
        matches,
 | 
			
		||||
        wallet_manager,
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
    Ok(CliCommandInfo {
 | 
			
		||||
        command: CliCommand::VoteUpdateCommission {
 | 
			
		||||
            vote_account_pubkey,
 | 
			
		||||
            commission,
 | 
			
		||||
            withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
 | 
			
		||||
        },
 | 
			
		||||
        signers: signer_info.signers,
 | 
			
		||||
    })
 | 
			
		||||
@@ -329,22 +388,22 @@ pub fn parse_vote_get_account_command(
 | 
			
		||||
 | 
			
		||||
pub fn parse_withdraw_from_vote_account(
 | 
			
		||||
    matches: &ArgMatches<'_>,
 | 
			
		||||
    default_signer_path: &str,
 | 
			
		||||
    default_signer: &DefaultSigner,
 | 
			
		||||
    wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
 | 
			
		||||
) -> Result<CliCommandInfo, CliError> {
 | 
			
		||||
    let vote_account_pubkey =
 | 
			
		||||
        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
 | 
			
		||||
    let destination_account_pubkey =
 | 
			
		||||
        pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
 | 
			
		||||
    let lamports = lamports_of_sol(matches, "amount").unwrap();
 | 
			
		||||
    let withdraw_amount = SpendAmount::new_from_matches(matches, "amount");
 | 
			
		||||
 | 
			
		||||
    let (withdraw_authority, withdraw_authority_pubkey) =
 | 
			
		||||
        signer_of(matches, "authorized_withdrawer", wallet_manager)?;
 | 
			
		||||
 | 
			
		||||
    let payer_provided = None;
 | 
			
		||||
    let signer_info = generate_unique_signers(
 | 
			
		||||
    let signer_info = default_signer.generate_unique_signers(
 | 
			
		||||
        vec![payer_provided, withdraw_authority],
 | 
			
		||||
        matches,
 | 
			
		||||
        default_signer_path,
 | 
			
		||||
        wallet_manager,
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
@@ -353,7 +412,7 @@ pub fn parse_withdraw_from_vote_account(
 | 
			
		||||
            vote_account_pubkey,
 | 
			
		||||
            destination_account_pubkey,
 | 
			
		||||
            withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
 | 
			
		||||
            lamports,
 | 
			
		||||
            withdraw_amount,
 | 
			
		||||
        },
 | 
			
		||||
        signers: signer_info.signers,
 | 
			
		||||
    })
 | 
			
		||||
@@ -362,13 +421,14 @@ pub fn parse_withdraw_from_vote_account(
 | 
			
		||||
pub fn process_create_vote_account(
 | 
			
		||||
    rpc_client: &RpcClient,
 | 
			
		||||
    config: &CliConfig,
 | 
			
		||||
    vote_account: SignerIndex,
 | 
			
		||||
    seed: &Option<String>,
 | 
			
		||||
    identity_account: SignerIndex,
 | 
			
		||||
    authorized_voter: &Option<Pubkey>,
 | 
			
		||||
    authorized_withdrawer: &Option<Pubkey>,
 | 
			
		||||
    commission: u8,
 | 
			
		||||
) -> ProcessResult {
 | 
			
		||||
    let vote_account = config.signers[1];
 | 
			
		||||
    let vote_account = config.signers[vote_account];
 | 
			
		||||
    let vote_account_pubkey = vote_account.pubkey();
 | 
			
		||||
    let vote_account_address = if let Some(seed) = seed {
 | 
			
		||||
        Pubkey::create_with_seed(&vote_account_pubkey, &seed, &solana_vote_program::id())?
 | 
			
		||||
@@ -417,7 +477,7 @@ pub fn process_create_vote_account(
 | 
			
		||||
                lamports,
 | 
			
		||||
            )
 | 
			
		||||
        };
 | 
			
		||||
        Message::new(&ixs)
 | 
			
		||||
        Message::new(&ixs, Some(&config.signers[0].pubkey()))
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if let Ok(vote_account) = rpc_client.get_account(&vote_account_address) {
 | 
			
		||||
@@ -475,7 +535,7 @@ pub fn process_vote_authorize(
 | 
			
		||||
        vote_authorize,        // vote or withdraw
 | 
			
		||||
    )];
 | 
			
		||||
 | 
			
		||||
    let message = Message::new_with_payer(&ixs, Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let mut tx = Transaction::new_unsigned(message);
 | 
			
		||||
    tx.try_sign(&config.signers, recent_blockhash)?;
 | 
			
		||||
    check_account_for_fee(
 | 
			
		||||
@@ -493,8 +553,9 @@ pub fn process_vote_update_validator(
 | 
			
		||||
    config: &CliConfig,
 | 
			
		||||
    vote_account_pubkey: &Pubkey,
 | 
			
		||||
    new_identity_account: SignerIndex,
 | 
			
		||||
    withdraw_authority: SignerIndex,
 | 
			
		||||
) -> ProcessResult {
 | 
			
		||||
    let authorized_withdrawer = config.signers[1];
 | 
			
		||||
    let authorized_withdrawer = config.signers[withdraw_authority];
 | 
			
		||||
    let new_identity_account = config.signers[new_identity_account];
 | 
			
		||||
    let new_identity_pubkey = new_identity_account.pubkey();
 | 
			
		||||
    check_unique_pubkeys(
 | 
			
		||||
@@ -508,7 +569,35 @@ pub fn process_vote_update_validator(
 | 
			
		||||
        &new_identity_pubkey,
 | 
			
		||||
    )];
 | 
			
		||||
 | 
			
		||||
    let message = Message::new_with_payer(&ixs, Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let mut tx = Transaction::new_unsigned(message);
 | 
			
		||||
    tx.try_sign(&config.signers, recent_blockhash)?;
 | 
			
		||||
    check_account_for_fee(
 | 
			
		||||
        rpc_client,
 | 
			
		||||
        &config.signers[0].pubkey(),
 | 
			
		||||
        &fee_calculator,
 | 
			
		||||
        &tx.message,
 | 
			
		||||
    )?;
 | 
			
		||||
    let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
 | 
			
		||||
    log_instruction_custom_error::<VoteError>(result, &config)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn process_vote_update_commission(
 | 
			
		||||
    rpc_client: &RpcClient,
 | 
			
		||||
    config: &CliConfig,
 | 
			
		||||
    vote_account_pubkey: &Pubkey,
 | 
			
		||||
    commission: u8,
 | 
			
		||||
    withdraw_authority: SignerIndex,
 | 
			
		||||
) -> ProcessResult {
 | 
			
		||||
    let authorized_withdrawer = config.signers[withdraw_authority];
 | 
			
		||||
    let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
 | 
			
		||||
    let ixs = vec![vote_instruction::update_commission(
 | 
			
		||||
        vote_account_pubkey,
 | 
			
		||||
        &authorized_withdrawer.pubkey(),
 | 
			
		||||
        commission,
 | 
			
		||||
    )];
 | 
			
		||||
 | 
			
		||||
    let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let mut tx = Transaction::new_unsigned(message);
 | 
			
		||||
    tx.try_sign(&config.signers, recent_blockhash)?;
 | 
			
		||||
    check_account_for_fee(
 | 
			
		||||
@@ -600,12 +689,28 @@ pub fn process_withdraw_from_vote_account(
 | 
			
		||||
    config: &CliConfig,
 | 
			
		||||
    vote_account_pubkey: &Pubkey,
 | 
			
		||||
    withdraw_authority: SignerIndex,
 | 
			
		||||
    lamports: u64,
 | 
			
		||||
    withdraw_amount: SpendAmount,
 | 
			
		||||
    destination_account_pubkey: &Pubkey,
 | 
			
		||||
) -> ProcessResult {
 | 
			
		||||
    let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
 | 
			
		||||
    let withdraw_authority = config.signers[withdraw_authority];
 | 
			
		||||
 | 
			
		||||
    let current_balance = rpc_client.get_balance(&vote_account_pubkey)?;
 | 
			
		||||
    let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(VoteState::size_of())?;
 | 
			
		||||
 | 
			
		||||
    let lamports = match withdraw_amount {
 | 
			
		||||
        SpendAmount::All => current_balance.saturating_sub(minimum_balance),
 | 
			
		||||
        SpendAmount::Some(withdraw_amount) => {
 | 
			
		||||
            if current_balance.saturating_sub(withdraw_amount) < minimum_balance {
 | 
			
		||||
                return Err(CliError::BadParameter(format!(
 | 
			
		||||
                    "Withdraw amount too large. The vote account balance must be at least {} SOL to remain rent exempt", lamports_to_sol(minimum_balance)
 | 
			
		||||
                ))
 | 
			
		||||
                .into());
 | 
			
		||||
            }
 | 
			
		||||
            withdraw_amount
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let ix = withdraw(
 | 
			
		||||
        vote_account_pubkey,
 | 
			
		||||
        &withdraw_authority.pubkey(),
 | 
			
		||||
@@ -613,7 +718,7 @@ pub fn process_withdraw_from_vote_account(
 | 
			
		||||
        destination_account_pubkey,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let message = Message::new_with_payer(&[ix], Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let message = Message::new(&[ix], Some(&config.signers[0].pubkey()));
 | 
			
		||||
    let mut transaction = Transaction::new_unsigned(message);
 | 
			
		||||
    transaction.try_sign(&config.signers, recent_blockhash)?;
 | 
			
		||||
    check_account_for_fee(
 | 
			
		||||
@@ -651,6 +756,10 @@ mod tests {
 | 
			
		||||
        let default_keypair = Keypair::new();
 | 
			
		||||
        let (default_keypair_file, mut tmp_file) = make_tmp_file();
 | 
			
		||||
        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
 | 
			
		||||
        let default_signer = DefaultSigner {
 | 
			
		||||
            path: default_keypair_file.clone(),
 | 
			
		||||
            arg_name: String::new(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let test_authorize_voter = test_commands.clone().get_matches_from(vec![
 | 
			
		||||
            "test",
 | 
			
		||||
@@ -660,7 +769,7 @@ mod tests {
 | 
			
		||||
            &pubkey2_string,
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_authorize_voter, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::VoteAuthorize {
 | 
			
		||||
                    vote_account_pubkey: pubkey,
 | 
			
		||||
@@ -683,7 +792,7 @@ mod tests {
 | 
			
		||||
            &pubkey2_string,
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_authorize_voter, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::VoteAuthorize {
 | 
			
		||||
                    vote_account_pubkey: pubkey,
 | 
			
		||||
@@ -713,9 +822,10 @@ mod tests {
 | 
			
		||||
            "10",
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_create_vote_account, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::CreateVoteAccount {
 | 
			
		||||
                    vote_account: 1,
 | 
			
		||||
                    seed: None,
 | 
			
		||||
                    identity_account: 2,
 | 
			
		||||
                    authorized_voter: None,
 | 
			
		||||
@@ -741,9 +851,10 @@ mod tests {
 | 
			
		||||
            &identity_keypair_file,
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_create_vote_account2, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::CreateVoteAccount {
 | 
			
		||||
                    vote_account: 1,
 | 
			
		||||
                    seed: None,
 | 
			
		||||
                    identity_account: 2,
 | 
			
		||||
                    authorized_voter: None,
 | 
			
		||||
@@ -773,9 +884,10 @@ mod tests {
 | 
			
		||||
            &authed.to_string(),
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_create_vote_account3, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_create_vote_account3, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::CreateVoteAccount {
 | 
			
		||||
                    vote_account: 1,
 | 
			
		||||
                    seed: None,
 | 
			
		||||
                    identity_account: 2,
 | 
			
		||||
                    authorized_voter: Some(authed),
 | 
			
		||||
@@ -803,9 +915,10 @@ mod tests {
 | 
			
		||||
            &authed.to_string(),
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_create_vote_account4, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::CreateVoteAccount {
 | 
			
		||||
                    vote_account: 1,
 | 
			
		||||
                    seed: None,
 | 
			
		||||
                    identity_account: 2,
 | 
			
		||||
                    authorized_voter: None,
 | 
			
		||||
@@ -828,11 +941,12 @@ mod tests {
 | 
			
		||||
            &keypair_file,
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_update_validator, &default_keypair_file, &mut None).unwrap(),
 | 
			
		||||
            parse_command(&test_update_validator, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::VoteUpdateValidator {
 | 
			
		||||
                    vote_account_pubkey: pubkey,
 | 
			
		||||
                    new_identity_account: 2,
 | 
			
		||||
                    withdraw_authority: 1,
 | 
			
		||||
                },
 | 
			
		||||
                signers: vec![
 | 
			
		||||
                    read_keypair_file(&default_keypair_file).unwrap().into(),
 | 
			
		||||
@@ -842,6 +956,28 @@ mod tests {
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let test_update_commission = test_commands.clone().get_matches_from(vec![
 | 
			
		||||
            "test",
 | 
			
		||||
            "vote-update-commission",
 | 
			
		||||
            &pubkey_string,
 | 
			
		||||
            "42",
 | 
			
		||||
            &keypair_file,
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_update_commission, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::VoteUpdateCommission {
 | 
			
		||||
                    vote_account_pubkey: pubkey,
 | 
			
		||||
                    commission: 42,
 | 
			
		||||
                    withdraw_authority: 1,
 | 
			
		||||
                },
 | 
			
		||||
                signers: vec![
 | 
			
		||||
                    read_keypair_file(&default_keypair_file).unwrap().into(),
 | 
			
		||||
                    Box::new(read_keypair_file(&keypair_file).unwrap()),
 | 
			
		||||
                ],
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Test WithdrawFromVoteAccount subcommand
 | 
			
		||||
        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
 | 
			
		||||
            "test",
 | 
			
		||||
@@ -851,18 +987,34 @@ mod tests {
 | 
			
		||||
            "42",
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(
 | 
			
		||||
                &test_withdraw_from_vote_account,
 | 
			
		||||
                &default_keypair_file,
 | 
			
		||||
                &mut None
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap(),
 | 
			
		||||
            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::WithdrawFromVoteAccount {
 | 
			
		||||
                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
 | 
			
		||||
                    destination_account_pubkey: pubkey,
 | 
			
		||||
                    withdraw_authority: 0,
 | 
			
		||||
                    lamports: 42_000_000_000
 | 
			
		||||
                    withdraw_amount: SpendAmount::Some(42_000_000_000),
 | 
			
		||||
                },
 | 
			
		||||
                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Test WithdrawFromVoteAccount subcommand
 | 
			
		||||
        let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
 | 
			
		||||
            "test",
 | 
			
		||||
            "withdraw-from-vote-account",
 | 
			
		||||
            &keypair_file,
 | 
			
		||||
            &pubkey_string,
 | 
			
		||||
            "ALL",
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::WithdrawFromVoteAccount {
 | 
			
		||||
                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
 | 
			
		||||
                    destination_account_pubkey: pubkey,
 | 
			
		||||
                    withdraw_authority: 0,
 | 
			
		||||
                    withdraw_amount: SpendAmount::All,
 | 
			
		||||
                },
 | 
			
		||||
                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
 | 
			
		||||
            }
 | 
			
		||||
@@ -882,18 +1034,13 @@ mod tests {
 | 
			
		||||
            &withdraw_authority_file,
 | 
			
		||||
        ]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            parse_command(
 | 
			
		||||
                &test_withdraw_from_vote_account,
 | 
			
		||||
                &default_keypair_file,
 | 
			
		||||
                &mut None
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap(),
 | 
			
		||||
            parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
 | 
			
		||||
            CliCommandInfo {
 | 
			
		||||
                command: CliCommand::WithdrawFromVoteAccount {
 | 
			
		||||
                    vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
 | 
			
		||||
                    destination_account_pubkey: pubkey,
 | 
			
		||||
                    withdraw_authority: 1,
 | 
			
		||||
                    lamports: 42_000_000_000
 | 
			
		||||
                    withdraw_amount: SpendAmount::Some(42_000_000_000),
 | 
			
		||||
                },
 | 
			
		||||
                signers: vec![
 | 
			
		||||
                    read_keypair_file(&default_keypair_file).unwrap().into(),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,14 @@
 | 
			
		||||
use solana_cli::test_utils::check_balance;
 | 
			
		||||
use solana_cli::{
 | 
			
		||||
    cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
 | 
			
		||||
    cli_output::OutputFormat,
 | 
			
		||||
    nonce,
 | 
			
		||||
    offline::{
 | 
			
		||||
        blockhash_query::{self, BlockhashQuery},
 | 
			
		||||
        parse_sign_only_reply_string,
 | 
			
		||||
    },
 | 
			
		||||
    spend_utils::SpendAmount,
 | 
			
		||||
};
 | 
			
		||||
use solana_client::rpc_client::RpcClient;
 | 
			
		||||
use solana_cli_output::{parse_sign_only_reply_string, OutputFormat};
 | 
			
		||||
use solana_client::{
 | 
			
		||||
    blockhash_query::{self, BlockhashQuery},
 | 
			
		||||
    nonce_utils,
 | 
			
		||||
    rpc_client::RpcClient,
 | 
			
		||||
};
 | 
			
		||||
use solana_core::contact_info::ContactInfo;
 | 
			
		||||
use solana_core::validator::{TestValidator, TestValidatorOptions};
 | 
			
		||||
use solana_faucet::faucet::run_local_faucet;
 | 
			
		||||
@@ -299,8 +298,8 @@ fn test_create_account_with_seed() {
 | 
			
		||||
    check_balance(0, &rpc_client, &to_address);
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_address)
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_address)
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,15 +3,14 @@ use serde_json::Value;
 | 
			
		||||
use solana_cli::test_utils::check_balance;
 | 
			
		||||
use solana_cli::{
 | 
			
		||||
    cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig, PayCommand},
 | 
			
		||||
    cli_output::OutputFormat,
 | 
			
		||||
    nonce,
 | 
			
		||||
    offline::{
 | 
			
		||||
        blockhash_query::{self, BlockhashQuery},
 | 
			
		||||
        parse_sign_only_reply_string,
 | 
			
		||||
    },
 | 
			
		||||
    spend_utils::SpendAmount,
 | 
			
		||||
};
 | 
			
		||||
use solana_client::rpc_client::RpcClient;
 | 
			
		||||
use solana_cli_output::{parse_sign_only_reply_string, OutputFormat};
 | 
			
		||||
use solana_client::{
 | 
			
		||||
    blockhash_query::{self, BlockhashQuery},
 | 
			
		||||
    nonce_utils,
 | 
			
		||||
    rpc_client::RpcClient,
 | 
			
		||||
};
 | 
			
		||||
use solana_core::validator::TestValidator;
 | 
			
		||||
use solana_faucet::faucet::run_local_faucet;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
@@ -410,8 +409,8 @@ fn test_nonced_pay_tx() {
 | 
			
		||||
    check_balance(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey());
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
@@ -433,8 +432,8 @@ fn test_nonced_pay_tx() {
 | 
			
		||||
    check_balance(10, &rpc_client, &bob_pubkey);
 | 
			
		||||
 | 
			
		||||
    // Verify that nonce has been used
 | 
			
		||||
    let nonce_hash2 = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash2 = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
    assert_ne!(nonce_hash, nonce_hash2);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,14 @@
 | 
			
		||||
use solana_cli::test_utils::check_balance;
 | 
			
		||||
use solana_cli::{
 | 
			
		||||
    cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
 | 
			
		||||
    cli_output::OutputFormat,
 | 
			
		||||
    nonce,
 | 
			
		||||
    offline::{
 | 
			
		||||
        blockhash_query::{self, BlockhashQuery},
 | 
			
		||||
        parse_sign_only_reply_string,
 | 
			
		||||
    },
 | 
			
		||||
    spend_utils::SpendAmount,
 | 
			
		||||
};
 | 
			
		||||
use solana_client::rpc_client::RpcClient;
 | 
			
		||||
use solana_cli_output::{parse_sign_only_reply_string, OutputFormat};
 | 
			
		||||
use solana_client::{
 | 
			
		||||
    blockhash_query::{self, BlockhashQuery},
 | 
			
		||||
    nonce_utils,
 | 
			
		||||
    rpc_client::RpcClient,
 | 
			
		||||
};
 | 
			
		||||
use solana_core::validator::{TestValidator, TestValidatorOptions};
 | 
			
		||||
use solana_faucet::faucet::run_local_faucet;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
@@ -57,6 +56,7 @@ fn test_stake_delegation_force() {
 | 
			
		||||
    let vote_keypair = Keypair::new();
 | 
			
		||||
    config.signers = vec![&default_signer, &vote_keypair];
 | 
			
		||||
    config.command = CliCommand::CreateVoteAccount {
 | 
			
		||||
        vote_account: 1,
 | 
			
		||||
        seed: None,
 | 
			
		||||
        identity_account: 0,
 | 
			
		||||
        authorized_voter: None,
 | 
			
		||||
@@ -501,8 +501,8 @@ fn test_nonced_stake_delegation_and_deactivation() {
 | 
			
		||||
    process_command(&config).unwrap();
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
@@ -525,8 +525,8 @@ fn test_nonced_stake_delegation_and_deactivation() {
 | 
			
		||||
    process_command(&config).unwrap();
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
@@ -741,8 +741,8 @@ fn test_stake_authorize() {
 | 
			
		||||
    process_command(&config).unwrap();
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
@@ -787,8 +787,8 @@ fn test_stake_authorize() {
 | 
			
		||||
    };
 | 
			
		||||
    assert_eq!(current_authority, online_authority_pubkey);
 | 
			
		||||
 | 
			
		||||
    let new_nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let new_nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
    assert_ne!(nonce_hash, new_nonce_hash);
 | 
			
		||||
@@ -1023,8 +1023,8 @@ fn test_stake_split() {
 | 
			
		||||
    check_balance(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey());
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
@@ -1280,8 +1280,8 @@ fn test_stake_set_lockup() {
 | 
			
		||||
    check_balance(minimum_nonce_balance, &rpc_client, &nonce_account_pubkey);
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
@@ -1399,8 +1399,8 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
 | 
			
		||||
    process_command(&config).unwrap();
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
@@ -1450,8 +1450,8 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
 | 
			
		||||
    check_balance(50_000, &rpc_client, &stake_pubkey);
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
@@ -1494,8 +1494,8 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
 | 
			
		||||
    check_balance(42, &rpc_client, &recipient_pubkey);
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,14 @@
 | 
			
		||||
use solana_cli::test_utils::check_balance;
 | 
			
		||||
use solana_cli::{
 | 
			
		||||
    cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
 | 
			
		||||
    cli_output::OutputFormat,
 | 
			
		||||
    nonce,
 | 
			
		||||
    offline::{
 | 
			
		||||
        blockhash_query::{self, BlockhashQuery},
 | 
			
		||||
        parse_sign_only_reply_string,
 | 
			
		||||
    },
 | 
			
		||||
    spend_utils::SpendAmount,
 | 
			
		||||
};
 | 
			
		||||
use solana_client::rpc_client::RpcClient;
 | 
			
		||||
use solana_cli_output::{parse_sign_only_reply_string, OutputFormat};
 | 
			
		||||
use solana_client::{
 | 
			
		||||
    blockhash_query::{self, BlockhashQuery},
 | 
			
		||||
    nonce_utils,
 | 
			
		||||
    rpc_client::RpcClient,
 | 
			
		||||
};
 | 
			
		||||
use solana_core::validator::{TestValidator, TestValidatorOptions};
 | 
			
		||||
use solana_faucet::faucet::run_local_faucet;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
@@ -147,8 +146,8 @@ fn test_transfer() {
 | 
			
		||||
    check_balance(49_987 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
@@ -171,8 +170,8 @@ fn test_transfer() {
 | 
			
		||||
    process_command(&config).unwrap();
 | 
			
		||||
    check_balance(49_976 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
 | 
			
		||||
    check_balance(30, &rpc_client, &recipient_pubkey);
 | 
			
		||||
    let new_nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let new_nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
    assert_ne!(nonce_hash, new_nonce_hash);
 | 
			
		||||
@@ -188,8 +187,8 @@ fn test_transfer() {
 | 
			
		||||
    check_balance(49_975 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
 | 
			
		||||
 | 
			
		||||
    // Fetch nonce hash
 | 
			
		||||
    let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
    let nonce_hash = nonce_utils::get_account(&rpc_client, &nonce_account.pubkey())
 | 
			
		||||
        .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .blockhash;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,12 @@
 | 
			
		||||
use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig};
 | 
			
		||||
use solana_cli::test_utils::check_balance;
 | 
			
		||||
use solana_client::rpc_client::RpcClient;
 | 
			
		||||
use solana_cli::{
 | 
			
		||||
    cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
 | 
			
		||||
    spend_utils::SpendAmount,
 | 
			
		||||
    test_utils::check_balance,
 | 
			
		||||
};
 | 
			
		||||
use solana_client::{
 | 
			
		||||
    blockhash_query::{self, BlockhashQuery},
 | 
			
		||||
    rpc_client::RpcClient,
 | 
			
		||||
};
 | 
			
		||||
use solana_core::validator::TestValidator;
 | 
			
		||||
use solana_faucet::faucet::run_local_faucet;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
@@ -45,6 +51,7 @@ fn test_vote_authorize_and_withdraw() {
 | 
			
		||||
    let vote_account_pubkey = vote_account_keypair.pubkey();
 | 
			
		||||
    config.signers = vec![&default_signer, &vote_account_keypair];
 | 
			
		||||
    config.command = CliCommand::CreateVoteAccount {
 | 
			
		||||
        vote_account: 1,
 | 
			
		||||
        seed: None,
 | 
			
		||||
        identity_account: 0,
 | 
			
		||||
        authorized_voter: None,
 | 
			
		||||
@@ -64,6 +71,23 @@ fn test_vote_authorize_and_withdraw() {
 | 
			
		||||
        .max(1);
 | 
			
		||||
    check_balance(expected_balance, &rpc_client, &vote_account_pubkey);
 | 
			
		||||
 | 
			
		||||
    // Transfer in some more SOL
 | 
			
		||||
    config.signers = vec![&default_signer];
 | 
			
		||||
    config.command = CliCommand::Transfer {
 | 
			
		||||
        amount: SpendAmount::Some(1_000),
 | 
			
		||||
        to: vote_account_pubkey,
 | 
			
		||||
        from: 0,
 | 
			
		||||
        sign_only: false,
 | 
			
		||||
        no_wait: false,
 | 
			
		||||
        blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
 | 
			
		||||
        nonce_account: None,
 | 
			
		||||
        nonce_authority: 0,
 | 
			
		||||
        fee_payer: 0,
 | 
			
		||||
    };
 | 
			
		||||
    process_command(&config).unwrap();
 | 
			
		||||
    let expected_balance = expected_balance + 1_000;
 | 
			
		||||
    check_balance(expected_balance, &rpc_client, &vote_account_pubkey);
 | 
			
		||||
 | 
			
		||||
    // Authorize vote account withdrawal to another signer
 | 
			
		||||
    let withdraw_authority = Keypair::new();
 | 
			
		||||
    config.signers = vec![&default_signer];
 | 
			
		||||
@@ -86,7 +110,7 @@ fn test_vote_authorize_and_withdraw() {
 | 
			
		||||
    config.command = CliCommand::WithdrawFromVoteAccount {
 | 
			
		||||
        vote_account_pubkey,
 | 
			
		||||
        withdraw_authority: 1,
 | 
			
		||||
        lamports: 100,
 | 
			
		||||
        withdraw_amount: SpendAmount::Some(100),
 | 
			
		||||
        destination_account_pubkey: destination_account,
 | 
			
		||||
    };
 | 
			
		||||
    process_command(&config).unwrap();
 | 
			
		||||
@@ -99,6 +123,7 @@ fn test_vote_authorize_and_withdraw() {
 | 
			
		||||
    config.command = CliCommand::VoteUpdateValidator {
 | 
			
		||||
        vote_account_pubkey,
 | 
			
		||||
        new_identity_account: 2,
 | 
			
		||||
        withdraw_authority: 1,
 | 
			
		||||
    };
 | 
			
		||||
    process_command(&config).unwrap();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "solana-client"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
description = "Solana Client"
 | 
			
		||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
 | 
			
		||||
repository = "https://github.com/solana-labs/solana"
 | 
			
		||||
@@ -9,29 +9,32 @@ license = "Apache-2.0"
 | 
			
		||||
edition = "2018"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
bincode = "1.2.1"
 | 
			
		||||
bincode = "1.3.1"
 | 
			
		||||
bs58 = "0.3.1"
 | 
			
		||||
clap = "2.33.0"
 | 
			
		||||
indicatif = "0.14.0"
 | 
			
		||||
jsonrpc-core = "14.1.0"
 | 
			
		||||
jsonrpc-core = "15.0.0"
 | 
			
		||||
log = "0.4.8"
 | 
			
		||||
rayon = "1.3.0"
 | 
			
		||||
rayon = "1.4.0"
 | 
			
		||||
reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "rustls-tls", "json"] }
 | 
			
		||||
serde = "1.0.110"
 | 
			
		||||
serde_derive = "1.0.103"
 | 
			
		||||
serde_json = "1.0.53"
 | 
			
		||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.0" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.0" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.0" }
 | 
			
		||||
solana-vote-program = { path = "../programs/vote", version = "1.2.0" }
 | 
			
		||||
solana-account-decoder = { path = "../account-decoder", version = "1.2.32" }
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.32" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.32" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.32" }
 | 
			
		||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.32" }
 | 
			
		||||
solana-vote-program = { path = "../programs/vote", version = "1.2.32" }
 | 
			
		||||
thiserror = "1.0"
 | 
			
		||||
tungstenite = "0.10.1"
 | 
			
		||||
url = "2.1.1"
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
assert_matches = "1.3.0"
 | 
			
		||||
jsonrpc-core = "14.1.0"
 | 
			
		||||
jsonrpc-http-server = "14.1.0"
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.0" }
 | 
			
		||||
jsonrpc-core = "15.0.0"
 | 
			
		||||
jsonrpc-http-server = "15.0.0"
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.32" }
 | 
			
		||||
 | 
			
		||||
[package.metadata.docs.rs]
 | 
			
		||||
targets = ["x86_64-unknown-linux-gnu"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,11 @@
 | 
			
		||||
use super::*;
 | 
			
		||||
use crate::{nonce_utils, rpc_client::RpcClient};
 | 
			
		||||
use clap::ArgMatches;
 | 
			
		||||
use solana_clap_utils::{
 | 
			
		||||
    input_parsers::{pubkey_of, value_of},
 | 
			
		||||
    nonce::*,
 | 
			
		||||
    offline::*,
 | 
			
		||||
};
 | 
			
		||||
use solana_sdk::{fee_calculator::FeeCalculator, hash::Hash, pubkey::Pubkey};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
pub enum Source {
 | 
			
		||||
@@ -17,8 +24,8 @@ impl Source {
 | 
			
		||||
                Ok(res)
 | 
			
		||||
            }
 | 
			
		||||
            Self::NonceAccount(ref pubkey) => {
 | 
			
		||||
                let data = nonce::get_account(rpc_client, pubkey)
 | 
			
		||||
                    .and_then(|ref a| nonce::data_from_account(a))?;
 | 
			
		||||
                let data = nonce_utils::get_account(rpc_client, pubkey)
 | 
			
		||||
                    .and_then(|ref a| nonce_utils::data_from_account(a))?;
 | 
			
		||||
                Ok((data.blockhash, data.fee_calculator))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -35,8 +42,8 @@ impl Source {
 | 
			
		||||
                Ok(res)
 | 
			
		||||
            }
 | 
			
		||||
            Self::NonceAccount(ref pubkey) => {
 | 
			
		||||
                let res = nonce::get_account(rpc_client, pubkey)
 | 
			
		||||
                    .and_then(|ref a| nonce::data_from_account(a))
 | 
			
		||||
                let res = nonce_utils::get_account(rpc_client, pubkey)
 | 
			
		||||
                    .and_then(|ref a| nonce_utils::data_from_account(a))
 | 
			
		||||
                    .and_then(|d| {
 | 
			
		||||
                        if d.blockhash == *blockhash {
 | 
			
		||||
                            Ok(Some(d.fee_calculator))
 | 
			
		||||
@@ -73,7 +80,7 @@ impl BlockhashQuery {
 | 
			
		||||
    pub fn new_from_matches(matches: &ArgMatches<'_>) -> Self {
 | 
			
		||||
        let blockhash = value_of(matches, BLOCKHASH_ARG.name);
 | 
			
		||||
        let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
 | 
			
		||||
        let nonce_account = pubkey_of(matches, nonce::NONCE_ARG.name);
 | 
			
		||||
        let nonce_account = pubkey_of(matches, NONCE_ARG.name);
 | 
			
		||||
        BlockhashQuery::new(blockhash, sign_only, nonce_account)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -103,16 +110,15 @@ impl Default for BlockhashQuery {
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{nonce::nonce_arg, offline::blockhash_query::BlockhashQuery};
 | 
			
		||||
    use crate::{
 | 
			
		||||
        blockhash_query,
 | 
			
		||||
        rpc_request::RpcRequest,
 | 
			
		||||
        rpc_response::{Response, RpcFeeCalculator, RpcResponseContext},
 | 
			
		||||
    };
 | 
			
		||||
    use clap::App;
 | 
			
		||||
    use serde_json::{self, json, Value};
 | 
			
		||||
    use solana_client::{
 | 
			
		||||
        rpc_request::RpcRequest,
 | 
			
		||||
        rpc_response::{Response, RpcAccount, RpcFeeCalculator, RpcResponseContext},
 | 
			
		||||
    };
 | 
			
		||||
    use solana_sdk::{
 | 
			
		||||
        account::Account, fee_calculator::FeeCalculator, hash::hash, nonce, system_program,
 | 
			
		||||
    };
 | 
			
		||||
    use solana_account_decoder::{UiAccount, UiAccountEncoding};
 | 
			
		||||
    use solana_sdk::{account::Account, hash::hash, nonce, system_program};
 | 
			
		||||
    use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
@@ -165,9 +171,7 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_blockhash_query_new_from_matches_ok() {
 | 
			
		||||
        let test_commands = App::new("blockhash_query_test")
 | 
			
		||||
            .arg(nonce_arg())
 | 
			
		||||
            .offline_args();
 | 
			
		||||
        let test_commands = App::new("blockhash_query_test").nonce_args().offline_args();
 | 
			
		||||
        let blockhash = hash(&[1u8]);
 | 
			
		||||
        let blockhash_string = blockhash.to_string();
 | 
			
		||||
 | 
			
		||||
@@ -344,7 +348,13 @@ mod tests {
 | 
			
		||||
        )
 | 
			
		||||
        .unwrap();
 | 
			
		||||
        let nonce_pubkey = Pubkey::new(&[4u8; 32]);
 | 
			
		||||
        let rpc_nonce_account = RpcAccount::encode(nonce_account);
 | 
			
		||||
        let rpc_nonce_account = UiAccount::encode(
 | 
			
		||||
            &nonce_pubkey,
 | 
			
		||||
            nonce_account,
 | 
			
		||||
            UiAccountEncoding::Base64,
 | 
			
		||||
            None,
 | 
			
		||||
            None,
 | 
			
		||||
        );
 | 
			
		||||
        let get_account_response = json!(Response {
 | 
			
		||||
            context: RpcResponseContext { slot: 1 },
 | 
			
		||||
            value: json!(Some(rpc_nonce_account)),
 | 
			
		||||
@@ -1,13 +1,16 @@
 | 
			
		||||
#[macro_use]
 | 
			
		||||
extern crate serde_derive;
 | 
			
		||||
 | 
			
		||||
pub mod blockhash_query;
 | 
			
		||||
pub mod client_error;
 | 
			
		||||
pub mod http_sender;
 | 
			
		||||
pub mod mock_sender;
 | 
			
		||||
pub mod nonce_utils;
 | 
			
		||||
pub mod perf_utils;
 | 
			
		||||
pub mod pubsub_client;
 | 
			
		||||
pub mod rpc_client;
 | 
			
		||||
pub mod rpc_config;
 | 
			
		||||
pub mod rpc_filter;
 | 
			
		||||
pub mod rpc_request;
 | 
			
		||||
pub mod rpc_response;
 | 
			
		||||
pub mod rpc_sender;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										68
									
								
								client/src/nonce_utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								client/src/nonce_utils.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
use crate::rpc_client::RpcClient;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    account::Account,
 | 
			
		||||
    account_utils::StateMut,
 | 
			
		||||
    nonce::{
 | 
			
		||||
        state::{Data, Versions},
 | 
			
		||||
        State,
 | 
			
		||||
    },
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    system_program,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, thiserror::Error, PartialEq)]
 | 
			
		||||
pub enum Error {
 | 
			
		||||
    #[error("invalid account owner")]
 | 
			
		||||
    InvalidAccountOwner,
 | 
			
		||||
    #[error("invalid account data")]
 | 
			
		||||
    InvalidAccountData,
 | 
			
		||||
    #[error("unexpected account data size")]
 | 
			
		||||
    UnexpectedDataSize,
 | 
			
		||||
    #[error("query hash does not match stored hash")]
 | 
			
		||||
    InvalidHash,
 | 
			
		||||
    #[error("query authority does not match account authority")]
 | 
			
		||||
    InvalidAuthority,
 | 
			
		||||
    #[error("invalid state for requested operation")]
 | 
			
		||||
    InvalidStateForOperation,
 | 
			
		||||
    #[error("client error: {0}")]
 | 
			
		||||
    Client(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_account(rpc_client: &RpcClient, nonce_pubkey: &Pubkey) -> Result<Account, Error> {
 | 
			
		||||
    rpc_client
 | 
			
		||||
        .get_account(nonce_pubkey)
 | 
			
		||||
        .map_err(|e| Error::Client(format!("{}", e)))
 | 
			
		||||
        .and_then(|a| match account_identity_ok(&a) {
 | 
			
		||||
            Ok(()) => Ok(a),
 | 
			
		||||
            Err(e) => Err(e),
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn account_identity_ok(account: &Account) -> Result<(), Error> {
 | 
			
		||||
    if account.owner != system_program::id() {
 | 
			
		||||
        Err(Error::InvalidAccountOwner)
 | 
			
		||||
    } else if account.data.is_empty() {
 | 
			
		||||
        Err(Error::UnexpectedDataSize)
 | 
			
		||||
    } else {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn state_from_account(account: &Account) -> Result<State, Error> {
 | 
			
		||||
    account_identity_ok(account)?;
 | 
			
		||||
    StateMut::<Versions>::state(account)
 | 
			
		||||
        .map_err(|_| Error::InvalidAccountData)
 | 
			
		||||
        .map(|v| v.convert_to_current())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn data_from_account(account: &Account) -> Result<Data, Error> {
 | 
			
		||||
    account_identity_ok(account)?;
 | 
			
		||||
    state_from_account(account).and_then(|ref s| data_from_state(s).map(|d| d.clone()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn data_from_state(state: &State) -> Result<&Data, Error> {
 | 
			
		||||
    match state {
 | 
			
		||||
        State::Uninitialized => Err(Error::InvalidStateForOperation),
 | 
			
		||||
        State::Initialized(data) => Ok(data),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,8 +2,12 @@ use crate::{
 | 
			
		||||
    client_error::{ClientError, ClientErrorKind, Result as ClientResult},
 | 
			
		||||
    http_sender::HttpSender,
 | 
			
		||||
    mock_sender::{MockSender, Mocks},
 | 
			
		||||
    rpc_config::RpcLargestAccountsConfig,
 | 
			
		||||
    rpc_request::{RpcError, RpcRequest},
 | 
			
		||||
    rpc_config::RpcAccountInfoConfig,
 | 
			
		||||
    rpc_config::{
 | 
			
		||||
        RpcGetConfirmedSignaturesForAddress2Config, RpcLargestAccountsConfig,
 | 
			
		||||
        RpcSendTransactionConfig, RpcTokenAccountsFilter,
 | 
			
		||||
    },
 | 
			
		||||
    rpc_request::{RpcError, RpcRequest, TokenAccountsFilter},
 | 
			
		||||
    rpc_response::*,
 | 
			
		||||
    rpc_sender::RpcSender,
 | 
			
		||||
};
 | 
			
		||||
@@ -11,6 +15,7 @@ use bincode::serialize;
 | 
			
		||||
use indicatif::{ProgressBar, ProgressStyle};
 | 
			
		||||
use log::*;
 | 
			
		||||
use serde_json::{json, Value};
 | 
			
		||||
use solana_account_decoder::{parse_token::UiTokenAmount, UiAccount, UiAccountEncoding};
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    account::Account,
 | 
			
		||||
    clock::{
 | 
			
		||||
@@ -22,18 +27,16 @@ use solana_sdk::{
 | 
			
		||||
    epoch_schedule::EpochSchedule,
 | 
			
		||||
    fee_calculator::{FeeCalculator, FeeRateGovernor},
 | 
			
		||||
    hash::Hash,
 | 
			
		||||
    inflation::Inflation,
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    signature::Signature,
 | 
			
		||||
    signers::Signers,
 | 
			
		||||
    transaction::{self, Transaction},
 | 
			
		||||
};
 | 
			
		||||
use solana_transaction_status::{
 | 
			
		||||
    ConfirmedBlock, ConfirmedTransaction, TransactionEncoding, TransactionStatus,
 | 
			
		||||
    ConfirmedBlock, ConfirmedTransaction, TransactionStatus, UiTransactionEncoding,
 | 
			
		||||
};
 | 
			
		||||
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
 | 
			
		||||
use std::{
 | 
			
		||||
    error,
 | 
			
		||||
    net::SocketAddr,
 | 
			
		||||
    thread::sleep,
 | 
			
		||||
    time::{Duration, Instant},
 | 
			
		||||
@@ -95,10 +98,20 @@ impl RpcClient {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn send_transaction(&self, transaction: &Transaction) -> ClientResult<Signature> {
 | 
			
		||||
        self.send_transaction_with_config(transaction, RpcSendTransactionConfig::default())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn send_transaction_with_config(
 | 
			
		||||
        &self,
 | 
			
		||||
        transaction: &Transaction,
 | 
			
		||||
        config: RpcSendTransactionConfig,
 | 
			
		||||
    ) -> ClientResult<Signature> {
 | 
			
		||||
        let serialized_encoded = bs58::encode(serialize(transaction).unwrap()).into_string();
 | 
			
		||||
 | 
			
		||||
        let signature_base58_str: String =
 | 
			
		||||
            self.send(RpcRequest::SendTransaction, json!([serialized_encoded]))?;
 | 
			
		||||
        let signature_base58_str: String = self.send(
 | 
			
		||||
            RpcRequest::SendTransaction,
 | 
			
		||||
            json!([serialized_encoded, config]),
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        let signature = signature_base58_str
 | 
			
		||||
            .parse::<Signature>()
 | 
			
		||||
@@ -122,7 +135,7 @@ impl RpcClient {
 | 
			
		||||
        &self,
 | 
			
		||||
        transaction: &Transaction,
 | 
			
		||||
        sig_verify: bool,
 | 
			
		||||
    ) -> RpcResult<TransactionStatus> {
 | 
			
		||||
    ) -> RpcResult<RpcSimulateTransactionResult> {
 | 
			
		||||
        let serialized_encoded = bs58::encode(serialize(transaction).unwrap()).into_string();
 | 
			
		||||
        self.send(
 | 
			
		||||
            RpcRequest::SimulateTransaction,
 | 
			
		||||
@@ -230,13 +243,13 @@ impl RpcClient {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_confirmed_block(&self, slot: Slot) -> ClientResult<ConfirmedBlock> {
 | 
			
		||||
        self.get_confirmed_block_with_encoding(slot, TransactionEncoding::Json)
 | 
			
		||||
        self.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Json)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_confirmed_block_with_encoding(
 | 
			
		||||
        &self,
 | 
			
		||||
        slot: Slot,
 | 
			
		||||
        encoding: TransactionEncoding,
 | 
			
		||||
        encoding: UiTransactionEncoding,
 | 
			
		||||
    ) -> ClientResult<ConfirmedBlock> {
 | 
			
		||||
        self.send(RpcRequest::GetConfirmedBlock, json!([slot, encoding]))
 | 
			
		||||
    }
 | 
			
		||||
@@ -274,10 +287,39 @@ impl RpcClient {
 | 
			
		||||
        Ok(signatures)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_confirmed_signatures_for_address2(
 | 
			
		||||
        &self,
 | 
			
		||||
        address: &Pubkey,
 | 
			
		||||
    ) -> ClientResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
 | 
			
		||||
        self.get_confirmed_signatures_for_address2_with_config(
 | 
			
		||||
            address,
 | 
			
		||||
            GetConfirmedSignaturesForAddress2Config::default(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_confirmed_signatures_for_address2_with_config(
 | 
			
		||||
        &self,
 | 
			
		||||
        address: &Pubkey,
 | 
			
		||||
        config: GetConfirmedSignaturesForAddress2Config,
 | 
			
		||||
    ) -> ClientResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
 | 
			
		||||
        let config = RpcGetConfirmedSignaturesForAddress2Config {
 | 
			
		||||
            before: config.before.map(|signature| signature.to_string()),
 | 
			
		||||
            until: config.until.map(|signature| signature.to_string()),
 | 
			
		||||
            limit: config.limit,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let result: Vec<RpcConfirmedTransactionStatusWithSignature> = self.send(
 | 
			
		||||
            RpcRequest::GetConfirmedSignaturesForAddress2,
 | 
			
		||||
            json!([address.to_string(), config]),
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        Ok(result)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_confirmed_transaction(
 | 
			
		||||
        &self,
 | 
			
		||||
        signature: &Signature,
 | 
			
		||||
        encoding: TransactionEncoding,
 | 
			
		||||
        encoding: UiTransactionEncoding,
 | 
			
		||||
    ) -> ClientResult<ConfirmedTransaction> {
 | 
			
		||||
        self.send(
 | 
			
		||||
            RpcRequest::GetConfirmedTransaction,
 | 
			
		||||
@@ -346,8 +388,12 @@ impl RpcClient {
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_inflation(&self) -> ClientResult<Inflation> {
 | 
			
		||||
        self.send(RpcRequest::GetInflation, Value::Null)
 | 
			
		||||
    pub fn get_inflation_governor(&self) -> ClientResult<RpcInflationGovernor> {
 | 
			
		||||
        self.send(RpcRequest::GetInflationGovernor, Value::Null)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_inflation_rate(&self) -> ClientResult<RpcInflationRate> {
 | 
			
		||||
        self.send(RpcRequest::GetInflationRate, Value::Null)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_version(&self) -> ClientResult<RpcVersionInfo> {
 | 
			
		||||
@@ -362,134 +408,42 @@ impl RpcClient {
 | 
			
		||||
        &self,
 | 
			
		||||
        transaction: &Transaction,
 | 
			
		||||
    ) -> ClientResult<Signature> {
 | 
			
		||||
        let mut send_retries = 20;
 | 
			
		||||
        loop {
 | 
			
		||||
            let mut status_retries = 15;
 | 
			
		||||
            let signature = self.send_transaction(transaction)?;
 | 
			
		||||
            let status = loop {
 | 
			
		||||
                let status = self.get_signature_status(&signature)?;
 | 
			
		||||
                if status.is_none() {
 | 
			
		||||
                    status_retries -= 1;
 | 
			
		||||
                    if status_retries == 0 {
 | 
			
		||||
                        break status;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
        let signature = self.send_transaction(transaction)?;
 | 
			
		||||
        let recent_blockhash = transaction.message.recent_blockhash;
 | 
			
		||||
        let status = loop {
 | 
			
		||||
            let status = self.get_signature_status(&signature)?;
 | 
			
		||||
            if status.is_none() {
 | 
			
		||||
                if self
 | 
			
		||||
                    .get_fee_calculator_for_blockhash_with_commitment(
 | 
			
		||||
                        &recent_blockhash,
 | 
			
		||||
                        CommitmentConfig::recent(),
 | 
			
		||||
                    )?
 | 
			
		||||
                    .value
 | 
			
		||||
                    .is_none()
 | 
			
		||||
                {
 | 
			
		||||
                    break status;
 | 
			
		||||
                }
 | 
			
		||||
                if cfg!(not(test)) {
 | 
			
		||||
                    // Retry twice a second
 | 
			
		||||
                    sleep(Duration::from_millis(500));
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            send_retries = if let Some(result) = status.clone() {
 | 
			
		||||
                match result {
 | 
			
		||||
                    Ok(_) => return Ok(signature),
 | 
			
		||||
                    Err(_) => 0,
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                send_retries - 1
 | 
			
		||||
            };
 | 
			
		||||
            if send_retries == 0 {
 | 
			
		||||
                if let Some(err) = status {
 | 
			
		||||
                    return Err(err.unwrap_err().into());
 | 
			
		||||
                } else {
 | 
			
		||||
                    return Err(
 | 
			
		||||
                        RpcError::ForUser("unable to confirm transaction. \
 | 
			
		||||
                                          This can happen in situations such as transaction expiration \
 | 
			
		||||
                                          and insufficient fee-payer funds".to_string()).into(),
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                break status;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn send_and_confirm_transactions_with_spinner<T: Signers>(
 | 
			
		||||
        &self,
 | 
			
		||||
        mut transactions: Vec<Transaction>,
 | 
			
		||||
        signer_keys: &T,
 | 
			
		||||
    ) -> Result<(), Box<dyn error::Error>> {
 | 
			
		||||
        let progress_bar = new_spinner_progress_bar();
 | 
			
		||||
        let mut send_retries = 5;
 | 
			
		||||
        loop {
 | 
			
		||||
            let mut status_retries = 15;
 | 
			
		||||
 | 
			
		||||
            // Send all transactions
 | 
			
		||||
            let mut transactions_signatures = vec![];
 | 
			
		||||
            let num_transactions = transactions.len();
 | 
			
		||||
            for transaction in transactions {
 | 
			
		||||
                if cfg!(not(test)) {
 | 
			
		||||
                    // Delay ~1 tick between write transactions in an attempt to reduce AccountInUse errors
 | 
			
		||||
                    // when all the write transactions modify the same program account (eg, deploying a
 | 
			
		||||
                    // new program)
 | 
			
		||||
                    sleep(Duration::from_millis(1000 / DEFAULT_TICKS_PER_SECOND));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let signature = self.send_transaction(&transaction).ok();
 | 
			
		||||
                transactions_signatures.push((transaction, signature));
 | 
			
		||||
 | 
			
		||||
                progress_bar.set_message(&format!(
 | 
			
		||||
                    "[{}/{}] Transactions sent",
 | 
			
		||||
                    transactions_signatures.len(),
 | 
			
		||||
                    num_transactions
 | 
			
		||||
                ));
 | 
			
		||||
            if cfg!(not(test)) {
 | 
			
		||||
                // Retry twice a second
 | 
			
		||||
                sleep(Duration::from_millis(500));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Collect statuses for all the transactions, drop those that are confirmed
 | 
			
		||||
            while status_retries > 0 {
 | 
			
		||||
                status_retries -= 1;
 | 
			
		||||
 | 
			
		||||
                progress_bar.set_message(&format!(
 | 
			
		||||
                    "[{}/{}] Transactions confirmed",
 | 
			
		||||
                    num_transactions - transactions_signatures.len(),
 | 
			
		||||
                    num_transactions
 | 
			
		||||
                ));
 | 
			
		||||
 | 
			
		||||
                if cfg!(not(test)) {
 | 
			
		||||
                    // Retry twice a second
 | 
			
		||||
                    sleep(Duration::from_millis(500));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                transactions_signatures = transactions_signatures
 | 
			
		||||
                    .into_iter()
 | 
			
		||||
                    .filter(|(_transaction, signature)| {
 | 
			
		||||
                        if let Some(signature) = signature {
 | 
			
		||||
                            if let Ok(status) = self.get_signature_status(&signature) {
 | 
			
		||||
                                if self
 | 
			
		||||
                                    .get_num_blocks_since_signature_confirmation(&signature)
 | 
			
		||||
                                    .unwrap_or(0)
 | 
			
		||||
                                    > 1
 | 
			
		||||
                                {
 | 
			
		||||
                                    return false;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    return match status {
 | 
			
		||||
                                        None => true,
 | 
			
		||||
                                        Some(result) => result.is_err(),
 | 
			
		||||
                                    };
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        true
 | 
			
		||||
                    })
 | 
			
		||||
                    .collect();
 | 
			
		||||
 | 
			
		||||
                if transactions_signatures.is_empty() {
 | 
			
		||||
                    return Ok(());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if send_retries == 0 {
 | 
			
		||||
                return Err(RpcError::ForUser("Transactions failed".to_string()).into());
 | 
			
		||||
            }
 | 
			
		||||
            send_retries -= 1;
 | 
			
		||||
 | 
			
		||||
            // Re-sign any failed transactions with a new blockhash and retry
 | 
			
		||||
            let (blockhash, _fee_calculator) =
 | 
			
		||||
                self.get_new_blockhash(&transactions_signatures[0].0.message().recent_blockhash)?;
 | 
			
		||||
            transactions = vec![];
 | 
			
		||||
            for (mut transaction, _) in transactions_signatures.into_iter() {
 | 
			
		||||
                transaction.try_sign(signer_keys, blockhash)?;
 | 
			
		||||
                transactions.push(transaction);
 | 
			
		||||
        };
 | 
			
		||||
        if let Some(result) = status {
 | 
			
		||||
            match result {
 | 
			
		||||
                Ok(_) => Ok(signature),
 | 
			
		||||
                Err(err) => Err(err.into()),
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(RpcError::ForUser(
 | 
			
		||||
                "unable to confirm transaction. \
 | 
			
		||||
                                  This can happen in situations such as transaction expiration \
 | 
			
		||||
                                  and insufficient fee-payer funds"
 | 
			
		||||
                    .to_string(),
 | 
			
		||||
            )
 | 
			
		||||
            .into())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -515,9 +469,14 @@ impl RpcClient {
 | 
			
		||||
        pubkey: &Pubkey,
 | 
			
		||||
        commitment_config: CommitmentConfig,
 | 
			
		||||
    ) -> RpcResult<Option<Account>> {
 | 
			
		||||
        let config = RpcAccountInfoConfig {
 | 
			
		||||
            encoding: Some(UiAccountEncoding::Base64),
 | 
			
		||||
            commitment: Some(commitment_config),
 | 
			
		||||
            data_slice: None,
 | 
			
		||||
        };
 | 
			
		||||
        let response = self.sender.send(
 | 
			
		||||
            RpcRequest::GetAccountInfo,
 | 
			
		||||
            json!([pubkey.to_string(), commitment_config]),
 | 
			
		||||
            json!([pubkey.to_string(), config]),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        response
 | 
			
		||||
@@ -530,9 +489,9 @@ impl RpcClient {
 | 
			
		||||
                let Response {
 | 
			
		||||
                    context,
 | 
			
		||||
                    value: rpc_account,
 | 
			
		||||
                } = serde_json::from_value::<Response<Option<RpcAccount>>>(result_json)?;
 | 
			
		||||
                } = serde_json::from_value::<Response<Option<UiAccount>>>(result_json)?;
 | 
			
		||||
                trace!("Response account {:?} {:?}", pubkey, rpc_account);
 | 
			
		||||
                let account = rpc_account.and_then(|rpc_account| rpc_account.decode().ok());
 | 
			
		||||
                let account = rpc_account.and_then(|rpc_account| rpc_account.decode());
 | 
			
		||||
                Ok(Response {
 | 
			
		||||
                    context,
 | 
			
		||||
                    value: account,
 | 
			
		||||
@@ -546,6 +505,38 @@ impl RpcClient {
 | 
			
		||||
            })?
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_multiple_accounts(&self, pubkeys: &[Pubkey]) -> ClientResult<Vec<Option<Account>>> {
 | 
			
		||||
        Ok(self
 | 
			
		||||
            .get_multiple_accounts_with_commitment(pubkeys, CommitmentConfig::default())?
 | 
			
		||||
            .value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_multiple_accounts_with_commitment(
 | 
			
		||||
        &self,
 | 
			
		||||
        pubkeys: &[Pubkey],
 | 
			
		||||
        commitment_config: CommitmentConfig,
 | 
			
		||||
    ) -> RpcResult<Vec<Option<Account>>> {
 | 
			
		||||
        let config = RpcAccountInfoConfig {
 | 
			
		||||
            encoding: Some(UiAccountEncoding::Base64),
 | 
			
		||||
            commitment: Some(commitment_config),
 | 
			
		||||
            data_slice: None,
 | 
			
		||||
        };
 | 
			
		||||
        let pubkeys: Vec<_> = pubkeys.iter().map(|pubkey| pubkey.to_string()).collect();
 | 
			
		||||
        let response = self.send(RpcRequest::GetMultipleAccounts, json!([pubkeys, config]))?;
 | 
			
		||||
        let Response {
 | 
			
		||||
            context,
 | 
			
		||||
            value: accounts,
 | 
			
		||||
        } = serde_json::from_value::<Response<Vec<Option<UiAccount>>>>(response)?;
 | 
			
		||||
        let accounts: Vec<Option<Account>> = accounts
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(|rpc_account| rpc_account.map(|a| a.decode()).flatten())
 | 
			
		||||
            .collect();
 | 
			
		||||
        Ok(Response {
 | 
			
		||||
            context,
 | 
			
		||||
            value: accounts,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_account_data(&self, pubkey: &Pubkey) -> ClientResult<Vec<u8>> {
 | 
			
		||||
        Ok(self.get_account(pubkey)?.data)
 | 
			
		||||
    }
 | 
			
		||||
@@ -586,19 +577,17 @@ impl RpcClient {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_program_accounts(&self, pubkey: &Pubkey) -> ClientResult<Vec<(Pubkey, Account)>> {
 | 
			
		||||
        let accounts: Vec<RpcKeyedAccount> =
 | 
			
		||||
            self.send(RpcRequest::GetProgramAccounts, json!([pubkey.to_string()]))?;
 | 
			
		||||
        let mut pubkey_accounts: Vec<(Pubkey, Account)> = Vec::new();
 | 
			
		||||
        for RpcKeyedAccount { pubkey, account } in accounts.into_iter() {
 | 
			
		||||
            let pubkey = pubkey.parse().map_err(|_| {
 | 
			
		||||
                ClientError::new_with_request(
 | 
			
		||||
                    RpcError::ParseError("Pubkey".to_string()).into(),
 | 
			
		||||
                    RpcRequest::GetProgramAccounts,
 | 
			
		||||
                )
 | 
			
		||||
            })?;
 | 
			
		||||
            pubkey_accounts.push((pubkey, account.decode().unwrap()));
 | 
			
		||||
        }
 | 
			
		||||
        Ok(pubkey_accounts)
 | 
			
		||||
        let config = RpcAccountInfoConfig {
 | 
			
		||||
            encoding: Some(UiAccountEncoding::Base64),
 | 
			
		||||
            commitment: None,
 | 
			
		||||
            data_slice: None,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let accounts: Vec<RpcKeyedAccount> = self.send(
 | 
			
		||||
            RpcRequest::GetProgramAccounts,
 | 
			
		||||
            json!([pubkey.to_string(), config]),
 | 
			
		||||
        )?;
 | 
			
		||||
        parse_keyed_accounts(accounts, RpcRequest::GetProgramAccounts)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Request the transaction count.
 | 
			
		||||
@@ -614,26 +603,46 @@ impl RpcClient {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> {
 | 
			
		||||
        Ok(self
 | 
			
		||||
        let (blockhash, fee_calculator, _last_valid_slot) = self
 | 
			
		||||
            .get_recent_blockhash_with_commitment(CommitmentConfig::default())?
 | 
			
		||||
            .value)
 | 
			
		||||
            .value;
 | 
			
		||||
        Ok((blockhash, fee_calculator))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_recent_blockhash_with_commitment(
 | 
			
		||||
        &self,
 | 
			
		||||
        commitment_config: CommitmentConfig,
 | 
			
		||||
    ) -> RpcResult<(Hash, FeeCalculator)> {
 | 
			
		||||
        let Response {
 | 
			
		||||
    ) -> RpcResult<(Hash, FeeCalculator, Slot)> {
 | 
			
		||||
        let (context, blockhash, fee_calculator, last_valid_slot) = if let Ok(Response {
 | 
			
		||||
            context,
 | 
			
		||||
            value:
 | 
			
		||||
                RpcFees {
 | 
			
		||||
                    blockhash,
 | 
			
		||||
                    fee_calculator,
 | 
			
		||||
                    last_valid_slot,
 | 
			
		||||
                },
 | 
			
		||||
        }) =
 | 
			
		||||
            self.send::<Response<RpcFees>>(RpcRequest::GetFees, json!([commitment_config]))
 | 
			
		||||
        {
 | 
			
		||||
            (context, blockhash, fee_calculator, last_valid_slot)
 | 
			
		||||
        } else if let Ok(Response {
 | 
			
		||||
            context,
 | 
			
		||||
            value:
 | 
			
		||||
                RpcBlockhashFeeCalculator {
 | 
			
		||||
                    blockhash,
 | 
			
		||||
                    fee_calculator,
 | 
			
		||||
                },
 | 
			
		||||
        } = self.send::<Response<RpcBlockhashFeeCalculator>>(
 | 
			
		||||
        }) = self.send::<Response<RpcBlockhashFeeCalculator>>(
 | 
			
		||||
            RpcRequest::GetRecentBlockhash,
 | 
			
		||||
            json!([commitment_config]),
 | 
			
		||||
        )?;
 | 
			
		||||
        ) {
 | 
			
		||||
            (context, blockhash, fee_calculator, 0)
 | 
			
		||||
        } else {
 | 
			
		||||
            return Err(ClientError::new_with_request(
 | 
			
		||||
                RpcError::ParseError("RpcBlockhashFeeCalculator or RpcFees".to_string()).into(),
 | 
			
		||||
                RpcRequest::GetRecentBlockhash,
 | 
			
		||||
            ));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let blockhash = blockhash.parse().map_err(|_| {
 | 
			
		||||
            ClientError::new_with_request(
 | 
			
		||||
@@ -643,7 +652,7 @@ impl RpcClient {
 | 
			
		||||
        })?;
 | 
			
		||||
        Ok(Response {
 | 
			
		||||
            context,
 | 
			
		||||
            value: (blockhash, fee_calculator),
 | 
			
		||||
            value: (blockhash, fee_calculator, last_valid_slot),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -651,12 +660,28 @@ impl RpcClient {
 | 
			
		||||
        &self,
 | 
			
		||||
        blockhash: &Hash,
 | 
			
		||||
    ) -> ClientResult<Option<FeeCalculator>> {
 | 
			
		||||
        let Response { value, .. } = self.send::<Response<Option<RpcFeeCalculator>>>(
 | 
			
		||||
        Ok(self
 | 
			
		||||
            .get_fee_calculator_for_blockhash_with_commitment(
 | 
			
		||||
                blockhash,
 | 
			
		||||
                CommitmentConfig::default(),
 | 
			
		||||
            )?
 | 
			
		||||
            .value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_fee_calculator_for_blockhash_with_commitment(
 | 
			
		||||
        &self,
 | 
			
		||||
        blockhash: &Hash,
 | 
			
		||||
        commitment_config: CommitmentConfig,
 | 
			
		||||
    ) -> RpcResult<Option<FeeCalculator>> {
 | 
			
		||||
        let Response { context, value } = self.send::<Response<Option<RpcFeeCalculator>>>(
 | 
			
		||||
            RpcRequest::GetFeeCalculatorForBlockhash,
 | 
			
		||||
            json!([blockhash.to_string()]),
 | 
			
		||||
            json!([blockhash.to_string(), commitment_config]),
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        Ok(value.map(|rf| rf.fee_calculator))
 | 
			
		||||
        Ok(Response {
 | 
			
		||||
            context,
 | 
			
		||||
            value: value.map(|rf| rf.fee_calculator),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_fee_rate_governor(&self) -> RpcResult<FeeRateGovernor> {
 | 
			
		||||
@@ -709,6 +734,118 @@ impl RpcClient {
 | 
			
		||||
        Ok(hash)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_token_account_balance(&self, pubkey: &Pubkey) -> ClientResult<UiTokenAmount> {
 | 
			
		||||
        Ok(self
 | 
			
		||||
            .get_token_account_balance_with_commitment(pubkey, CommitmentConfig::default())?
 | 
			
		||||
            .value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_token_account_balance_with_commitment(
 | 
			
		||||
        &self,
 | 
			
		||||
        pubkey: &Pubkey,
 | 
			
		||||
        commitment_config: CommitmentConfig,
 | 
			
		||||
    ) -> RpcResult<UiTokenAmount> {
 | 
			
		||||
        self.send(
 | 
			
		||||
            RpcRequest::GetTokenAccountBalance,
 | 
			
		||||
            json!([pubkey.to_string(), commitment_config]),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_token_accounts_by_delegate(
 | 
			
		||||
        &self,
 | 
			
		||||
        delegate: &Pubkey,
 | 
			
		||||
        token_account_filter: TokenAccountsFilter,
 | 
			
		||||
    ) -> ClientResult<Vec<RpcKeyedAccount>> {
 | 
			
		||||
        Ok(self
 | 
			
		||||
            .get_token_accounts_by_delegate_with_commitment(
 | 
			
		||||
                delegate,
 | 
			
		||||
                token_account_filter,
 | 
			
		||||
                CommitmentConfig::default(),
 | 
			
		||||
            )?
 | 
			
		||||
            .value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_token_accounts_by_delegate_with_commitment(
 | 
			
		||||
        &self,
 | 
			
		||||
        delegate: &Pubkey,
 | 
			
		||||
        token_account_filter: TokenAccountsFilter,
 | 
			
		||||
        commitment_config: CommitmentConfig,
 | 
			
		||||
    ) -> RpcResult<Vec<RpcKeyedAccount>> {
 | 
			
		||||
        let token_account_filter = match token_account_filter {
 | 
			
		||||
            TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()),
 | 
			
		||||
            TokenAccountsFilter::ProgramId(program_id) => {
 | 
			
		||||
                RpcTokenAccountsFilter::ProgramId(program_id.to_string())
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let config = RpcAccountInfoConfig {
 | 
			
		||||
            encoding: Some(UiAccountEncoding::JsonParsed),
 | 
			
		||||
            commitment: Some(commitment_config),
 | 
			
		||||
            data_slice: None,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.send(
 | 
			
		||||
            RpcRequest::GetTokenAccountsByOwner,
 | 
			
		||||
            json!([delegate.to_string(), token_account_filter, config]),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_token_accounts_by_owner(
 | 
			
		||||
        &self,
 | 
			
		||||
        owner: &Pubkey,
 | 
			
		||||
        token_account_filter: TokenAccountsFilter,
 | 
			
		||||
    ) -> ClientResult<Vec<RpcKeyedAccount>> {
 | 
			
		||||
        Ok(self
 | 
			
		||||
            .get_token_accounts_by_owner_with_commitment(
 | 
			
		||||
                owner,
 | 
			
		||||
                token_account_filter,
 | 
			
		||||
                CommitmentConfig::default(),
 | 
			
		||||
            )?
 | 
			
		||||
            .value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_token_accounts_by_owner_with_commitment(
 | 
			
		||||
        &self,
 | 
			
		||||
        owner: &Pubkey,
 | 
			
		||||
        token_account_filter: TokenAccountsFilter,
 | 
			
		||||
        commitment_config: CommitmentConfig,
 | 
			
		||||
    ) -> RpcResult<Vec<RpcKeyedAccount>> {
 | 
			
		||||
        let token_account_filter = match token_account_filter {
 | 
			
		||||
            TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()),
 | 
			
		||||
            TokenAccountsFilter::ProgramId(program_id) => {
 | 
			
		||||
                RpcTokenAccountsFilter::ProgramId(program_id.to_string())
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let config = RpcAccountInfoConfig {
 | 
			
		||||
            encoding: Some(UiAccountEncoding::JsonParsed),
 | 
			
		||||
            commitment: Some(commitment_config),
 | 
			
		||||
            data_slice: None,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.send(
 | 
			
		||||
            RpcRequest::GetTokenAccountsByOwner,
 | 
			
		||||
            json!([owner.to_string(), token_account_filter, config]),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_token_supply(&self, mint: &Pubkey) -> ClientResult<UiTokenAmount> {
 | 
			
		||||
        Ok(self
 | 
			
		||||
            .get_token_supply_with_commitment(mint, CommitmentConfig::default())?
 | 
			
		||||
            .value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_token_supply_with_commitment(
 | 
			
		||||
        &self,
 | 
			
		||||
        mint: &Pubkey,
 | 
			
		||||
        commitment_config: CommitmentConfig,
 | 
			
		||||
    ) -> RpcResult<UiTokenAmount> {
 | 
			
		||||
        self.send(
 | 
			
		||||
            RpcRequest::GetTokenSupply,
 | 
			
		||||
            json!([mint.to_string(), commitment_config]),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn poll_balance_with_timeout_and_commitment(
 | 
			
		||||
        &self,
 | 
			
		||||
        pubkey: &Pubkey,
 | 
			
		||||
@@ -913,69 +1050,66 @@ impl RpcClient {
 | 
			
		||||
    pub fn send_and_confirm_transaction_with_spinner(
 | 
			
		||||
        &self,
 | 
			
		||||
        transaction: &Transaction,
 | 
			
		||||
    ) -> ClientResult<Signature> {
 | 
			
		||||
        self.send_and_confirm_transaction_with_spinner_and_config(
 | 
			
		||||
            transaction,
 | 
			
		||||
            RpcSendTransactionConfig::default(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn send_and_confirm_transaction_with_spinner_and_config(
 | 
			
		||||
        &self,
 | 
			
		||||
        transaction: &Transaction,
 | 
			
		||||
        config: RpcSendTransactionConfig,
 | 
			
		||||
    ) -> ClientResult<Signature> {
 | 
			
		||||
        let mut confirmations = 0;
 | 
			
		||||
 | 
			
		||||
        let progress_bar = new_spinner_progress_bar();
 | 
			
		||||
 | 
			
		||||
        let mut send_retries = 20;
 | 
			
		||||
        let signature = loop {
 | 
			
		||||
            progress_bar.set_message(&format!(
 | 
			
		||||
                "[{}/{}] Finalizing transaction {}",
 | 
			
		||||
                confirmations,
 | 
			
		||||
                MAX_LOCKOUT_HISTORY + 1,
 | 
			
		||||
                transaction.signatures[0],
 | 
			
		||||
            ));
 | 
			
		||||
            let mut status_retries = 15;
 | 
			
		||||
            let (signature, status) = loop {
 | 
			
		||||
                let signature = self.send_transaction(transaction)?;
 | 
			
		||||
 | 
			
		||||
                // Get recent commitment in order to count confirmations for successful transactions
 | 
			
		||||
                let status = self
 | 
			
		||||
                    .get_signature_status_with_commitment(&signature, CommitmentConfig::recent())?;
 | 
			
		||||
                if status.is_none() {
 | 
			
		||||
                    status_retries -= 1;
 | 
			
		||||
                    if status_retries == 0 {
 | 
			
		||||
                        break (signature, status);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
        progress_bar.set_message(&format!(
 | 
			
		||||
            "[{}/{}] Finalizing transaction {}",
 | 
			
		||||
            confirmations,
 | 
			
		||||
            MAX_LOCKOUT_HISTORY + 1,
 | 
			
		||||
            transaction.signatures[0],
 | 
			
		||||
        ));
 | 
			
		||||
        let recent_blockhash = transaction.message.recent_blockhash;
 | 
			
		||||
        let signature = self.send_transaction_with_config(transaction, config)?;
 | 
			
		||||
        let (signature, status) = loop {
 | 
			
		||||
            // Get recent commitment in order to count confirmations for successful transactions
 | 
			
		||||
            let status =
 | 
			
		||||
                self.get_signature_status_with_commitment(&signature, CommitmentConfig::recent())?;
 | 
			
		||||
            if status.is_none() {
 | 
			
		||||
                if self
 | 
			
		||||
                    .get_fee_calculator_for_blockhash_with_commitment(
 | 
			
		||||
                        &recent_blockhash,
 | 
			
		||||
                        CommitmentConfig::recent(),
 | 
			
		||||
                    )?
 | 
			
		||||
                    .value
 | 
			
		||||
                    .is_none()
 | 
			
		||||
                {
 | 
			
		||||
                    break (signature, status);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if cfg!(not(test)) {
 | 
			
		||||
                    sleep(Duration::from_millis(500));
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            send_retries = if let Some(result) = status.clone() {
 | 
			
		||||
                match result {
 | 
			
		||||
                    Ok(_) => 0,
 | 
			
		||||
                    // If transaction errors, return right away; no point in counting confirmations
 | 
			
		||||
                    Err(_) => 0,
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                send_retries - 1
 | 
			
		||||
            };
 | 
			
		||||
            if send_retries == 0 {
 | 
			
		||||
                if let Some(result) = status {
 | 
			
		||||
                    match result {
 | 
			
		||||
                        Ok(_) => {
 | 
			
		||||
                            break signature;
 | 
			
		||||
                        }
 | 
			
		||||
                        Err(err) => {
 | 
			
		||||
                            return Err(err.into());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    return Err(RpcError::ForUser(
 | 
			
		||||
                        "unable to confirm transaction. \
 | 
			
		||||
                            This can happen in situations such as transaction \
 | 
			
		||||
                            expiration and insufficient fee-payer funds"
 | 
			
		||||
                            .to_string(),
 | 
			
		||||
                    )
 | 
			
		||||
                    .into());
 | 
			
		||||
                }
 | 
			
		||||
                break (signature, status);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if cfg!(not(test)) {
 | 
			
		||||
                sleep(Duration::from_millis(500));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        if let Some(result) = status {
 | 
			
		||||
            if let Err(err) = result {
 | 
			
		||||
                return Err(err.into());
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            return Err(RpcError::ForUser(
 | 
			
		||||
                "unable to confirm transaction. \
 | 
			
		||||
                                      This can happen in situations such as transaction expiration \
 | 
			
		||||
                                      and insufficient fee-payer funds"
 | 
			
		||||
                    .to_string(),
 | 
			
		||||
            )
 | 
			
		||||
            .into());
 | 
			
		||||
        }
 | 
			
		||||
        let now = Instant::now();
 | 
			
		||||
        loop {
 | 
			
		||||
            // Return when default (max) commitment is reached
 | 
			
		||||
@@ -1023,6 +1157,13 @@ impl RpcClient {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub struct GetConfirmedSignaturesForAddress2Config {
 | 
			
		||||
    pub before: Option<Signature>,
 | 
			
		||||
    pub until: Option<Signature>,
 | 
			
		||||
    pub limit: Option<usize>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn new_spinner_progress_bar() -> ProgressBar {
 | 
			
		||||
    let progress_bar = ProgressBar::new(42);
 | 
			
		||||
    progress_bar
 | 
			
		||||
@@ -1039,6 +1180,31 @@ pub fn get_rpc_request_str(rpc_addr: SocketAddr, tls: bool) -> String {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_keyed_accounts(
 | 
			
		||||
    accounts: Vec<RpcKeyedAccount>,
 | 
			
		||||
    request: RpcRequest,
 | 
			
		||||
) -> ClientResult<Vec<(Pubkey, Account)>> {
 | 
			
		||||
    let mut pubkey_accounts: Vec<(Pubkey, Account)> = Vec::new();
 | 
			
		||||
    for RpcKeyedAccount { pubkey, account } in accounts.into_iter() {
 | 
			
		||||
        let pubkey = pubkey.parse().map_err(|_| {
 | 
			
		||||
            ClientError::new_with_request(
 | 
			
		||||
                RpcError::ParseError("Pubkey".to_string()).into(),
 | 
			
		||||
                request,
 | 
			
		||||
            )
 | 
			
		||||
        })?;
 | 
			
		||||
        pubkey_accounts.push((
 | 
			
		||||
            pubkey,
 | 
			
		||||
            account.decode().ok_or_else(|| {
 | 
			
		||||
                ClientError::new_with_request(
 | 
			
		||||
                    RpcError::ParseError("Account from rpc".to_string()).into(),
 | 
			
		||||
                    request,
 | 
			
		||||
                )
 | 
			
		||||
            })?,
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    Ok(pubkey_accounts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,9 @@
 | 
			
		||||
use solana_sdk::commitment_config::CommitmentConfig;
 | 
			
		||||
use crate::rpc_filter::RpcFilterType;
 | 
			
		||||
use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig};
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    clock::Epoch,
 | 
			
		||||
    commitment_config::{CommitmentConfig, CommitmentLevel},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
@@ -6,10 +11,21 @@ pub struct RpcSignatureStatusConfig {
 | 
			
		||||
    pub search_transaction_history: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcSendTransactionConfig {
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub skip_preflight: bool,
 | 
			
		||||
    pub preflight_commitment: Option<CommitmentLevel>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcSimulateTransactionConfig {
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub sig_verify: bool,
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    pub commitment: Option<CommitmentConfig>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
@@ -26,3 +42,43 @@ pub struct RpcLargestAccountsConfig {
 | 
			
		||||
    pub commitment: Option<CommitmentConfig>,
 | 
			
		||||
    pub filter: Option<RpcLargestAccountsFilter>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcStakeConfig {
 | 
			
		||||
    pub epoch: Option<Epoch>,
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    pub commitment: Option<CommitmentConfig>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcAccountInfoConfig {
 | 
			
		||||
    pub encoding: Option<UiAccountEncoding>,
 | 
			
		||||
    pub data_slice: Option<UiDataSliceConfig>,
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    pub commitment: Option<CommitmentConfig>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcProgramAccountsConfig {
 | 
			
		||||
    pub filters: Option<Vec<RpcFilterType>>,
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    pub account_config: RpcAccountInfoConfig,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum RpcTokenAccountsFilter {
 | 
			
		||||
    Mint(String),
 | 
			
		||||
    ProgramId(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcGetConfirmedSignaturesForAddress2Config {
 | 
			
		||||
    pub before: Option<String>, // Signature as base-58 string
 | 
			
		||||
    pub until: Option<String>,  // Signature as base-58 string
 | 
			
		||||
    pub limit: Option<usize>,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										143
									
								
								client/src/rpc_filter.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								client/src/rpc_filter.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum RpcFilterType {
 | 
			
		||||
    DataSize(u64),
 | 
			
		||||
    Memcmp(Memcmp),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RpcFilterType {
 | 
			
		||||
    pub fn verify(&self) -> Result<(), RpcFilterError> {
 | 
			
		||||
        match self {
 | 
			
		||||
            RpcFilterType::DataSize(_) => Ok(()),
 | 
			
		||||
            RpcFilterType::Memcmp(compare) => {
 | 
			
		||||
                let encoding = compare.encoding.as_ref().unwrap_or(&MemcmpEncoding::Binary);
 | 
			
		||||
                match encoding {
 | 
			
		||||
                    MemcmpEncoding::Binary => {
 | 
			
		||||
                        let MemcmpEncodedBytes::Binary(bytes) = &compare.bytes;
 | 
			
		||||
                        bs58::decode(&bytes)
 | 
			
		||||
                            .into_vec()
 | 
			
		||||
                            .map(|_| ())
 | 
			
		||||
                            .map_err(|e| e.into())
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Error, Debug)]
 | 
			
		||||
pub enum RpcFilterError {
 | 
			
		||||
    #[error("bs58 decode error")]
 | 
			
		||||
    DecodeError(#[from] bs58::decode::Error),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum MemcmpEncoding {
 | 
			
		||||
    Binary,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase", untagged)]
 | 
			
		||||
pub enum MemcmpEncodedBytes {
 | 
			
		||||
    Binary(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
pub struct Memcmp {
 | 
			
		||||
    /// Data offset to begin match
 | 
			
		||||
    pub offset: usize,
 | 
			
		||||
    /// Bytes, encoded with specified encoding, or default Binary
 | 
			
		||||
    pub bytes: MemcmpEncodedBytes,
 | 
			
		||||
    /// Optional encoding specification
 | 
			
		||||
    pub encoding: Option<MemcmpEncoding>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Memcmp {
 | 
			
		||||
    pub fn bytes_match(&self, data: &[u8]) -> bool {
 | 
			
		||||
        match &self.bytes {
 | 
			
		||||
            MemcmpEncodedBytes::Binary(bytes) => {
 | 
			
		||||
                let bytes = bs58::decode(bytes).into_vec();
 | 
			
		||||
                if bytes.is_err() {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                let bytes = bytes.unwrap();
 | 
			
		||||
                if self.offset > data.len() {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                if data[self.offset..].len() < bytes.len() {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                data[self.offset..self.offset + bytes.len()] == bytes[..]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_bytes_match() {
 | 
			
		||||
        let data = vec![1, 2, 3, 4, 5];
 | 
			
		||||
 | 
			
		||||
        // Exact match of data succeeds
 | 
			
		||||
        assert!(Memcmp {
 | 
			
		||||
            offset: 0,
 | 
			
		||||
            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![1, 2, 3, 4, 5]).into_string()),
 | 
			
		||||
            encoding: None,
 | 
			
		||||
        }
 | 
			
		||||
        .bytes_match(&data));
 | 
			
		||||
 | 
			
		||||
        // Partial match of data succeeds
 | 
			
		||||
        assert!(Memcmp {
 | 
			
		||||
            offset: 0,
 | 
			
		||||
            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![1, 2]).into_string()),
 | 
			
		||||
            encoding: None,
 | 
			
		||||
        }
 | 
			
		||||
        .bytes_match(&data));
 | 
			
		||||
 | 
			
		||||
        // Offset partial match of data succeeds
 | 
			
		||||
        assert!(Memcmp {
 | 
			
		||||
            offset: 2,
 | 
			
		||||
            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![3, 4]).into_string()),
 | 
			
		||||
            encoding: None,
 | 
			
		||||
        }
 | 
			
		||||
        .bytes_match(&data));
 | 
			
		||||
 | 
			
		||||
        // Incorrect partial match of data fails
 | 
			
		||||
        assert!(!Memcmp {
 | 
			
		||||
            offset: 0,
 | 
			
		||||
            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![2]).into_string()),
 | 
			
		||||
            encoding: None,
 | 
			
		||||
        }
 | 
			
		||||
        .bytes_match(&data));
 | 
			
		||||
 | 
			
		||||
        // Bytes overrun data fails
 | 
			
		||||
        assert!(!Memcmp {
 | 
			
		||||
            offset: 2,
 | 
			
		||||
            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![3, 4, 5, 6]).into_string()),
 | 
			
		||||
            encoding: None,
 | 
			
		||||
        }
 | 
			
		||||
        .bytes_match(&data));
 | 
			
		||||
 | 
			
		||||
        // Offset outside data fails
 | 
			
		||||
        assert!(!Memcmp {
 | 
			
		||||
            offset: 6,
 | 
			
		||||
            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![5]).into_string()),
 | 
			
		||||
            encoding: None,
 | 
			
		||||
        }
 | 
			
		||||
        .bytes_match(&data));
 | 
			
		||||
 | 
			
		||||
        // Invalid base-58 fails
 | 
			
		||||
        assert!(!Memcmp {
 | 
			
		||||
            offset: 0,
 | 
			
		||||
            bytes: MemcmpEncodedBytes::Binary("III".to_string()),
 | 
			
		||||
            encoding: None,
 | 
			
		||||
        }
 | 
			
		||||
        .bytes_match(&data));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
use serde_json::{json, Value};
 | 
			
		||||
use solana_sdk::pubkey::Pubkey;
 | 
			
		||||
use std::fmt;
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
@@ -13,18 +14,23 @@ pub enum RpcRequest {
 | 
			
		||||
    GetConfirmedBlock,
 | 
			
		||||
    GetConfirmedBlocks,
 | 
			
		||||
    GetConfirmedSignaturesForAddress,
 | 
			
		||||
    GetConfirmedSignaturesForAddress2,
 | 
			
		||||
    GetConfirmedTransaction,
 | 
			
		||||
    GetEpochInfo,
 | 
			
		||||
    GetEpochSchedule,
 | 
			
		||||
    GetGenesisHash,
 | 
			
		||||
    GetIdentity,
 | 
			
		||||
    GetInflation,
 | 
			
		||||
    GetLargestAccounts,
 | 
			
		||||
    GetLeaderSchedule,
 | 
			
		||||
    GetProgramAccounts,
 | 
			
		||||
    GetRecentBlockhash,
 | 
			
		||||
    GetFeeCalculatorForBlockhash,
 | 
			
		||||
    GetFeeRateGovernor,
 | 
			
		||||
    GetFees,
 | 
			
		||||
    GetGenesisHash,
 | 
			
		||||
    GetIdentity,
 | 
			
		||||
    GetInflationGovernor,
 | 
			
		||||
    GetInflationRate,
 | 
			
		||||
    GetLargestAccounts,
 | 
			
		||||
    GetLeaderSchedule,
 | 
			
		||||
    GetMinimumBalanceForRentExemption,
 | 
			
		||||
    GetMultipleAccounts,
 | 
			
		||||
    GetProgramAccounts,
 | 
			
		||||
    GetRecentBlockhash,
 | 
			
		||||
    GetSignatureStatuses,
 | 
			
		||||
    GetSlot,
 | 
			
		||||
    GetSlotLeader,
 | 
			
		||||
@@ -33,17 +39,20 @@ pub enum RpcRequest {
 | 
			
		||||
    GetSlotsPerSegment,
 | 
			
		||||
    GetStoragePubkeysForSlot,
 | 
			
		||||
    GetSupply,
 | 
			
		||||
    GetTokenAccountBalance,
 | 
			
		||||
    GetTokenAccountsByDelegate,
 | 
			
		||||
    GetTokenAccountsByOwner,
 | 
			
		||||
    GetTokenSupply,
 | 
			
		||||
    GetTotalSupply,
 | 
			
		||||
    GetTransactionCount,
 | 
			
		||||
    GetVersion,
 | 
			
		||||
    GetVoteAccounts,
 | 
			
		||||
    MinimumLedgerSlot,
 | 
			
		||||
    RegisterNode,
 | 
			
		||||
    RequestAirdrop,
 | 
			
		||||
    SendTransaction,
 | 
			
		||||
    SimulateTransaction,
 | 
			
		||||
    SignVote,
 | 
			
		||||
    GetMinimumBalanceForRentExemption,
 | 
			
		||||
    MinimumLedgerSlot,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for RpcRequest {
 | 
			
		||||
@@ -58,18 +67,23 @@ impl fmt::Display for RpcRequest {
 | 
			
		||||
            RpcRequest::GetConfirmedBlock => "getConfirmedBlock",
 | 
			
		||||
            RpcRequest::GetConfirmedBlocks => "getConfirmedBlocks",
 | 
			
		||||
            RpcRequest::GetConfirmedSignaturesForAddress => "getConfirmedSignaturesForAddress",
 | 
			
		||||
            RpcRequest::GetConfirmedSignaturesForAddress2 => "getConfirmedSignaturesForAddress2",
 | 
			
		||||
            RpcRequest::GetConfirmedTransaction => "getConfirmedTransaction",
 | 
			
		||||
            RpcRequest::GetEpochInfo => "getEpochInfo",
 | 
			
		||||
            RpcRequest::GetEpochSchedule => "getEpochSchedule",
 | 
			
		||||
            RpcRequest::GetGenesisHash => "getGenesisHash",
 | 
			
		||||
            RpcRequest::GetIdentity => "getIdentity",
 | 
			
		||||
            RpcRequest::GetInflation => "getInflation",
 | 
			
		||||
            RpcRequest::GetLargestAccounts => "getLargestAccounts",
 | 
			
		||||
            RpcRequest::GetLeaderSchedule => "getLeaderSchedule",
 | 
			
		||||
            RpcRequest::GetProgramAccounts => "getProgramAccounts",
 | 
			
		||||
            RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
 | 
			
		||||
            RpcRequest::GetFeeCalculatorForBlockhash => "getFeeCalculatorForBlockhash",
 | 
			
		||||
            RpcRequest::GetFeeRateGovernor => "getFeeRateGovernor",
 | 
			
		||||
            RpcRequest::GetFees => "getFees",
 | 
			
		||||
            RpcRequest::GetGenesisHash => "getGenesisHash",
 | 
			
		||||
            RpcRequest::GetIdentity => "getIdentity",
 | 
			
		||||
            RpcRequest::GetInflationGovernor => "getInflationGovernor",
 | 
			
		||||
            RpcRequest::GetInflationRate => "getInflationRate",
 | 
			
		||||
            RpcRequest::GetLargestAccounts => "getLargestAccounts",
 | 
			
		||||
            RpcRequest::GetLeaderSchedule => "getLeaderSchedule",
 | 
			
		||||
            RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption",
 | 
			
		||||
            RpcRequest::GetMultipleAccounts => "getMultipleAccounts",
 | 
			
		||||
            RpcRequest::GetProgramAccounts => "getProgramAccounts",
 | 
			
		||||
            RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
 | 
			
		||||
            RpcRequest::GetSignatureStatuses => "getSignatureStatuses",
 | 
			
		||||
            RpcRequest::GetSlot => "getSlot",
 | 
			
		||||
            RpcRequest::GetSlotLeader => "getSlotLeader",
 | 
			
		||||
@@ -78,26 +92,35 @@ impl fmt::Display for RpcRequest {
 | 
			
		||||
            RpcRequest::GetSlotsPerSegment => "getSlotsPerSegment",
 | 
			
		||||
            RpcRequest::GetStoragePubkeysForSlot => "getStoragePubkeysForSlot",
 | 
			
		||||
            RpcRequest::GetSupply => "getSupply",
 | 
			
		||||
            RpcRequest::GetTokenAccountBalance => "getTokenAccountBalance",
 | 
			
		||||
            RpcRequest::GetTokenAccountsByDelegate => "getTokenAccountsByDelegate",
 | 
			
		||||
            RpcRequest::GetTokenAccountsByOwner => "getTokenAccountsByOwner",
 | 
			
		||||
            RpcRequest::GetTokenSupply => "getTokenSupply",
 | 
			
		||||
            RpcRequest::GetTotalSupply => "getTotalSupply",
 | 
			
		||||
            RpcRequest::GetTransactionCount => "getTransactionCount",
 | 
			
		||||
            RpcRequest::GetVersion => "getVersion",
 | 
			
		||||
            RpcRequest::GetVoteAccounts => "getVoteAccounts",
 | 
			
		||||
            RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot",
 | 
			
		||||
            RpcRequest::RegisterNode => "registerNode",
 | 
			
		||||
            RpcRequest::RequestAirdrop => "requestAirdrop",
 | 
			
		||||
            RpcRequest::SendTransaction => "sendTransaction",
 | 
			
		||||
            RpcRequest::SimulateTransaction => "simulateTransaction",
 | 
			
		||||
            RpcRequest::SignVote => "signVote",
 | 
			
		||||
            RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption",
 | 
			
		||||
            RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        write!(f, "{}", method)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub const NUM_LARGEST_ACCOUNTS: usize = 20;
 | 
			
		||||
pub const MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS: usize = 256;
 | 
			
		||||
pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE: u64 = 10_000;
 | 
			
		||||
pub const MAX_GET_CONFIRMED_BLOCKS_RANGE: u64 = 500_000;
 | 
			
		||||
pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT: usize = 1_000;
 | 
			
		||||
pub const MAX_MULTIPLE_ACCOUNTS: usize = 100;
 | 
			
		||||
pub const NUM_LARGEST_ACCOUNTS: usize = 20;
 | 
			
		||||
 | 
			
		||||
// Validators that are this number of slots behind are considered delinquent
 | 
			
		||||
pub const DELINQUENT_VALIDATOR_SLOT_DISTANCE: u64 = 128;
 | 
			
		||||
 | 
			
		||||
impl RpcRequest {
 | 
			
		||||
    pub(crate) fn build_request_json(self, id: u64, params: Value) -> Value {
 | 
			
		||||
@@ -123,9 +146,16 @@ pub enum RpcError {
 | 
			
		||||
    ForUser(String), /* "direct-to-user message" */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
pub enum TokenAccountsFilter {
 | 
			
		||||
    Mint(Pubkey),
 | 
			
		||||
    ProgramId(Pubkey),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::rpc_config::RpcTokenAccountsFilter;
 | 
			
		||||
    use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel};
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
@@ -144,10 +174,6 @@ mod tests {
 | 
			
		||||
        let request = test_request.build_request_json(1, Value::Null);
 | 
			
		||||
        assert_eq!(request["method"], "getEpochInfo");
 | 
			
		||||
 | 
			
		||||
        let test_request = RpcRequest::GetInflation;
 | 
			
		||||
        let request = test_request.build_request_json(1, Value::Null);
 | 
			
		||||
        assert_eq!(request["method"], "getInflation");
 | 
			
		||||
 | 
			
		||||
        let test_request = RpcRequest::GetRecentBlockhash;
 | 
			
		||||
        let request = test_request.build_request_json(1, Value::Null);
 | 
			
		||||
        assert_eq!(request["method"], "getRecentBlockhash");
 | 
			
		||||
@@ -194,5 +220,16 @@ mod tests {
 | 
			
		||||
        let request =
 | 
			
		||||
            test_request.build_request_json(1, json!([addr.clone(), commitment_config.clone()]));
 | 
			
		||||
        assert_eq!(request["params"], json!([addr, commitment_config]));
 | 
			
		||||
 | 
			
		||||
        // Test request with CommitmentConfig and params
 | 
			
		||||
        let test_request = RpcRequest::GetTokenAccountsByOwner;
 | 
			
		||||
        let mint = Pubkey::new_rand();
 | 
			
		||||
        let token_account_filter = RpcTokenAccountsFilter::Mint(mint.to_string());
 | 
			
		||||
        let request = test_request
 | 
			
		||||
            .build_request_json(1, json!([addr, token_account_filter, commitment_config]));
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            request["params"],
 | 
			
		||||
            json!([addr, token_account_filter, commitment_config])
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
use crate::{client_error, rpc_request::RpcError};
 | 
			
		||||
use crate::client_error;
 | 
			
		||||
use solana_account_decoder::{parse_token::UiTokenAmount, UiAccount};
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    account::Account,
 | 
			
		||||
    clock::{Epoch, Slot},
 | 
			
		||||
    fee_calculator::{FeeCalculator, FeeRateGovernor},
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    inflation::Inflation,
 | 
			
		||||
    transaction::{Result, TransactionError},
 | 
			
		||||
};
 | 
			
		||||
use std::{collections::HashMap, net::SocketAddr, str::FromStr};
 | 
			
		||||
use solana_transaction_status::ConfirmedTransactionStatusWithSignature;
 | 
			
		||||
use std::{collections::HashMap, net::SocketAddr};
 | 
			
		||||
 | 
			
		||||
pub type RpcResult<T> = client_error::Result<Response<T>>;
 | 
			
		||||
 | 
			
		||||
@@ -35,6 +36,14 @@ pub struct RpcBlockhashFeeCalculator {
 | 
			
		||||
    pub fee_calculator: FeeCalculator,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcFees {
 | 
			
		||||
    pub blockhash: String,
 | 
			
		||||
    pub fee_calculator: FeeCalculator,
 | 
			
		||||
    pub last_valid_slot: Slot,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcFeeCalculator {
 | 
			
		||||
@@ -47,11 +56,42 @@ pub struct RpcFeeRateGovernor {
 | 
			
		||||
    pub fee_rate_governor: FeeRateGovernor,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcInflationGovernor {
 | 
			
		||||
    pub initial: f64,
 | 
			
		||||
    pub terminal: f64,
 | 
			
		||||
    pub taper: f64,
 | 
			
		||||
    pub foundation: f64,
 | 
			
		||||
    pub foundation_term: f64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<Inflation> for RpcInflationGovernor {
 | 
			
		||||
    fn from(inflation: Inflation) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            initial: inflation.initial,
 | 
			
		||||
            terminal: inflation.terminal,
 | 
			
		||||
            taper: inflation.taper,
 | 
			
		||||
            foundation: inflation.foundation,
 | 
			
		||||
            foundation_term: inflation.foundation_term,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcInflationRate {
 | 
			
		||||
    pub total: f64,
 | 
			
		||||
    pub validator: f64,
 | 
			
		||||
    pub foundation: f64,
 | 
			
		||||
    pub epoch: Epoch,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcKeyedAccount {
 | 
			
		||||
    pub pubkey: String,
 | 
			
		||||
    pub account: RpcAccount,
 | 
			
		||||
    pub account: UiAccount,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug)]
 | 
			
		||||
@@ -60,43 +100,6 @@ pub struct RpcSignatureResult {
 | 
			
		||||
    pub err: Option<TransactionError>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A duplicate representation of a Message for pretty JSON serialization
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcAccount {
 | 
			
		||||
    pub lamports: u64,
 | 
			
		||||
    pub data: String,
 | 
			
		||||
    pub owner: String,
 | 
			
		||||
    pub executable: bool,
 | 
			
		||||
    pub rent_epoch: Epoch,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RpcAccount {
 | 
			
		||||
    pub fn encode(account: Account) -> Self {
 | 
			
		||||
        RpcAccount {
 | 
			
		||||
            lamports: account.lamports,
 | 
			
		||||
            data: bs58::encode(account.data.clone()).into_string(),
 | 
			
		||||
            owner: account.owner.to_string(),
 | 
			
		||||
            executable: account.executable,
 | 
			
		||||
            rent_epoch: account.rent_epoch,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn decode(&self) -> std::result::Result<Account, RpcError> {
 | 
			
		||||
        Ok(Account {
 | 
			
		||||
            lamports: self.lamports,
 | 
			
		||||
            data: bs58::decode(self.data.clone()).into_vec().map_err(|_| {
 | 
			
		||||
                RpcError::RpcRequestError("Could not parse encoded account data".to_string())
 | 
			
		||||
            })?,
 | 
			
		||||
            owner: Pubkey::from_str(&self.owner).map_err(|_| {
 | 
			
		||||
                RpcError::RpcRequestError("Could not parse encoded account owner".to_string())
 | 
			
		||||
            })?,
 | 
			
		||||
            executable: self.executable,
 | 
			
		||||
            rent_epoch: self.rent_epoch,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug)]
 | 
			
		||||
pub struct RpcContactInfo {
 | 
			
		||||
    /// Pubkey of the node as a base-58 string
 | 
			
		||||
@@ -171,6 +174,13 @@ pub struct RpcSignatureConfirmation {
 | 
			
		||||
    pub status: Result<()>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcSimulateTransactionResult {
 | 
			
		||||
    pub err: Option<TransactionError>,
 | 
			
		||||
    pub logs: Option<Vec<String>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcStorageTurn {
 | 
			
		||||
@@ -193,3 +203,54 @@ pub struct RpcSupply {
 | 
			
		||||
    pub non_circulating: u64,
 | 
			
		||||
    pub non_circulating_accounts: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub enum StakeActivationState {
 | 
			
		||||
    Activating,
 | 
			
		||||
    Active,
 | 
			
		||||
    Deactivating,
 | 
			
		||||
    Inactive,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcStakeActivation {
 | 
			
		||||
    pub state: StakeActivationState,
 | 
			
		||||
    pub active: u64,
 | 
			
		||||
    pub inactive: u64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcTokenAccountBalance {
 | 
			
		||||
    pub address: String,
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    pub amount: UiTokenAmount,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct RpcConfirmedTransactionStatusWithSignature {
 | 
			
		||||
    pub signature: String,
 | 
			
		||||
    pub slot: Slot,
 | 
			
		||||
    pub err: Option<TransactionError>,
 | 
			
		||||
    pub memo: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<ConfirmedTransactionStatusWithSignature> for RpcConfirmedTransactionStatusWithSignature {
 | 
			
		||||
    fn from(value: ConfirmedTransactionStatusWithSignature) -> Self {
 | 
			
		||||
        let ConfirmedTransactionStatusWithSignature {
 | 
			
		||||
            signature,
 | 
			
		||||
            slot,
 | 
			
		||||
            err,
 | 
			
		||||
            memo,
 | 
			
		||||
        } = value;
 | 
			
		||||
        Self {
 | 
			
		||||
            signature: signature.to_string(),
 | 
			
		||||
            slot,
 | 
			
		||||
            err,
 | 
			
		||||
            memo,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ use log::*;
 | 
			
		||||
use solana_sdk::{
 | 
			
		||||
    account::Account,
 | 
			
		||||
    client::{AsyncClient, Client, SyncClient},
 | 
			
		||||
    clock::MAX_PROCESSING_AGE,
 | 
			
		||||
    clock::{Slot, MAX_PROCESSING_AGE},
 | 
			
		||||
    commitment_config::CommitmentConfig,
 | 
			
		||||
    epoch_info::EpochInfo,
 | 
			
		||||
    fee_calculator::{FeeCalculator, FeeRateGovernor},
 | 
			
		||||
@@ -357,7 +357,7 @@ impl Client for ThinClient {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SyncClient for ThinClient {
 | 
			
		||||
    fn send_message<T: Signers>(
 | 
			
		||||
    fn send_and_confirm_message<T: Signers>(
 | 
			
		||||
        &self,
 | 
			
		||||
        keypairs: &T,
 | 
			
		||||
        message: Message,
 | 
			
		||||
@@ -368,16 +368,16 @@ impl SyncClient for ThinClient {
 | 
			
		||||
        Ok(signature)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn send_instruction(
 | 
			
		||||
    fn send_and_confirm_instruction(
 | 
			
		||||
        &self,
 | 
			
		||||
        keypair: &Keypair,
 | 
			
		||||
        instruction: Instruction,
 | 
			
		||||
    ) -> TransportResult<Signature> {
 | 
			
		||||
        let message = Message::new(&[instruction]);
 | 
			
		||||
        self.send_message(&[keypair], message)
 | 
			
		||||
        let message = Message::new(&[instruction], Some(&keypair.pubkey()));
 | 
			
		||||
        self.send_and_confirm_message(&[keypair], message)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn transfer(
 | 
			
		||||
    fn transfer_and_confirm(
 | 
			
		||||
        &self,
 | 
			
		||||
        lamports: u64,
 | 
			
		||||
        keypair: &Keypair,
 | 
			
		||||
@@ -385,7 +385,7 @@ impl SyncClient for ThinClient {
 | 
			
		||||
    ) -> TransportResult<Signature> {
 | 
			
		||||
        let transfer_instruction =
 | 
			
		||||
            system_instruction::transfer(&keypair.pubkey(), pubkey, lamports);
 | 
			
		||||
        self.send_instruction(keypair, transfer_instruction)
 | 
			
		||||
        self.send_and_confirm_instruction(keypair, transfer_instruction)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_account_data(&self, pubkey: &Pubkey) -> TransportResult<Option<Vec<u8>>> {
 | 
			
		||||
@@ -427,13 +427,15 @@ impl SyncClient for ThinClient {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)> {
 | 
			
		||||
        self.get_recent_blockhash_with_commitment(CommitmentConfig::default())
 | 
			
		||||
        let (blockhash, fee_calculator, _last_valid_slot) =
 | 
			
		||||
            self.get_recent_blockhash_with_commitment(CommitmentConfig::default())?;
 | 
			
		||||
        Ok((blockhash, fee_calculator))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_recent_blockhash_with_commitment(
 | 
			
		||||
        &self,
 | 
			
		||||
        commitment_config: CommitmentConfig,
 | 
			
		||||
    ) -> TransportResult<(Hash, FeeCalculator)> {
 | 
			
		||||
    ) -> TransportResult<(Hash, FeeCalculator, Slot)> {
 | 
			
		||||
        let index = self.optimizer.experiment();
 | 
			
		||||
        let now = Instant::now();
 | 
			
		||||
        let recent_blockhash =
 | 
			
		||||
@@ -441,7 +443,7 @@ impl SyncClient for ThinClient {
 | 
			
		||||
        match recent_blockhash {
 | 
			
		||||
            Ok(Response { value, .. }) => {
 | 
			
		||||
                self.optimizer.report(index, duration_as_ms(&now.elapsed()));
 | 
			
		||||
                Ok(value)
 | 
			
		||||
                Ok((value.0, value.1, value.2))
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                self.optimizer.report(index, std::u64::MAX);
 | 
			
		||||
@@ -609,7 +611,7 @@ impl AsyncClient for ThinClient {
 | 
			
		||||
        instruction: Instruction,
 | 
			
		||||
        recent_blockhash: Hash,
 | 
			
		||||
    ) -> TransportResult<Signature> {
 | 
			
		||||
        let message = Message::new(&[instruction]);
 | 
			
		||||
        let message = Message::new(&[instruction], Some(&keypair.pubkey()));
 | 
			
		||||
        self.async_send_message(&[keypair], message, recent_blockhash)
 | 
			
		||||
    }
 | 
			
		||||
    fn async_transfer(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "solana-core"
 | 
			
		||||
description = "Blockchain, Rebuilt for Scale"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
version = "1.2.32"
 | 
			
		||||
documentation = "https://docs.rs/solana"
 | 
			
		||||
homepage = "https://solana.com/"
 | 
			
		||||
readme = "../README.md"
 | 
			
		||||
@@ -14,65 +14,70 @@ edition = "2018"
 | 
			
		||||
codecov = { repository = "solana-labs/solana", branch = "master", service = "github" }
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
bincode = "1.2.1"
 | 
			
		||||
bincode = "1.3.1"
 | 
			
		||||
bv = { version = "0.11.1", features = ["serde"] }
 | 
			
		||||
bs58 = "0.3.1"
 | 
			
		||||
byteorder = "1.3.4"
 | 
			
		||||
chrono = { version = "0.4.11", features = ["serde"] }
 | 
			
		||||
core_affinity = "0.5.10"
 | 
			
		||||
crossbeam-channel = "0.4"
 | 
			
		||||
ed25519-dalek = "=1.0.0-pre.4"
 | 
			
		||||
fs_extra = "1.1.0"
 | 
			
		||||
flate2 = "1.0"
 | 
			
		||||
indexmap = "1.3"
 | 
			
		||||
itertools = "0.9.0"
 | 
			
		||||
jsonrpc-core = "14.1.0"
 | 
			
		||||
jsonrpc-core-client = { version = "14.1.0", features = ["ws"] }
 | 
			
		||||
jsonrpc-derive = "14.1.0"
 | 
			
		||||
jsonrpc-http-server = "14.1.0"
 | 
			
		||||
jsonrpc-pubsub = "14.1.0"
 | 
			
		||||
jsonrpc-ws-server = "14.1.0"
 | 
			
		||||
jsonrpc-core = "15.0.0"
 | 
			
		||||
jsonrpc-core-client = { version = "15.0.0", features = ["ws"] }
 | 
			
		||||
jsonrpc-derive = "15.0.0"
 | 
			
		||||
jsonrpc-http-server = "15.0.0"
 | 
			
		||||
jsonrpc-pubsub = "15.0.0"
 | 
			
		||||
jsonrpc-ws-server = "15.0.0"
 | 
			
		||||
log = "0.4.8"
 | 
			
		||||
num_cpus = "1.13.0"
 | 
			
		||||
num-traits = "0.2"
 | 
			
		||||
rand = "0.7.0"
 | 
			
		||||
rand_chacha = "0.2.2"
 | 
			
		||||
rayon = "1.3.0"
 | 
			
		||||
rayon = "1.4.0"
 | 
			
		||||
regex = "1.3.7"
 | 
			
		||||
serde = "1.0.110"
 | 
			
		||||
serde_derive = "1.0.103"
 | 
			
		||||
serde_json = "1.0.53"
 | 
			
		||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.2.0" }
 | 
			
		||||
solana-budget-program = { path = "../programs/budget", version = "1.2.0" }
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.0" }
 | 
			
		||||
solana-client = { path = "../client", version = "1.2.0" }
 | 
			
		||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.0" }
 | 
			
		||||
solana-faucet = { path = "../faucet", version = "1.2.0" }
 | 
			
		||||
ed25519-dalek = "=1.0.0-pre.3"
 | 
			
		||||
solana-ledger = { path = "../ledger", version = "1.2.0" }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.0" }
 | 
			
		||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.2.0" }
 | 
			
		||||
solana-metrics = { path = "../metrics", version = "1.2.0" }
 | 
			
		||||
solana-measure = { path = "../measure", version = "1.2.0" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.0" }
 | 
			
		||||
solana-perf = { path = "../perf", version = "1.2.0" }
 | 
			
		||||
solana-runtime = { path = "../runtime", version = "1.2.0" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.0" }
 | 
			
		||||
solana-stake-program = { path = "../programs/stake", version = "1.2.0" }
 | 
			
		||||
solana-streamer = { path = "../streamer", version = "1.2.0" }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.0" }
 | 
			
		||||
solana-vote-program = { path = "../programs/vote", version = "1.2.0" }
 | 
			
		||||
solana-vote-signer = { path = "../vote-signer", version = "1.2.0" }
 | 
			
		||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.2.0" }
 | 
			
		||||
solana-account-decoder = { path = "../account-decoder", version = "1.2.32" }
 | 
			
		||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.2.32" }
 | 
			
		||||
solana-budget-program = { path = "../programs/budget", version = "1.2.32" }
 | 
			
		||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.32" }
 | 
			
		||||
solana-client = { path = "../client", version = "1.2.32" }
 | 
			
		||||
solana-faucet = { path = "../faucet", version = "1.2.32" }
 | 
			
		||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.2.32" }
 | 
			
		||||
solana-ledger = { path = "../ledger", version = "1.2.32" }
 | 
			
		||||
solana-logger = { path = "../logger", version = "1.2.32" }
 | 
			
		||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.2.32" }
 | 
			
		||||
solana-metrics = { path = "../metrics", version = "1.2.32" }
 | 
			
		||||
solana-measure = { path = "../measure", version = "1.2.32" }
 | 
			
		||||
solana-net-utils = { path = "../net-utils", version = "1.2.32" }
 | 
			
		||||
solana-perf = { path = "../perf", version = "1.2.32" }
 | 
			
		||||
solana-runtime = { path = "../runtime", version = "1.2.32" }
 | 
			
		||||
solana-sdk = { path = "../sdk", version = "1.2.32" }
 | 
			
		||||
solana-stake-program = { path = "../programs/stake", version = "1.2.32" }
 | 
			
		||||
solana-storage-bigtable = { path = "../storage-bigtable", version = "1.2.32" }
 | 
			
		||||
solana-streamer = { path = "../streamer", version = "1.2.32" }
 | 
			
		||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.2.32" }
 | 
			
		||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.32" }
 | 
			
		||||
solana-version = { path = "../version", version = "1.2.32" }
 | 
			
		||||
solana-vote-program = { path = "../programs/vote", version = "1.2.32" }
 | 
			
		||||
solana-vote-signer = { path = "../vote-signer", version = "1.2.32" }
 | 
			
		||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.2.32" }
 | 
			
		||||
spl-token-v2-0 = { package = "spl-token", version = "2.0.6", features = ["skip-no-mangle"] }
 | 
			
		||||
tempfile = "3.1.0"
 | 
			
		||||
thiserror = "1.0"
 | 
			
		||||
tokio = "0.1"
 | 
			
		||||
tokio-codec = "0.1"
 | 
			
		||||
tokio-fs = "0.1"
 | 
			
		||||
tokio-io = "0.1"
 | 
			
		||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.2.0" }
 | 
			
		||||
tokio = { version = "0.2.22", features = ["full"] }
 | 
			
		||||
tokio_01 = { version = "0.1", package = "tokio" }
 | 
			
		||||
tokio_fs_01 = { version = "0.1", package = "tokio-fs" }
 | 
			
		||||
tokio_io_01 = { version = "0.1", package = "tokio-io" }
 | 
			
		||||
trees = "0.2.1"
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
base64 = "0.12.3"
 | 
			
		||||
matches = "0.1.6"
 | 
			
		||||
reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "rustls-tls", "json"] }
 | 
			
		||||
serial_test = "0.4.0"
 | 
			
		||||
@@ -85,6 +90,9 @@ name = "banking_stage"
 | 
			
		||||
[[bench]]
 | 
			
		||||
name = "blockstore"
 | 
			
		||||
 | 
			
		||||
[[bench]]
 | 
			
		||||
name = "crds_gossip_pull"
 | 
			
		||||
 | 
			
		||||
[[bench]]
 | 
			
		||||
name = "gen_keys"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ use solana_perf::test_tx::test_tx;
 | 
			
		||||
use solana_runtime::bank::Bank;
 | 
			
		||||
use solana_sdk::genesis_config::GenesisConfig;
 | 
			
		||||
use solana_sdk::hash::Hash;
 | 
			
		||||
use solana_sdk::message::Message;
 | 
			
		||||
use solana_sdk::pubkey::Pubkey;
 | 
			
		||||
use solana_sdk::signature::Keypair;
 | 
			
		||||
use solana_sdk::signature::Signature;
 | 
			
		||||
@@ -117,9 +118,8 @@ fn make_programs_txs(txes: usize, hash: Hash) -> Vec<Transaction> {
 | 
			
		||||
                let to_key = Pubkey::new_rand();
 | 
			
		||||
                instructions.push(system_instruction::transfer(&from_key.pubkey(), &to_key, 1));
 | 
			
		||||
            }
 | 
			
		||||
            let mut new = Transaction::new_unsigned_instructions(&instructions);
 | 
			
		||||
            new.sign(&[&from_key], hash);
 | 
			
		||||
            new
 | 
			
		||||
            let message = Message::new(&instructions, Some(&from_key.pubkey()));
 | 
			
		||||
            Transaction::new(&[&from_key], message, hash)
 | 
			
		||||
        })
 | 
			
		||||
        .collect()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
extern crate test;
 | 
			
		||||
 | 
			
		||||
use rand::{thread_rng, Rng};
 | 
			
		||||
use solana_core::broadcast_stage::broadcast_metrics::TransmitShredsStats;
 | 
			
		||||
use solana_core::broadcast_stage::{broadcast_shreds, get_broadcast_peers};
 | 
			
		||||
use solana_core::cluster_info::{ClusterInfo, Node};
 | 
			
		||||
use solana_core::contact_info::ContactInfo;
 | 
			
		||||
@@ -47,7 +48,7 @@ fn broadcast_shreds_bench(bencher: &mut Bencher) {
 | 
			
		||||
            &peers_and_stakes,
 | 
			
		||||
            &peers,
 | 
			
		||||
            &last_datapoint,
 | 
			
		||||
            &mut 0,
 | 
			
		||||
            &mut TransmitShredsStats::default(),
 | 
			
		||||
        )
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								core/benches/crds_gossip_pull.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								core/benches/crds_gossip_pull.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
#![feature(test)]
 | 
			
		||||
 | 
			
		||||
extern crate test;
 | 
			
		||||
 | 
			
		||||
use rand::{thread_rng, Rng};
 | 
			
		||||
use solana_core::crds_gossip_pull::CrdsFilter;
 | 
			
		||||
use solana_sdk::hash::{Hash, HASH_BYTES};
 | 
			
		||||
use test::Bencher;
 | 
			
		||||
 | 
			
		||||
#[bench]
 | 
			
		||||
fn bench_hash_as_u64(bencher: &mut Bencher) {
 | 
			
		||||
    let mut rng = thread_rng();
 | 
			
		||||
    let hashes: Vec<_> = (0..1000)
 | 
			
		||||
        .map(|_| {
 | 
			
		||||
            let mut buf = [0u8; HASH_BYTES];
 | 
			
		||||
            rng.fill(&mut buf);
 | 
			
		||||
            Hash::new(&buf)
 | 
			
		||||
        })
 | 
			
		||||
        .collect();
 | 
			
		||||
    bencher.iter(|| {
 | 
			
		||||
        hashes
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(CrdsFilter::hash_as_u64)
 | 
			
		||||
            .collect::<Vec<_>>()
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
@@ -12,9 +12,10 @@ const NUM_ENTRIES: usize = 800;
 | 
			
		||||
 | 
			
		||||
#[bench]
 | 
			
		||||
fn bench_poh_verify_ticks(bencher: &mut Bencher) {
 | 
			
		||||
    solana_logger::setup();
 | 
			
		||||
    let zero = Hash::default();
 | 
			
		||||
    let mut cur_hash = hash(&zero.as_ref());
 | 
			
		||||
    let start = *&cur_hash;
 | 
			
		||||
    let start_hash = hash(&zero.as_ref());
 | 
			
		||||
    let mut cur_hash = start_hash;
 | 
			
		||||
 | 
			
		||||
    let mut ticks: Vec<Entry> = Vec::with_capacity(NUM_ENTRIES);
 | 
			
		||||
    for _ in 0..NUM_ENTRIES {
 | 
			
		||||
@@ -22,15 +23,15 @@ fn bench_poh_verify_ticks(bencher: &mut Bencher) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bencher.iter(|| {
 | 
			
		||||
        ticks.verify(&start);
 | 
			
		||||
        assert!(ticks.verify(&start_hash));
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[bench]
 | 
			
		||||
fn bench_poh_verify_transaction_entries(bencher: &mut Bencher) {
 | 
			
		||||
    let zero = Hash::default();
 | 
			
		||||
    let mut cur_hash = hash(&zero.as_ref());
 | 
			
		||||
    let start = *&cur_hash;
 | 
			
		||||
    let start_hash = hash(&zero.as_ref());
 | 
			
		||||
    let mut cur_hash = start_hash;
 | 
			
		||||
 | 
			
		||||
    let keypair1 = Keypair::new();
 | 
			
		||||
    let pubkey1 = keypair1.pubkey();
 | 
			
		||||
@@ -42,6 +43,6 @@ fn bench_poh_verify_transaction_entries(bencher: &mut Bencher) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bencher.iter(|| {
 | 
			
		||||
        ticks.verify(&start);
 | 
			
		||||
        assert!(ticks.verify(&start_hash));
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ fn bench_retransmitter(bencher: &mut Bencher) {
 | 
			
		||||
 | 
			
		||||
    let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100_000);
 | 
			
		||||
    let bank0 = Bank::new(&genesis_config);
 | 
			
		||||
    let bank_forks = BankForks::new(0, bank0);
 | 
			
		||||
    let bank_forks = BankForks::new(bank0);
 | 
			
		||||
    let bank = bank_forks.working_bank();
 | 
			
		||||
    let bank_forks = Arc::new(RwLock::new(bank_forks));
 | 
			
		||||
    let (packet_sender, packet_receiver) = channel();
 | 
			
		||||
 
 | 
			
		||||
@@ -15,23 +15,34 @@ pub struct AccountsBackgroundService {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const INTERVAL_MS: u64 = 100;
 | 
			
		||||
const SHRUNKEN_ACCOUNT_PER_SEC: usize = 250;
 | 
			
		||||
const SHRUNKEN_ACCOUNT_PER_INTERVAL: usize =
 | 
			
		||||
    SHRUNKEN_ACCOUNT_PER_SEC / (1000 / INTERVAL_MS as usize);
 | 
			
		||||
const CLEAN_INTERVAL_SLOTS: u64 = 100;
 | 
			
		||||
 | 
			
		||||
impl AccountsBackgroundService {
 | 
			
		||||
    pub fn new(bank_forks: Arc<RwLock<BankForks>>, exit: &Arc<AtomicBool>) -> Self {
 | 
			
		||||
        info!("AccountsBackgroundService active");
 | 
			
		||||
        let exit = exit.clone();
 | 
			
		||||
        let mut consumed_budget = 0;
 | 
			
		||||
        let mut last_cleaned_slot = 0;
 | 
			
		||||
        let t_background = Builder::new()
 | 
			
		||||
            .name("solana-accounts-background".to_string())
 | 
			
		||||
            .spawn(move || loop {
 | 
			
		||||
                if exit.load(Ordering::Relaxed) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                let bank = bank_forks.read().unwrap().working_bank();
 | 
			
		||||
                let bank = bank_forks.read().unwrap().root_bank().clone();
 | 
			
		||||
 | 
			
		||||
                bank.process_dead_slots();
 | 
			
		||||
 | 
			
		||||
                // Currently, given INTERVAL_MS, we process 1 slot/100 ms
 | 
			
		||||
                bank.process_stale_slot();
 | 
			
		||||
                consumed_budget = bank
 | 
			
		||||
                    .process_stale_slot_with_budget(consumed_budget, SHRUNKEN_ACCOUNT_PER_INTERVAL);
 | 
			
		||||
 | 
			
		||||
                if bank.block_height() - last_cleaned_slot > CLEAN_INTERVAL_SLOTS {
 | 
			
		||||
                    bank.clean_accounts();
 | 
			
		||||
                    last_cleaned_slot = bank.block_height();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                sleep(Duration::from_millis(INTERVAL_MS));
 | 
			
		||||
            })
 | 
			
		||||
 
 | 
			
		||||
@@ -177,6 +177,7 @@ mod tests {
 | 
			
		||||
    use crate::cluster_info::make_accounts_hashes_message;
 | 
			
		||||
    use crate::contact_info::ContactInfo;
 | 
			
		||||
    use solana_ledger::bank_forks::CompressionType;
 | 
			
		||||
    use solana_ledger::snapshot_utils::SnapshotVersion;
 | 
			
		||||
    use solana_sdk::{
 | 
			
		||||
        hash::hash,
 | 
			
		||||
        signature::{Keypair, Signer},
 | 
			
		||||
@@ -239,6 +240,7 @@ mod tests {
 | 
			
		||||
                tar_output_file: PathBuf::from("."),
 | 
			
		||||
                storages: vec![],
 | 
			
		||||
                compression: CompressionType::Bzip2,
 | 
			
		||||
                snapshot_version: SnapshotVersion::default(),
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            AccountsHashVerifier::process_accounts_package(
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										152
									
								
								core/src/bank_weight_fork_choice.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								core/src/bank_weight_fork_choice.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,152 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    consensus::{ComputedBankState, Tower},
 | 
			
		||||
    fork_choice::ForkChoice,
 | 
			
		||||
    progress_map::{ForkStats, ProgressMap},
 | 
			
		||||
};
 | 
			
		||||
use solana_ledger::bank_forks::BankForks;
 | 
			
		||||
use solana_runtime::bank::Bank;
 | 
			
		||||
use solana_sdk::timing;
 | 
			
		||||
use std::time::Instant;
 | 
			
		||||
use std::{
 | 
			
		||||
    collections::{HashMap, HashSet},
 | 
			
		||||
    sync::{Arc, RwLock},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct BankWeightForkChoice {}
 | 
			
		||||
 | 
			
		||||
impl ForkChoice for BankWeightForkChoice {
 | 
			
		||||
    fn compute_bank_stats(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        bank: &Bank,
 | 
			
		||||
        _tower: &Tower,
 | 
			
		||||
        progress: &mut ProgressMap,
 | 
			
		||||
        computed_bank_state: &ComputedBankState,
 | 
			
		||||
    ) {
 | 
			
		||||
        let bank_slot = bank.slot();
 | 
			
		||||
        // Only time progress map should be missing a bank slot
 | 
			
		||||
        // is if this node was the leader for this slot as those banks
 | 
			
		||||
        // are not replayed in replay_active_banks()
 | 
			
		||||
        let parent_weight = bank
 | 
			
		||||
            .parent()
 | 
			
		||||
            .and_then(|b| progress.get(&b.slot()))
 | 
			
		||||
            .map(|x| x.fork_stats.fork_weight)
 | 
			
		||||
            .unwrap_or(0);
 | 
			
		||||
 | 
			
		||||
        let stats = progress
 | 
			
		||||
            .get_fork_stats_mut(bank_slot)
 | 
			
		||||
            .expect("All frozen banks must exist in the Progress map");
 | 
			
		||||
 | 
			
		||||
        let ComputedBankState { bank_weight, .. } = computed_bank_state;
 | 
			
		||||
        stats.weight = *bank_weight;
 | 
			
		||||
        stats.fork_weight = stats.weight + parent_weight;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Returns:
 | 
			
		||||
    // 1) The heaviest overall bank
 | 
			
		||||
    // 2) The heaviest bank on the same fork as the last vote (doesn't require a
 | 
			
		||||
    // switching proof to vote for)
 | 
			
		||||
    fn select_forks(
 | 
			
		||||
        &self,
 | 
			
		||||
        frozen_banks: &[Arc<Bank>],
 | 
			
		||||
        tower: &Tower,
 | 
			
		||||
        progress: &ProgressMap,
 | 
			
		||||
        ancestors: &HashMap<u64, HashSet<u64>>,
 | 
			
		||||
        _bank_forks: &RwLock<BankForks>,
 | 
			
		||||
    ) -> (Arc<Bank>, Option<Arc<Bank>>) {
 | 
			
		||||
        let tower_start = Instant::now();
 | 
			
		||||
        assert!(!frozen_banks.is_empty());
 | 
			
		||||
        let num_frozen_banks = frozen_banks.len();
 | 
			
		||||
 | 
			
		||||
        trace!("frozen_banks {}", frozen_banks.len());
 | 
			
		||||
        let num_old_banks = frozen_banks
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|b| b.slot() < tower.root().unwrap_or(0))
 | 
			
		||||
            .count();
 | 
			
		||||
 | 
			
		||||
        let last_vote = tower.last_vote().slots.last().cloned();
 | 
			
		||||
        let mut heaviest_bank_on_same_fork = None;
 | 
			
		||||
        let mut heaviest_same_fork_weight = 0;
 | 
			
		||||
        let stats: Vec<&ForkStats> = frozen_banks
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|bank| {
 | 
			
		||||
                // Only time progress map should be missing a bank slot
 | 
			
		||||
                // is if this node was the leader for this slot as those banks
 | 
			
		||||
                // are not replayed in replay_active_banks()
 | 
			
		||||
                let stats = progress
 | 
			
		||||
                    .get_fork_stats(bank.slot())
 | 
			
		||||
                    .expect("All frozen banks must exist in the Progress map");
 | 
			
		||||
 | 
			
		||||
                if let Some(last_vote) = last_vote {
 | 
			
		||||
                    if ancestors
 | 
			
		||||
                        .get(&bank.slot())
 | 
			
		||||
                        .expect("Entry in frozen banks must exist in ancestors")
 | 
			
		||||
                        .contains(&last_vote)
 | 
			
		||||
                    {
 | 
			
		||||
                        // Descendant of last vote cannot be locked out
 | 
			
		||||
                        assert!(!stats.is_locked_out);
 | 
			
		||||
 | 
			
		||||
                        // ancestors(slot) should not contain the slot itself,
 | 
			
		||||
                        // so we should never get the same bank as the last vote
 | 
			
		||||
                        assert_ne!(bank.slot(), last_vote);
 | 
			
		||||
                        // highest weight, lowest slot first. frozen_banks is sorted
 | 
			
		||||
                        // from least slot to greatest slot, so if two banks have
 | 
			
		||||
                        // the same fork weight, the lower slot will be picked
 | 
			
		||||
                        if stats.fork_weight > heaviest_same_fork_weight {
 | 
			
		||||
                            heaviest_bank_on_same_fork = Some(bank.clone());
 | 
			
		||||
                            heaviest_same_fork_weight = stats.fork_weight;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                stats
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
        let num_not_recent = stats.iter().filter(|s| !s.is_recent).count();
 | 
			
		||||
        let num_has_voted = stats.iter().filter(|s| s.has_voted).count();
 | 
			
		||||
        let num_empty = stats.iter().filter(|s| s.is_empty).count();
 | 
			
		||||
        let num_threshold_failure = stats.iter().filter(|s| !s.vote_threshold).count();
 | 
			
		||||
        let num_votable_threshold_failure = stats
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|s| s.is_recent && !s.has_voted && !s.vote_threshold)
 | 
			
		||||
            .count();
 | 
			
		||||
 | 
			
		||||
        let mut candidates: Vec<_> = frozen_banks.iter().zip(stats.iter()).collect();
 | 
			
		||||
 | 
			
		||||
        //highest weight, lowest slot first
 | 
			
		||||
        candidates.sort_by_key(|b| (b.1.fork_weight, 0i64 - b.0.slot() as i64));
 | 
			
		||||
        let rv = candidates
 | 
			
		||||
            .last()
 | 
			
		||||
            .expect("frozen banks was nonempty so candidates must also be nonempty");
 | 
			
		||||
        let ms = timing::duration_as_ms(&tower_start.elapsed());
 | 
			
		||||
        let weights: Vec<(u128, u64, u64)> = candidates
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|x| (x.1.weight, x.0.slot(), x.1.block_height))
 | 
			
		||||
            .collect();
 | 
			
		||||
        debug!(
 | 
			
		||||
            "@{:?} tower duration: {:?} len: {}/{} weights: {:?}",
 | 
			
		||||
            timing::timestamp(),
 | 
			
		||||
            ms,
 | 
			
		||||
            candidates.len(),
 | 
			
		||||
            stats.iter().filter(|s| !s.has_voted).count(),
 | 
			
		||||
            weights,
 | 
			
		||||
        );
 | 
			
		||||
        datapoint_debug!(
 | 
			
		||||
            "replay_stage-select_forks",
 | 
			
		||||
            ("frozen_banks", num_frozen_banks as i64, i64),
 | 
			
		||||
            ("not_recent", num_not_recent as i64, i64),
 | 
			
		||||
            ("has_voted", num_has_voted as i64, i64),
 | 
			
		||||
            ("old_banks", num_old_banks as i64, i64),
 | 
			
		||||
            ("empty_banks", num_empty as i64, i64),
 | 
			
		||||
            ("threshold_failure", num_threshold_failure as i64, i64),
 | 
			
		||||
            (
 | 
			
		||||
                "votable_threshold_failure",
 | 
			
		||||
                num_votable_threshold_failure as i64,
 | 
			
		||||
                i64
 | 
			
		||||
            ),
 | 
			
		||||
            ("tower_duration", ms as i64, i64),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        (rv.0.clone(), heaviest_bank_on_same_fork)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user