Line data Source code
1 : // Copyright (c) 2014-2020 The Bitcoin Core developers
2 : // Distributed under the MIT software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <chain.h>
6 : #include <chainparams.h>
7 : #include <consensus/params.h>
8 : #include <test/util/setup_common.h>
9 : #include <validation.h>
10 : #include <versionbits.h>
11 :
12 : #include <boost/test/unit_test.hpp>
13 :
14 : /* Define a virtual block time, one block per 10 minutes after Nov 14 2014, 0:55:36am */
15 64997 : static int32_t TestTime(int nHeight) { return 1415926536 + 600 * nHeight; }
16 :
17 89 : static const Consensus::Params paramsDummy = Consensus::Params();
18 :
19 17832 : class TestConditionChecker : public AbstractThresholdConditionChecker
20 : {
21 : private:
22 : mutable ThresholdConditionCache cache;
23 :
24 : public:
25 30994 : int64_t BeginTime(const Consensus::Params& params) const override { return TestTime(10000); }
26 31123 : int64_t EndTime(const Consensus::Params& params) const override { return TestTime(20000); }
27 35163 : int Period(const Consensus::Params& params) const override { return 1000; }
28 31123 : int Threshold(const Consensus::Params& params) const override { return 900; }
29 824000 : bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return (pindex->nVersion & 0x100); }
30 :
31 12574 : ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, paramsDummy, cache); }
32 12316 : int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, paramsDummy, cache); }
33 : };
34 :
35 11244 : class TestAlwaysActiveConditionChecker : public TestConditionChecker
36 : {
37 : public:
38 12445 : int64_t BeginTime(const Consensus::Params& params) const override { return Consensus::BIP9Deployment::ALWAYS_ACTIVE; }
39 : };
40 :
41 : #define CHECKERS 6
42 :
43 : class VersionBitsTester
44 : {
45 : // A fake blockchain
46 : std::vector<CBlockIndex*> vpblock;
47 :
48 : // 6 independent checkers for the same bit.
49 : // The first one performs all checks, the second only 50%, the third only 25%, etc...
50 : // This is to test whether lack of cached information leads to the same results.
51 : TestConditionChecker checker[CHECKERS];
52 : // Another 6 that assume always active activation
53 : TestAlwaysActiveConditionChecker checker_always[CHECKERS];
54 :
55 : // Test counter (to identify failures)
56 : int num;
57 :
58 : public:
59 792 : VersionBitsTester() : num(0) {}
60 :
61 322 : VersionBitsTester& Reset() {
62 3986466 : for (unsigned int i = 0; i < vpblock.size(); i++) {
63 3986144 : delete vpblock[i];
64 : }
65 2254 : for (unsigned int i = 0; i < CHECKERS; i++) {
66 1932 : checker[i] = TestConditionChecker();
67 1932 : checker_always[i] = TestAlwaysActiveConditionChecker();
68 : }
69 322 : vpblock.clear();
70 322 : return *this;
71 : }
72 :
73 132 : ~VersionBitsTester() {
74 66 : Reset();
75 792 : }
76 :
77 10950 : VersionBitsTester& Mine(unsigned int height, int32_t nTime, int32_t nVersion) {
78 3997094 : while (vpblock.size() < height) {
79 3986144 : CBlockIndex* pindex = new CBlockIndex();
80 3986144 : pindex->nHeight = vpblock.size();
81 3986144 : pindex->pprev = vpblock.size() > 0 ? vpblock.back() : nullptr;
82 3986144 : pindex->nTime = nTime;
83 3986144 : pindex->nVersion = nVersion;
84 3986144 : pindex->BuildSkip();
85 3986144 : vpblock.push_back(pindex);
86 3986144 : }
87 10950 : return *this;
88 0 : }
89 :
90 3136 : VersionBitsTester& TestStateSinceHeight(int height) {
91 21952 : for (int i = 0; i < CHECKERS; i++) {
92 18816 : if (InsecureRandBits(i) == 0) {
93 6158 : BOOST_CHECK_MESSAGE(checker[i].GetStateSinceHeightFor(vpblock.empty() ? nullptr : vpblock.back()) == height, strprintf("Test %i for StateSinceHeight", num));
94 6158 : BOOST_CHECK_MESSAGE(checker_always[i].GetStateSinceHeightFor(vpblock.empty() ? nullptr : vpblock.back()) == 0, strprintf("Test %i for StateSinceHeight (always active)", num));
95 : }
96 : }
97 3136 : num++;
98 3136 : return *this;
99 0 : }
100 :
101 1152 : VersionBitsTester& TestDefined() {
102 8064 : for (int i = 0; i < CHECKERS; i++) {
103 6912 : if (InsecureRandBits(i) == 0) {
104 2283 : BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::DEFINED, strprintf("Test %i for DEFINED", num));
105 2283 : BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num));
106 : }
107 : }
108 1152 : num++;
109 1152 : return *this;
110 0 : }
111 :
112 768 : VersionBitsTester& TestStarted() {
113 5376 : for (int i = 0; i < CHECKERS; i++) {
114 4608 : if (InsecureRandBits(i) == 0) {
115 1516 : BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::STARTED, strprintf("Test %i for STARTED", num));
116 1516 : BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num));
117 : }
118 : }
119 768 : num++;
120 768 : return *this;
121 0 : }
122 :
123 128 : VersionBitsTester& TestLockedIn() {
124 896 : for (int i = 0; i < CHECKERS; i++) {
125 768 : if (InsecureRandBits(i) == 0) {
126 246 : BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::LOCKED_IN, strprintf("Test %i for LOCKED_IN", num));
127 246 : BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num));
128 : }
129 : }
130 128 : num++;
131 128 : return *this;
132 0 : }
133 :
134 192 : VersionBitsTester& TestActive() {
135 1344 : for (int i = 0; i < CHECKERS; i++) {
136 1152 : if (InsecureRandBits(i) == 0) {
137 373 : BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE", num));
138 373 : BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num));
139 : }
140 : }
141 192 : num++;
142 192 : return *this;
143 0 : }
144 :
145 960 : VersionBitsTester& TestFailed() {
146 6720 : for (int i = 0; i < CHECKERS; i++) {
147 5760 : if (InsecureRandBits(i) == 0) {
148 1869 : BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::FAILED, strprintf("Test %i for FAILED", num));
149 1869 : BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num));
150 : }
151 : }
152 960 : num++;
153 960 : return *this;
154 0 : }
155 :
156 8070 : CBlockIndex * Tip() { return vpblock.size() ? vpblock.back() : nullptr; }
157 : };
158 :
159 89 : BOOST_FIXTURE_TEST_SUITE(versionbits_tests, TestingSetup)
160 :
161 95 : BOOST_AUTO_TEST_CASE(versionbits_test)
162 : {
163 65 : for (int i = 0; i < 64; i++) {
164 : // DEFINED -> FAILED
165 128 : VersionBitsTester().TestDefined().TestStateSinceHeight(0)
166 64 : .Mine(1, TestTime(1), 0x100).TestDefined().TestStateSinceHeight(0)
167 64 : .Mine(11, TestTime(11), 0x100).TestDefined().TestStateSinceHeight(0)
168 64 : .Mine(989, TestTime(989), 0x100).TestDefined().TestStateSinceHeight(0)
169 64 : .Mine(999, TestTime(20000), 0x100).TestDefined().TestStateSinceHeight(0)
170 64 : .Mine(1000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(1000)
171 64 : .Mine(1999, TestTime(30001), 0x100).TestFailed().TestStateSinceHeight(1000)
172 64 : .Mine(2000, TestTime(30002), 0x100).TestFailed().TestStateSinceHeight(1000)
173 64 : .Mine(2001, TestTime(30003), 0x100).TestFailed().TestStateSinceHeight(1000)
174 64 : .Mine(2999, TestTime(30004), 0x100).TestFailed().TestStateSinceHeight(1000)
175 64 : .Mine(3000, TestTime(30005), 0x100).TestFailed().TestStateSinceHeight(1000)
176 :
177 : // DEFINED -> STARTED -> FAILED
178 64 : .Reset().TestDefined().TestStateSinceHeight(0)
179 64 : .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
180 64 : .Mine(1000, TestTime(10000) - 1, 0x100).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
181 64 : .Mine(2000, TestTime(10000), 0x100).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
182 64 : .Mine(2051, TestTime(10010), 0).TestStarted().TestStateSinceHeight(2000) // 51 old blocks
183 64 : .Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) // 899 new blocks
184 64 : .Mine(3000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(3000) // 50 old blocks (so 899 out of the past 1000)
185 64 : .Mine(4000, TestTime(20010), 0x100).TestFailed().TestStateSinceHeight(3000)
186 :
187 : // DEFINED -> STARTED -> FAILED while threshold reached
188 64 : .Reset().TestDefined().TestStateSinceHeight(0)
189 64 : .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
190 64 : .Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
191 64 : .Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
192 64 : .Mine(2999, TestTime(30000), 0x100).TestStarted().TestStateSinceHeight(2000) // 999 new blocks
193 64 : .Mine(3000, TestTime(30000), 0x100).TestFailed().TestStateSinceHeight(3000) // 1 new block (so 1000 out of the past 1000 are new)
194 64 : .Mine(3999, TestTime(30001), 0).TestFailed().TestStateSinceHeight(3000)
195 64 : .Mine(4000, TestTime(30002), 0).TestFailed().TestStateSinceHeight(3000)
196 64 : .Mine(14333, TestTime(30003), 0).TestFailed().TestStateSinceHeight(3000)
197 64 : .Mine(24000, TestTime(40000), 0).TestFailed().TestStateSinceHeight(3000)
198 :
199 : // DEFINED -> STARTED -> LOCKEDIN at the last minute -> ACTIVE
200 64 : .Reset().TestDefined()
201 64 : .Mine(1, TestTime(1), 0).TestDefined().TestStateSinceHeight(0)
202 64 : .Mine(1000, TestTime(10000) - 1, 0x101).TestDefined().TestStateSinceHeight(0) // One second more and it would be defined
203 64 : .Mine(2000, TestTime(10000), 0x101).TestStarted().TestStateSinceHeight(2000) // So that's what happens the next period
204 64 : .Mine(2050, TestTime(10010), 0x200).TestStarted().TestStateSinceHeight(2000) // 50 old blocks
205 64 : .Mine(2950, TestTime(10020), 0x100).TestStarted().TestStateSinceHeight(2000) // 900 new blocks
206 64 : .Mine(2999, TestTime(19999), 0x200).TestStarted().TestStateSinceHeight(2000) // 49 old blocks
207 64 : .Mine(3000, TestTime(29999), 0x200).TestLockedIn().TestStateSinceHeight(3000) // 1 old block (so 900 out of the past 1000)
208 64 : .Mine(3999, TestTime(30001), 0).TestLockedIn().TestStateSinceHeight(3000)
209 64 : .Mine(4000, TestTime(30002), 0).TestActive().TestStateSinceHeight(4000)
210 64 : .Mine(14333, TestTime(30003), 0).TestActive().TestStateSinceHeight(4000)
211 64 : .Mine(24000, TestTime(40000), 0).TestActive().TestStateSinceHeight(4000)
212 :
213 : // DEFINED multiple periods -> STARTED multiple periods -> FAILED
214 64 : .Reset().TestDefined().TestStateSinceHeight(0)
215 64 : .Mine(999, TestTime(999), 0).TestDefined().TestStateSinceHeight(0)
216 64 : .Mine(1000, TestTime(1000), 0).TestDefined().TestStateSinceHeight(0)
217 64 : .Mine(2000, TestTime(2000), 0).TestDefined().TestStateSinceHeight(0)
218 64 : .Mine(3000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
219 64 : .Mine(4000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
220 64 : .Mine(5000, TestTime(10000), 0).TestStarted().TestStateSinceHeight(3000)
221 64 : .Mine(6000, TestTime(20000), 0).TestFailed().TestStateSinceHeight(6000)
222 64 : .Mine(7000, TestTime(20000), 0x100).TestFailed().TestStateSinceHeight(6000);
223 : }
224 :
225 : // Sanity checks of version bit deployments
226 1 : const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
227 1 : const Consensus::Params &mainnetParams = chainParams->GetConsensus();
228 2 : for (int i=0; i<(int) Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) {
229 1 : uint32_t bitmask = VersionBitsMask(mainnetParams, static_cast<Consensus::DeploymentPos>(i));
230 : // Make sure that no deployment tries to set an invalid bit.
231 1 : BOOST_CHECK_EQUAL(bitmask & ~(uint32_t)VERSIONBITS_TOP_MASK, bitmask);
232 :
233 : // Verify that the deployment windows of different deployment using the
234 : // same bit are disjoint.
235 : // This test may need modification at such time as a new deployment
236 : // is proposed that reuses the bit of an activated soft fork, before the
237 : // end time of that soft fork. (Alternatively, the end time of that
238 : // activated soft fork could be later changed to be earlier to avoid
239 : // overlap.)
240 1 : for (int j=i+1; j<(int) Consensus::MAX_VERSION_BITS_DEPLOYMENTS; j++) {
241 0 : if (VersionBitsMask(mainnetParams, static_cast<Consensus::DeploymentPos>(j)) == bitmask) {
242 0 : BOOST_CHECK(mainnetParams.vDeployments[j].nStartTime > mainnetParams.vDeployments[i].nTimeout ||
243 : mainnetParams.vDeployments[i].nStartTime > mainnetParams.vDeployments[j].nTimeout);
244 : }
245 : }
246 1 : }
247 1 : }
248 :
249 95 : BOOST_AUTO_TEST_CASE(versionbits_computeblockversion)
250 : {
251 : // Check that ComputeBlockVersion will set the appropriate bit correctly
252 : // on mainnet.
253 1 : const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
254 1 : const Consensus::Params &mainnetParams = chainParams->GetConsensus();
255 :
256 : // Use the TESTDUMMY deployment for testing purposes.
257 1 : int64_t bit = mainnetParams.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit;
258 1 : int64_t nStartTime = mainnetParams.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime;
259 1 : int64_t nTimeout = mainnetParams.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout;
260 :
261 1 : assert(nStartTime < nTimeout);
262 :
263 : // In the first chain, test that the bit is set by CBV until it has failed.
264 : // In the second chain, test the bit is set by CBV while STARTED and
265 : // LOCKED-IN, and then no longer set while ACTIVE.
266 1 : VersionBitsTester firstChain, secondChain;
267 :
268 : // Start generating blocks before nStartTime
269 1 : int64_t nTime = nStartTime - 1;
270 :
271 : // Before MedianTimePast of the chain has crossed nStartTime, the bit
272 : // should not be set.
273 : CBlockIndex *lastBlock = nullptr;
274 1 : lastBlock = firstChain.Mine(mainnetParams.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
275 1 : BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit), 0);
276 :
277 : // Mine more blocks (4 less than the adjustment period) at the old time, and check that CBV isn't setting the bit yet.
278 2012 : for (uint32_t i = 1; i < mainnetParams.nMinerConfirmationWindow - 4; i++) {
279 2011 : lastBlock = firstChain.Mine(mainnetParams.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
280 : // This works because VERSIONBITS_LAST_OLD_BLOCK_VERSION happens
281 : // to be 4, and the bit we're testing happens to be bit 28.
282 2011 : BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit), 0);
283 : }
284 : // Now mine 5 more blocks at the start time -- MTP should not have passed yet, so
285 : // CBV should still not yet set the bit.
286 : nTime = nStartTime;
287 6 : for (uint32_t i = mainnetParams.nMinerConfirmationWindow - 4; i <= mainnetParams.nMinerConfirmationWindow; i++) {
288 5 : lastBlock = firstChain.Mine(mainnetParams.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
289 5 : BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit), 0);
290 : }
291 :
292 : // Advance to the next period and transition to STARTED,
293 1 : lastBlock = firstChain.Mine(mainnetParams.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
294 : // so ComputeBlockVersion should now set the bit,
295 1 : BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit)) != 0);
296 : // and should also be using the VERSIONBITS_TOP_BITS.
297 1 : BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS);
298 :
299 : // Check that ComputeBlockVersion will set the bit until nTimeout
300 1 : nTime += 600;
301 1 : uint32_t blocksToMine = mainnetParams.nMinerConfirmationWindow * 2; // test blocks for up to 2 time periods
302 1 : uint32_t nHeight = mainnetParams.nMinerConfirmationWindow * 3;
303 : // These blocks are all before nTimeout is reached.
304 4033 : while (nTime < nTimeout && blocksToMine > 0) {
305 4032 : lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
306 4032 : BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit)) != 0);
307 4032 : BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS);
308 4032 : blocksToMine--;
309 4032 : nTime += 600;
310 : nHeight += 1;
311 : }
312 :
313 : nTime = nTimeout;
314 : // FAILED is only triggered at the end of a period, so CBV should be setting
315 : // the bit until the period transition.
316 2016 : for (uint32_t i = 0; i < mainnetParams.nMinerConfirmationWindow - 1; i++) {
317 2015 : lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
318 2015 : BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit)) != 0);
319 : nHeight += 1;
320 : }
321 : // The next block should trigger no longer setting the bit.
322 1 : lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
323 1 : BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit), 0);
324 :
325 : // On a new chain:
326 : // verify that the bit will be set after lock-in, and then stop being set
327 : // after activation.
328 : nTime = nStartTime;
329 :
330 : // Mine one period worth of blocks, and check that the bit will be on for the
331 : // next period.
332 1 : lastBlock = secondChain.Mine(mainnetParams.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
333 1 : BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit)) != 0);
334 :
335 : // Mine another period worth of blocks, signaling the new bit.
336 1 : lastBlock = secondChain.Mine(mainnetParams.nMinerConfirmationWindow * 2, nTime, VERSIONBITS_TOP_BITS | (1<<bit)).Tip();
337 : // After one period of setting the bit on each block, it should have locked in.
338 : // We keep setting the bit for one more period though, until activation.
339 1 : BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit)) != 0);
340 :
341 : // Now check that we keep mining the block until the end of this period, and
342 : // then stop at the beginning of the next period.
343 1 : lastBlock = secondChain.Mine((mainnetParams.nMinerConfirmationWindow * 3) - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
344 1 : BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1 << bit)) != 0);
345 1 : lastBlock = secondChain.Mine(mainnetParams.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip();
346 1 : BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit), 0);
347 :
348 : // Finally, verify that after a soft fork has activated, CBV no longer uses
349 : // VERSIONBITS_LAST_OLD_BLOCK_VERSION.
350 : //BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS);
351 1 : }
352 :
353 :
354 89 : BOOST_AUTO_TEST_SUITE_END()
|