LCOV - code coverage report
Current view: top level - src/wallet/test - db_tests.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 95.8 % 166 159
Test Date: 2025-06-01 06:26:32 Functions: 94.7 % 19 18
Branches: 50.5 % 992 501

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2018-present 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 <boost/test/unit_test.hpp>
       6                 :             : 
       7                 :             : #include <test/util/setup_common.h>
       8                 :             : #include <util/check.h>
       9                 :             : #include <util/fs.h>
      10                 :             : #include <util/translation.h>
      11                 :             : #include <wallet/sqlite.h>
      12                 :             : #include <wallet/migrate.h>
      13                 :             : #include <wallet/test/util.h>
      14                 :             : #include <wallet/walletutil.h>
      15                 :             : 
      16                 :             : #include <cstddef>
      17                 :             : #include <fstream>
      18                 :             : #include <memory>
      19                 :             : #include <span>
      20                 :             : #include <string>
      21                 :             : #include <string_view>
      22                 :             : #include <utility>
      23                 :             : #include <vector>
      24                 :             : 
      25                 :           0 : inline std::ostream& operator<<(std::ostream& os, const std::pair<const SerializeData, SerializeData>& kv)
      26                 :             : {
      27                 :           0 :     std::span key{kv.first}, value{kv.second};
      28                 :           0 :     os << "(\"" << std::string_view{reinterpret_cast<const char*>(key.data()), key.size()} << "\", \""
      29                 :           0 :        << std::string_view{reinterpret_cast<const char*>(value.data()), value.size()} << "\")";
      30                 :           0 :     return os;
      31                 :             : }
      32                 :             : 
      33                 :             : namespace wallet {
      34                 :             : 
      35                 :          22 : inline std::span<const std::byte> StringBytes(std::string_view str)
      36                 :             : {
      37                 :          22 :     return std::as_bytes(std::span{str});
      38                 :             : }
      39                 :             : 
      40                 :          14 : static SerializeData StringData(std::string_view str)
      41                 :             : {
      42                 :          14 :     auto bytes = StringBytes(str);
      43                 :          14 :     return SerializeData{bytes.begin(), bytes.end()};
      44                 :             : }
      45                 :             : 
      46                 :           8 : static void CheckPrefix(DatabaseBatch& batch, std::span<const std::byte> prefix, MockableData expected)
      47                 :             : {
      48                 :           8 :     std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix);
      49                 :           8 :     MockableData actual;
      50                 :          68 :     while (true) {
      51                 :          38 :         DataStream key, value;
      52         [ +  - ]:          38 :         DatabaseCursor::Status status = cursor->Next(key, value);
      53         [ +  + ]:          38 :         if (status == DatabaseCursor::Status::DONE) break;
      54   [ +  -  +  -  :          60 :         BOOST_CHECK(status == DatabaseCursor::Status::MORE);
                   +  - ]
      55   [ +  -  +  -  :          60 :         BOOST_CHECK(
          +  -  +  -  +  
                      - ]
      56                 :             :             actual.emplace(SerializeData(key.begin(), key.end()), SerializeData(value.begin(), value.end())).second);
      57                 :          38 :     }
      58   [ +  -  +  -  :          16 :     BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end());
                   +  - ]
      59                 :           8 : }
      60                 :             : 
      61                 :             : BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup)
      62                 :             : 
      63                 :           4 : static std::vector<std::unique_ptr<WalletDatabase>> TestDatabases(const fs::path& path_root)
      64                 :             : {
      65                 :           4 :     std::vector<std::unique_ptr<WalletDatabase>> dbs;
      66         [ +  - ]:           4 :     DatabaseOptions options;
      67                 :           4 :     DatabaseStatus status;
      68         [ +  - ]:           4 :     bilingual_str error;
      69                 :             :     // Unable to test BerkeleyRO since we cannot create a new BDB database to open
      70   [ +  -  +  -  :          12 :     dbs.emplace_back(MakeSQLiteDatabase(path_root / "sqlite", options, status, error));
             +  -  +  - ]
      71   [ +  -  +  - ]:           4 :     dbs.emplace_back(CreateMockableWalletDatabase());
      72                 :           4 :     return dbs;
      73                 :           4 : }
      74                 :             : 
      75   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
      76                 :             : {
      77                 :             :     // Test each supported db
      78         [ +  + ]:           3 :     for (const auto& database : TestDatabases(m_path_root)) {
      79         [ +  - ]:           2 :         std::vector<std::string> prefixes = {"", "FIRST", "SECOND", "P\xfe\xff", "P\xff\x01", "\xff\xff"};
      80                 :             : 
      81   [ +  -  +  - ]:           2 :         std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch();
      82                 :             :         // Write elements to it
      83         [ +  + ]:          22 :         for (unsigned int i = 0; i < 10; i++) {
      84         [ +  + ]:         140 :             for (const auto& prefix : prefixes) {
      85   [ +  -  +  -  :         240 :                 BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i));
             +  -  +  - ]
      86                 :             :             }
      87                 :             :         }
      88                 :             : 
      89                 :             :         // Now read all the items by prefix and verify that each element gets parsed correctly
      90         [ +  + ]:          14 :         for (const auto& prefix : prefixes) {
      91                 :          12 :             DataStream s_prefix;
      92         [ +  - ]:          12 :             s_prefix << prefix;
      93         [ +  - ]:          12 :             std::unique_ptr<DatabaseCursor> cursor = handler->GetNewPrefixCursor(s_prefix);
      94                 :          12 :             DataStream key;
      95                 :          12 :             DataStream value;
      96         [ +  + ]:         132 :             for (int i = 0; i < 10; i++) {
      97         [ +  - ]:         120 :                 DatabaseCursor::Status status = cursor->Next(key, value);
      98   [ +  -  +  - ]:         120 :                 BOOST_CHECK_EQUAL(status, DatabaseCursor::Status::MORE);
      99                 :             : 
     100         [ +  - ]:         120 :                 std::string key_back;
     101                 :         120 :                 unsigned int i_back;
     102   [ +  -  +  - ]:         120 :                 key >> key_back >> i_back;
     103   [ +  -  +  - ]:         120 :                 BOOST_CHECK_EQUAL(key_back, prefix);
     104                 :             : 
     105                 :         120 :                 unsigned int value_back;
     106         [ +  - ]:         120 :                 value >> value_back;
     107   [ +  -  +  - ]:         120 :                 BOOST_CHECK_EQUAL(value_back, i_back);
     108                 :         120 :             }
     109                 :             : 
     110                 :             :             // Let's now read it once more, it should return DONE
     111   [ +  -  +  -  :          24 :             BOOST_CHECK(cursor->Next(key, value) == DatabaseCursor::Status::DONE);
                   +  - ]
     112                 :          12 :         }
     113         [ +  - ]:           2 :         handler.reset();
     114         [ +  - ]:           2 :         database->Close();
     115                 :           3 :     }
     116                 :           1 : }
     117                 :             : 
     118                 :             : // Lower level DatabaseBase::GetNewPrefixCursor test, to cover cases that aren't
     119                 :             : // covered in the higher level test above. The higher level test uses
     120                 :             : // serialized strings which are prefixed with string length, so it doesn't test
     121                 :             : // truly empty prefixes or prefixes that begin with \xff
     122   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(db_cursor_prefix_byte_test)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     123                 :             : {
     124                 :           1 :     const MockableData::value_type
     125   [ +  -  +  - ]:           1 :         e{StringData(""), StringData("e")},
     126   [ +  -  +  -  :           1 :         p{StringData("prefix"), StringData("p")},
                   +  - ]
     127   [ +  -  +  -  :           1 :         ps{StringData("prefixsuffix"), StringData("ps")},
                   +  - ]
     128   [ +  -  +  -  :           1 :         f{StringData("\xff"), StringData("f")},
                   +  - ]
     129   [ +  -  +  -  :           1 :         fs{StringData("\xffsuffix"), StringData("fs")},
                   +  - ]
     130   [ +  -  +  -  :           1 :         ff{StringData("\xff\xff"), StringData("ff")},
                   +  - ]
     131   [ +  -  +  - ]:           1 :         ffs{StringData("\xff\xffsuffix"), StringData("ffs")};
     132   [ +  -  +  + ]:           3 :     for (const auto& database : TestDatabases(m_path_root)) {
     133         [ +  - ]:           2 :         std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
     134                 :             : 
     135                 :             :         // Write elements to it if not berkeleyro
     136   [ +  -  +  -  :          44 :         for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) {
          +  -  +  -  +  
          -  +  -  +  -  
          -  +  +  +  -  
                      - ]
     137         [ -  + ]:          14 :             batch->Write(std::span{k}, std::span{v});
     138   [ +  +  -  - ]:          16 :         }
     139                 :             : 
     140   [ +  -  +  -  :          18 :         CheckPrefix(*batch, StringBytes(""), {e, p, ps, f, fs, ff, ffs});
             +  +  -  - ]
     141   [ +  -  +  -  :           8 :         CheckPrefix(*batch, StringBytes("prefix"), {p, ps});
             +  +  -  - ]
     142   [ +  -  +  +  :          12 :         CheckPrefix(*batch, StringBytes("\xff"), {f, fs, ff, ffs});
                   -  - ]
     143   [ +  -  +  -  :           8 :         CheckPrefix(*batch, StringBytes("\xff\xff"), {ff, ffs});
             +  +  -  - ]
     144         [ +  - ]:           2 :         batch.reset();
     145         [ +  - ]:           2 :         database->Close();
     146                 :           3 :     }
     147   [ +  -  +  -  :          15 : }
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  -  -  -  -  
             -  -  -  - ]
     148                 :             : 
     149   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(db_availability_after_write_error)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     150                 :             : {
     151                 :             :     // Ensures the database remains accessible without deadlocking after a write error.
     152                 :             :     // To simulate the behavior, record overwrites are disallowed, and the test verifies
     153                 :             :     // that the database remains active after failing to store an existing record.
     154         [ +  + ]:           3 :     for (const auto& database : TestDatabases(m_path_root)) {
     155                 :             :         // Write original record
     156         [ +  - ]:           2 :         std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
     157         [ +  - ]:           2 :         std::string key = "key";
     158         [ +  - ]:           2 :         std::string value = "value";
     159         [ +  - ]:           2 :         std::string value2 = "value_2";
     160   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->Write(key, value));
             +  -  +  - ]
     161                 :             :         // Attempt to overwrite the record (expect failure)
     162   [ +  -  +  -  :           4 :         BOOST_CHECK(!batch->Write(key, value2, /*fOverwrite=*/false));
             +  -  +  - ]
     163                 :             :         // Successfully overwrite the record
     164   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->Write(key, value2, /*fOverwrite=*/true));
             +  -  +  - ]
     165                 :             :         // Sanity-check; read and verify the overwritten value
     166         [ +  - ]:           2 :         std::string read_value;
     167   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->Read(key, read_value));
             +  -  +  - ]
     168   [ +  -  +  - ]:           2 :         BOOST_CHECK_EQUAL(read_value, value2);
     169                 :           3 :     }
     170                 :           1 : }
     171                 :             : 
     172                 :             : // Verify 'ErasePrefix' functionality using db keys similar to the ones used by the wallet.
     173                 :             : // Keys are in the form of std::pair<TYPE, ENTRY_ID>
     174   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(erase_prefix)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     175                 :             : {
     176                 :           1 :     const std::string key = "key";
     177         [ +  - ]:           1 :     const std::string key2 = "key2";
     178         [ +  - ]:           1 :     const std::string value = "value";
     179         [ +  - ]:           1 :     const std::string value2 = "value_2";
     180   [ +  -  +  -  :          16 :     auto make_key = [](std::string type, std::string id) { return std::make_pair(type, id); };
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
     181                 :             : 
     182   [ +  -  +  + ]:           3 :     for (const auto& database : TestDatabases(m_path_root)) {
     183   [ +  -  -  + ]:           2 :         if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
     184                 :             :             // Skip this test if BerkeleyRO
     185                 :           0 :             continue;
     186                 :             :         }
     187         [ +  - ]:           2 :         std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
     188                 :             : 
     189                 :             :         // Write two entries with the same key type prefix, a third one with a different prefix
     190                 :             :         // and a fourth one with the type-id values inverted
     191   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Write(make_key(key, value), value));
          +  -  +  -  +  
                -  +  - ]
     192   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Write(make_key(key, value2), value2));
          +  -  +  -  +  
                -  +  - ]
     193   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Write(make_key(key2, value), value));
          +  -  +  -  +  
                -  +  - ]
     194   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Write(make_key(value, key), value));
          +  -  +  -  +  
                -  +  - ]
     195                 :             : 
     196                 :             :         // Erase the ones with the same prefix and verify result
     197   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->TxnBegin());
             +  -  +  - ]
     198   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->ErasePrefix(DataStream() << key));
          +  -  +  -  +  
                      - ]
     199   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->TxnCommit());
             +  -  +  - ]
     200                 :             : 
     201   [ +  -  +  -  :           8 :         BOOST_CHECK(!batch->Exists(make_key(key, value)));
          +  -  +  -  +  
                -  +  - ]
     202   [ +  -  +  -  :           8 :         BOOST_CHECK(!batch->Exists(make_key(key, value2)));
          +  -  +  -  +  
                -  +  - ]
     203                 :             :         // Also verify that entries with a different prefix were not erased
     204   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Exists(make_key(key2, value)));
          +  -  +  -  +  
                -  +  - ]
     205   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Exists(make_key(value, key)));
          +  -  +  -  +  
                      - ]
     206                 :           3 :     }
     207                 :           1 : }
     208                 :             : 
     209                 :             : // Test-only statement execution error
     210                 :             : constexpr int TEST_SQLITE_ERROR = -999;
     211                 :             : 
     212                 :             : class DbExecBlocker : public SQliteExecHandler
     213                 :             : {
     214                 :             : private:
     215                 :             :     SQliteExecHandler m_base_exec;
     216                 :             :     std::set<std::string> m_blocked_statements;
     217                 :             : public:
     218         [ +  - ]:           1 :     DbExecBlocker(std::set<std::string> blocked_statements) : m_blocked_statements(blocked_statements) {}
     219                 :           1 :     int Exec(SQLiteDatabase& database, const std::string& statement) override {
     220         [ -  + ]:           1 :         if (m_blocked_statements.contains(statement)) return TEST_SQLITE_ERROR;
     221                 :           0 :         return m_base_exec.Exec(database, statement);
     222                 :             :     }
     223                 :             : };
     224                 :             : 
     225   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(txn_close_failure_dangling_txn)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     226                 :             : {
     227                 :             :     // Verifies that there is no active dangling, to-be-reversed db txn
     228                 :             :     // after the batch object that initiated it is destroyed.
     229         [ +  - ]:           1 :     DatabaseOptions options;
     230                 :           1 :     DatabaseStatus status;
     231         [ +  - ]:           1 :     bilingual_str error;
     232   [ +  -  +  -  :           3 :     std::unique_ptr<SQLiteDatabase> database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error);
                   +  - ]
     233                 :             : 
     234         [ +  - ]:           1 :     std::string key = "key";
     235         [ +  - ]:           1 :     std::string value = "value";
     236                 :             : 
     237         [ +  - ]:           1 :     std::unique_ptr<SQLiteBatch> batch = std::make_unique<SQLiteBatch>(*database);
     238   [ +  -  +  -  :           2 :     BOOST_CHECK(batch->TxnBegin());
             +  -  +  - ]
     239   [ +  -  +  -  :           2 :     BOOST_CHECK(batch->Write(key, value));
             +  -  +  - ]
     240                 :             :     // Set a handler to prevent txn abortion during destruction.
     241                 :             :     // Mimicking a db statement execution failure.
     242   [ +  -  +  -  :           2 :     batch->SetExecHandler(std::make_unique<DbExecBlocker>(std::set<std::string>{"ROLLBACK TRANSACTION"}));
          +  -  +  +  -  
                      - ]
     243                 :             :     // Destroy batch
     244         [ +  - ]:           1 :     batch.reset();
     245                 :             : 
     246                 :             :     // Ensure there is no dangling, to-be-reversed db txn
     247   [ +  -  +  -  :           2 :     BOOST_CHECK(!database->HasActiveTxn());
             +  -  +  - ]
     248                 :             : 
     249                 :             :     // And, just as a sanity check; verify that new batchs only write what they suppose to write
     250                 :             :     // and nothing else.
     251         [ +  - ]:           1 :     std::string key2 = "key2";
     252         [ +  - ]:           1 :     std::unique_ptr<SQLiteBatch> batch2 = std::make_unique<SQLiteBatch>(*database);
     253   [ +  -  +  -  :           2 :     BOOST_CHECK(batch2->Write(key2, value));
             +  -  +  - ]
     254                 :             :     // The first key must not exist
     255   [ +  -  +  -  :           2 :     BOOST_CHECK(!batch2->Exists(key));
                   +  - ]
     256         [ +  - ]:           3 : }
     257                 :             : 
     258   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(concurrent_txn_dont_interfere)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     259                 :             : {
     260                 :           1 :     std::string key = "key";
     261         [ +  - ]:           1 :     std::string value = "value";
     262         [ +  - ]:           1 :     std::string value2 = "value_2";
     263                 :             : 
     264         [ +  - ]:           1 :     DatabaseOptions options;
     265                 :           1 :     DatabaseStatus status;
     266         [ +  - ]:           1 :     bilingual_str error;
     267   [ +  -  +  -  :           3 :     const auto& database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error);
                   +  - ]
     268                 :             : 
     269   [ +  -  +  - ]:           1 :     std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch();
     270                 :             : 
     271                 :             :     // Verify concurrent db transactions does not interfere between each other.
     272                 :             :     // Start db txn, write key and check the key does exist within the db txn.
     273   [ +  -  +  -  :           2 :     BOOST_CHECK(handler->TxnBegin());
             +  -  +  - ]
     274   [ +  -  +  -  :           2 :     BOOST_CHECK(handler->Write(key, value));
             +  -  +  - ]
     275   [ +  -  +  -  :           2 :     BOOST_CHECK(handler->Exists(key));
             +  -  +  - ]
     276                 :             : 
     277                 :             :     // But, the same key, does not exist in another handler
     278   [ +  -  +  - ]:           1 :     std::unique_ptr<DatabaseBatch> handler2 = Assert(database)->MakeBatch();
     279   [ +  -  +  -  :           2 :     BOOST_CHECK(handler2->Exists(key));
             +  -  +  - ]
     280                 :             : 
     281                 :             :     // Attempt to commit the handler txn calling the handler2 methods.
     282                 :             :     // Which, must not be possible.
     283   [ +  -  +  -  :           2 :     BOOST_CHECK(!handler2->TxnCommit());
             +  -  +  - ]
     284   [ +  -  +  -  :           2 :     BOOST_CHECK(!handler2->TxnAbort());
             +  -  +  - ]
     285                 :             : 
     286                 :             :     // Only the first handler can commit the changes.
     287   [ +  -  +  -  :           2 :     BOOST_CHECK(handler->TxnCommit());
             +  -  +  - ]
     288                 :             :     // And, once commit is completed, handler2 can read the record
     289         [ +  - ]:           1 :     std::string read_value;
     290   [ +  -  +  -  :           2 :     BOOST_CHECK(handler2->Read(key, read_value));
             +  -  +  - ]
     291   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(read_value, value);
     292                 :             : 
     293                 :             :     // Also, once txn is committed, single write statements are re-enabled.
     294                 :             :     // Which means that handler2 can read the record changes directly.
     295   [ +  -  +  -  :           2 :     BOOST_CHECK(handler->Write(key, value2, /*fOverwrite=*/true));
             +  -  +  - ]
     296   [ +  -  +  -  :           2 :     BOOST_CHECK(handler2->Read(key, read_value));
             +  -  +  - ]
     297   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(read_value, value2);
     298                 :           2 : }
     299                 :             : 
     300                 :             : BOOST_AUTO_TEST_SUITE_END()
     301                 :             : } // namespace wallet
        

Generated by: LCOV version 2.0-1