1+ <?php
2+
3+ declare (strict_types=1 );
4+
5+ namespace AndKom \PhpBitcoinBlockchain ;
6+
7+ use AndKom \BCDataStream \Reader ;
8+
9+ /**
10+ * Class ChainStateReader
11+ * @package AndKom\PhpBitcoinBlockchain
12+ */
13+ class ChainStateReader
14+ {
15+ const PREFIX_COIN = 'C ' ;
16+ const KEY_BEST_BLOCK = 'B ' ;
17+ const KEY_OBFUSCATE_KEY = "\x0e\x00obfuscate_key " ;
18+
19+ /**
20+ * @var string
21+ */
22+ protected $ chainStateDir ;
23+
24+ /**
25+ * @var \LevelDB
26+ */
27+ protected $ db ;
28+
29+ /**
30+ * @var string
31+ */
32+ protected $ obfuscateKey ;
33+
34+ /**
35+ * ChainStateReader constructor.
36+ * @param string $chainStateDir
37+ */
38+ public function __construct (string $ chainStateDir = '' )
39+ {
40+ $ this ->chainStateDir = $ chainStateDir ;
41+ }
42+
43+ /**
44+ * @return \LevelDB
45+ */
46+ protected function openDb (): \LevelDB
47+ {
48+ if ($ this ->db ) {
49+ return $ this ->db ;
50+ }
51+
52+ return $ this ->db = new \LevelDB ($ this ->chainStateDir );
53+ }
54+
55+ /**
56+ * @return ChainStateReader
57+ */
58+ protected function closeDb (): self
59+ {
60+ if ($ this ->db ) {
61+ $ this ->db ->close ();
62+ $ this ->db = null ;
63+ }
64+
65+ return $ this ;
66+ }
67+
68+ /**
69+ * @param \LevelDB $db
70+ * @return string
71+ */
72+ protected function getObfuscateKeyFromDb (\LevelDB $ db ): string
73+ {
74+ $ obfuscateKey = $ db ->get (static ::KEY_OBFUSCATE_KEY );
75+
76+ // first byte is key size
77+ $ obfuscateKey = substr ($ obfuscateKey , 1 );
78+
79+ return $ obfuscateKey ;
80+ }
81+
82+ /**
83+ * @param \LevelDB $db
84+ * @param string $value
85+ * @return string
86+ */
87+ protected function deobfuscateValue (\LevelDB $ db , string $ value ): string
88+ {
89+ if (!$ this ->obfuscateKey ) {
90+ $ this ->obfuscateKey = $ this ->getObfuscateKeyFromDb ($ db );
91+ }
92+
93+ return $ this ->obfuscateKey ? Utils::xor ($ this ->obfuscateKey , $ value ) : $ value ;
94+ }
95+
96+ /**
97+ * @return string
98+ * @throws Exception
99+ * @throws \LevelDBException
100+ */
101+ public function getBestBlock (): string
102+ {
103+ $ db = $ this ->openDb ();
104+
105+ $ bestBlock = $ db ->get (static ::KEY_BEST_BLOCK );
106+ $ bestBlock = $ this ->deobfuscateValue ($ db , $ bestBlock );
107+
108+ $ this ->closeDb ();
109+
110+ if (!$ bestBlock ) {
111+ throw new Exception ('Unable to get best block. ' );
112+ }
113+
114+ return $ bestBlock ;
115+ }
116+
117+ /**
118+ * @return string
119+ * @throws Exception
120+ * @throws \LevelDBException
121+ */
122+ public function getObfuscateKey (): string
123+ {
124+ $ db = $ this ->openDb ();
125+
126+ $ obfuscateKey = $ this ->getObfuscateKeyFromDb ($ db );
127+
128+ $ this ->closeDb ();
129+
130+ if (!$ obfuscateKey ) {
131+ throw new Exception ('Unable to get obfuscate key. ' );
132+ }
133+
134+ return $ obfuscateKey ;
135+ }
136+
137+ /**
138+ * @return \Generator
139+ * @throws Exception
140+ * @throws \LevelDBException
141+ */
142+ public function read (): \Generator
143+ {
144+ if (!class_exists ('\LevelDB ' )) {
145+ throw new Exception ('Extension leveldb is not installed. ' );
146+ }
147+
148+ $ db = new \LevelDB ($ this ->chainStateDir );
149+
150+ foreach ($ db ->getIterator () as $ key => $ value ) {
151+ $ key = new Reader ($ key );
152+ $ prefix = $ key ->read (1 );
153+
154+ if ($ prefix != static ::PREFIX_COIN ) {
155+ continue ;
156+ }
157+
158+ $ value = $ this ->deobfuscateValue ($ db , $ value );
159+
160+ yield UnspentOutput::parse ($ key , new Reader ($ value ));
161+ }
162+
163+ $ this ->closeDb ();
164+ }
165+ }
0 commit comments