Skip to content

Commit 64e3f63

Browse files
MarcoFalkeknst
authored andcommitted
Merge bitcoin#16060: Bury bip9 deployments
e78aaf4 [docs] Add release notes for burying bip 9 soft fork deployments (John Newbery) 8319e73 [tests] Add coverage for the content of getblockchaininfo.softforks (James O'Beirne) 0328dcd [Consensus] Bury segwit deployment (John Newbery) 1c93b9b [Consensus] Bury CSV deployment height (John Newbery) 3862e47 [rpc] Tidy up reporting of buried and ongoing softforks (John Newbery) Pull request description: This hardcodes CSV and segwit activation heights, similar to the BIP 90 buried deployments for BIPs 34, 65 and 66. CSV and segwit have been active for over 18 months. Hardcoding the activation height is a code simplification, makes it easier to understand segwit activation status, and reduces technical debt. This was originally attempted by jl2012 in bitcoin#11398 and again by me in bitcoin#12360. ACKs for top commit: ajtowns: ACK e78aaf4 ; checked diff to previous acked commit, checked tests still work ariard: ACK e78aaf4, check diff, run the tests again and successfully activated csv/segwit heights on mainnet as expected. MarcoFalke: ACK e78aaf4 (still didn't check if the mainnet block heights are correct, but the code looks good now) Tree-SHA512: 7e951829106e21a81725f7d3e236eddbb59349189740907bb47e33f5dbf95c43753ac1231f47ae7bee85c8c81b2146afcdfdc11deb1503947f23093a9c399912
1 parent 3bad4c8 commit 64e3f63

17 files changed

+321
-224
lines changed

doc/release-notes-16060.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Low-level RPC changes
2+
----------------------
3+
4+
- Soft fork reporting in the `getblockchaininfo` return object has been
5+
updated. For full details, see the RPC help text. In summary:
6+
- The `bip9_softforks` sub-object is no longer returned
7+
- The `softforks` sub-object now returns an object keyed by soft fork name,
8+
rather than an array
9+
- Each softfork object in the `softforks` object contains a `type` value which
10+
is either `buried` (for soft fork deployments where the activation height is
11+
hard-coded into the client implementation), or `bip9` (for soft fork deployments
12+
where activation is controlled by BIP 9 signaling).
13+
14+
- `getblocktemplate` no longer returns a `rules` array containing `CSV`
15+
(the BIP 9 deployments that are currently in active state).

src/chainparams.cpp

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ class CMainParams : public CChainParams {
156156
consensus.BIP34Hash = uint256S("0x000001f35e70f7c5705f64c6c5cc3dea9449e74d5b5c7cf74dad1bcca14a8012");
157157
consensus.BIP65Height = 619382; // 00000000000076d8fcea02ec0963de4abfd01e771fec0863f960c2c64fe6f357
158158
consensus.BIP66Height = 245817; // 00000000000b1fa2dfa312863570e13fae9ca7b5566cb27e55422620b469aefa
159+
consensus.CSVHeight = 622944; // 00000000000002e3d3a6224cfce80bae367fd3283d1e5a8ba50e5e60b2d2905d
159160
consensus.DIP0001Height = 782208;
160161
consensus.DIP0003Height = 1028160;
161162
consensus.DIP0003EnforcementHeight = 1047200;
@@ -176,11 +177,6 @@ class CMainParams : public CChainParams {
176177
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008
177178
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008
178179

179-
// Deployment of BIP68, BIP112, and BIP113.
180-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].bit = 0;
181-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nStartTime = 1486252800; // Feb 5th, 2017
182-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nTimeout = 1517788800; // Feb 5th, 2018
183-
184180
// Deployment of DIP0001
185181
consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].bit = 1;
186182
consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nStartTime = 1508025600; // Oct 15th, 2017
@@ -404,6 +400,7 @@ class CTestNetParams : public CChainParams {
404400
consensus.BIP34Hash = uint256S("0x000008ebb1db2598e897d17275285767717c6acfeac4c73def49fbea1ddcbcb6");
405401
consensus.BIP65Height = 2431; // 0000039cf01242c7f921dcb4806a5994bc003b48c1973ae0c89b67809c2bb2ab
406402
consensus.BIP66Height = 2075; // 0000002acdd29a14583540cb72e1c5cc83783560e38fa7081495d474fe1671f7
403+
consensus.CSVHeight = 8064; // 00000005eb94d027e34649373669191188858a22c70f4a6d29105e559124cec7
407404
consensus.DIP0001Height = 5500;
408405
consensus.DIP0003Height = 7000;
409406
consensus.DIP0003EnforcementHeight = 7300;
@@ -424,11 +421,6 @@ class CTestNetParams : public CChainParams {
424421
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008
425422
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008
426423

427-
// Deployment of BIP68, BIP112, and BIP113.
428-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].bit = 0;
429-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nStartTime = 1544655600; // Dec 13th, 2018
430-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nTimeout = 999999999999ULL;
431-
432424
// Deployment of DIP0001
433425
consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].bit = 1;
434426
consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nStartTime = 1544655600; // Dec 13th, 2018
@@ -625,6 +617,7 @@ class CDevNetParams : public CChainParams {
625617
consensus.BIP34Height = 1; // BIP34 activated immediately on devnet
626618
consensus.BIP65Height = 1; // BIP65 activated immediately on devnet
627619
consensus.BIP66Height = 1; // BIP66 activated immediately on devnet
620+
consensus.CSVHeight = 1; // BIP68 activated immediately on devnet
628621
consensus.DIP0001Height = 2; // DIP0001 activated immediately on devnet
629622
consensus.DIP0003Height = 2; // DIP0003 activated immediately on devnet
630623
consensus.DIP0003EnforcementHeight = 2; // DIP0003 activated immediately on devnet
@@ -645,11 +638,6 @@ class CDevNetParams : public CChainParams {
645638
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008
646639
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008
647640

648-
// Deployment of BIP68, BIP112, and BIP113.
649-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].bit = 0;
650-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nStartTime = 1506556800; // September 28th, 2017
651-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nTimeout = 999999999999ULL;
652-
653641
// Deployment of DIP0001
654642
consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].bit = 1;
655643
consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nStartTime = 1505692800; // Sep 18th, 2017
@@ -917,6 +905,7 @@ class CRegTestParams : public CChainParams {
917905
consensus.BIP34Hash = uint256();
918906
consensus.BIP65Height = 1351; // BIP65 activated on regtest (Used in functional tests)
919907
consensus.BIP66Height = 1251; // BIP66 activated on regtest (Used in functional tests)
908+
consensus.CSVHeight = 432; // CSV activated on regtest (Used in rpc activation tests)
920909
consensus.DIP0001Height = 2000;
921910
consensus.DIP0003Height = 432;
922911
consensus.DIP0003EnforcementHeight = 500;
@@ -936,9 +925,6 @@ class CRegTestParams : public CChainParams {
936925
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28;
937926
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 0;
938927
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 999999999999ULL;
939-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].bit = 0;
940-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nStartTime = 0;
941-
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nTimeout = 999999999999ULL;
942928
consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].bit = 1;
943929
consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nStartTime = 0;
944930
consensus.vDeployments[Consensus::DEPLOYMENT_DIP0001].nTimeout = 999999999999ULL;
@@ -1009,7 +995,7 @@ class CRegTestParams : public CChainParams {
1009995
m_assumed_blockchain_size = 0;
1010996
m_assumed_chain_state_size = 0;
1011997

1012-
UpdateVersionBitsParametersFromArgs(args);
998+
UpdateActivationParametersFromArgs(args);
1013999
UpdateDIP3ParametersFromArgs(args);
10141000
UpdateDIP8ParametersFromArgs(args);
10151001
UpdateBudgetParametersFromArgs(args);
@@ -1105,7 +1091,7 @@ class CRegTestParams : public CChainParams {
11051091
consensus.vDeployments[d].nFalloffCoeff = nFalloffCoeff;
11061092
}
11071093
}
1108-
void UpdateVersionBitsParametersFromArgs(const ArgsManager& args);
1094+
void UpdateActivationParametersFromArgs(const ArgsManager& args);
11091095

11101096
/**
11111097
* Allows modifying the DIP3 activation and enforcement height
@@ -1171,7 +1157,7 @@ class CRegTestParams : public CChainParams {
11711157
void UpdateLLMQInstantSendDIP0024FromArgs(const ArgsManager& args);
11721158
};
11731159

1174-
void CRegTestParams::UpdateVersionBitsParametersFromArgs(const ArgsManager& args)
1160+
void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args)
11751161
{
11761162
if (!args.IsArgSet("-vbparams")) return;
11771163

src/consensus/params.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ namespace Consensus {
1515

1616
enum DeploymentPos {
1717
DEPLOYMENT_TESTDUMMY,
18-
DEPLOYMENT_CSV, // Deployment of BIP68, BIP112, and BIP113.
1918
DEPLOYMENT_DIP0001, // Deployment of DIP0001 and lower transaction fees.
2019
DEPLOYMENT_BIP147, // Deployment of BIP147 (NULLDUMMY)
2120
DEPLOYMENT_DIP0003, // Deployment of DIP0002 and DIP0003 (txv3 and deterministic MN lists)
@@ -78,6 +77,8 @@ struct Params {
7877
int BIP65Height;
7978
/** Block height at which BIP66 becomes active */
8079
int BIP66Height;
80+
/** Block height at which CSV (BIP68, BIP112 and BIP113) becomes active */
81+
int CSVHeight;
8182
/** Block height at which DIP0001 becomes active */
8283
int DIP0001Height;
8384
/** Block height at which DIP0003 becomes active */

src/rpc/blockchain.cpp

Lines changed: 56 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,54 +1418,49 @@ static UniValue verifychain(const JSONRPCRequest& request)
14181418
::ChainstateActive(), Params(), ::ChainstateActive().CoinsTip(), *node_context.evodb, check_level, check_depth);
14191419
}
14201420

1421-
/** Implementation of IsSuperMajority with better feedback */
1422-
static UniValue SoftForkMajorityDesc(int version, const CBlockIndex* pindex, const Consensus::Params& consensusParams)
1421+
static void BuriedForkDescPushBack(UniValue& softforks, const std::string &name, int height) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
14231422
{
1424-
UniValue rv(UniValue::VOBJ);
1425-
bool activated = false;
1426-
switch(version)
1427-
{
1428-
case 2:
1429-
activated = pindex->nHeight >= consensusParams.BIP34Height;
1430-
break;
1431-
case 3:
1432-
activated = pindex->nHeight >= consensusParams.BIP66Height;
1433-
break;
1434-
case 4:
1435-
activated = pindex->nHeight >= consensusParams.BIP65Height;
1436-
break;
1437-
}
1438-
rv.pushKV("status", activated);
1439-
return rv;
1440-
}
1423+
// For buried deployments.
1424+
// A buried deployment is one where the height of the activation has been hardcoded into
1425+
// the client implementation long after the consensus change has activated. See BIP 90.
1426+
// Buried deployments with activation height value of
1427+
// std::numeric_limits<int>::max() are disabled and thus hidden.
1428+
if (height == std::numeric_limits<int>::max()) return;
14411429

1442-
static UniValue SoftForkDesc(const std::string &name, int version, const CBlockIndex* pindex, const Consensus::Params& consensusParams)
1443-
{
14441430
UniValue rv(UniValue::VOBJ);
1445-
rv.pushKV("id", name);
1446-
rv.pushKV("version", version);
1447-
rv.pushKV("reject", SoftForkMajorityDesc(version, pindex, consensusParams));
1448-
return rv;
1431+
rv.pushKV("type", "buried");
1432+
// getblockchaininfo reports the softfork as active from when the chain height is
1433+
// one below the activation height
1434+
rv.pushKV("active", ::ChainActive().Tip()->nHeight + 1 >= height);
1435+
rv.pushKV("height", height);
1436+
softforks.pushKV(name, rv);
14491437
}
14501438

1451-
static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Consensus::DeploymentPos id)
1439+
static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &name, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
14521440
{
1453-
UniValue rv(UniValue::VOBJ);
1441+
// For BIP9 deployments.
1442+
// Deployments (e.g. testdummy) with timeout value before Jan 1, 2009 are hidden.
1443+
// A timeout value of 0 guarantees a softfork will never be activated.
1444+
// This is used when merging logic to implement a proposed softfork without a specified deployment schedule.
1445+
if (consensusParams.vDeployments[id].nTimeout <= 1230768000) return;
1446+
1447+
UniValue bip9(UniValue::VOBJ);
14541448
const ThresholdState thresholdState = VersionBitsState(::ChainActive().Tip(), consensusParams, id, versionbitscache);
14551449
switch (thresholdState) {
1456-
case ThresholdState::DEFINED: rv.pushKV("status", "defined"); break;
1457-
case ThresholdState::STARTED: rv.pushKV("status", "started"); break;
1458-
case ThresholdState::LOCKED_IN: rv.pushKV("status", "locked_in"); break;
1459-
case ThresholdState::ACTIVE: rv.pushKV("status", "active"); break;
1460-
case ThresholdState::FAILED: rv.pushKV("status", "failed"); break;
1450+
case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break;
1451+
case ThresholdState::STARTED: bip9.pushKV("status", "started"); break;
1452+
case ThresholdState::LOCKED_IN: bip9.pushKV("status", "locked_in"); break;
1453+
case ThresholdState::ACTIVE: bip9.pushKV("status", "active"); break;
1454+
case ThresholdState::FAILED: bip9.pushKV("status", "failed"); break;
14611455
}
14621456
if (ThresholdState::STARTED == thresholdState)
14631457
{
1464-
rv.pushKV("bit", consensusParams.vDeployments[id].bit);
1458+
bip9.pushKV("bit", consensusParams.vDeployments[id].bit);
14651459
}
1466-
rv.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
1467-
rv.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
1468-
rv.pushKV("since", VersionBitsStateSinceHeight(::ChainActive().Tip(), consensusParams, id, versionbitscache));
1460+
bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
1461+
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
1462+
int64_t since_height = VersionBitsStateSinceHeight(::ChainActive().Tip(), consensusParams, id, versionbitscache);
1463+
bip9.pushKV("since", since_height);
14691464
if (ThresholdState::STARTED == thresholdState)
14701465
{
14711466
UniValue statsUV(UniValue::VOBJ);
@@ -1475,18 +1470,18 @@ static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Conse
14751470
statsUV.pushKV("elapsed", statsStruct.elapsed);
14761471
statsUV.pushKV("count", statsStruct.count);
14771472
statsUV.pushKV("possible", statsStruct.possible);
1478-
rv.pushKV("statistics", statsUV);
1473+
bip9.pushKV("statistics", statsUV);
14791474
}
1480-
return rv;
1481-
}
14821475

1483-
static void BIP9SoftForkDescPushBack(UniValue& bip9_softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id)
1484-
{
1485-
// Deployments with timeout value of 0 are hidden.
1486-
// A timeout value of 0 guarantees a softfork will never be activated.
1487-
// This is used when softfork codes are merged without specifying the deployment schedule.
1488-
if (consensusParams.vDeployments[id].nTimeout > 0)
1489-
bip9_softforks.pushKV(VersionBitsDeploymentInfo[id].name, BIP9SoftForkDesc(consensusParams, id));
1476+
UniValue rv(UniValue::VOBJ);
1477+
rv.pushKV("type", "bip9");
1478+
rv.pushKV("bip9", bip9);
1479+
if (ThresholdState::ACTIVE == thresholdState) {
1480+
rv.pushKV("height", since_height);
1481+
}
1482+
rv.pushKV("active", ThresholdState::ACTIVE == thresholdState);
1483+
1484+
softforks.pushKV(name, rv);
14901485
}
14911486

14921487
UniValue getblockchaininfo(const JSONRPCRequest& request)
@@ -1512,28 +1507,17 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
15121507
{RPCResult::Type::NUM, "pruneheight", "lowest-height complete block stored (only present if pruning is enabled)"},
15131508
{RPCResult::Type::BOOL, "automatic_pruning", "whether automatic pruning is enabled (only present if pruning is enabled)"},
15141509
{RPCResult::Type::NUM, "prune_target_size", "the target size used by pruning (only present if automatic pruning is enabled)"},
1515-
{RPCResult::Type::ARR, "softforks", "status of softforks in progress",
1516-
{
1517-
{RPCResult::Type::OBJ, "", "",
1518-
{
1519-
{RPCResult::Type::STR, "xxxx", "name of the softfork"},
1520-
{RPCResult::Type::STR, "version", "block version"},
1521-
{RPCResult::Type::OBJ, "reject", "progress toward rejecting pre-softfork blocks",
1522-
{
1523-
{RPCResult::Type::BOOL, "status", "true if threshold reached"},
1524-
}},
1525-
}},
1526-
}},
1527-
{RPCResult::Type::OBJ_DYN, "bip9_softforks", "status of BIP9 softforks in progress",
1510+
{RPCResult::Type::OBJ, "softforks", "status of softforks in progress",
15281511
{
1529-
{RPCResult::Type::OBJ, "xxxx", "name of the softfork",
1512+
{RPCResult::Type::STR, "type", "one of \"buried\", \"bip9\""},
1513+
{RPCResult::Type::OBJ, "bip9", "status of bip9 softforks (only for \"bip9\" type)",
15301514
{
15311515
{RPCResult::Type::STR, "status", "one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\""},
15321516
{RPCResult::Type::NUM, "bit", "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)"},
15331517
{RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"},
15341518
{RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"},
15351519
{RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
1536-
{RPCResult::Type::OBJ, "statistics", "numeric statistics about BIP9 signalling for a softfork (only for \"started\" status)",
1520+
{RPCResult::Type::OBJ, "statistics", "numeric statistics about BIP9 signalling for a softfork",
15371521
{
15381522
{RPCResult::Type::NUM, "period", "the length in blocks of the BIP9 signalling period"},
15391523
{RPCResult::Type::NUM, "threshold", "the number of blocks with the version bit set required to activate the feature"},
@@ -1542,6 +1526,8 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
15421526
{RPCResult::Type::BOOL, "possible", "returns false if there are not enough blocks left in this period to pass activation threshold"},
15431527
}},
15441528
}},
1529+
{RPCResult::Type::NUM, "height", "height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)"},
1530+
{RPCResult::Type::BOOL, "active", "true if the rules are enforced for the mempool and the next block"},
15451531
}},
15461532
{RPCResult::Type::STR, "warnings", "any network and blockchain warnings"},
15471533
}},
@@ -1586,17 +1572,17 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
15861572
}
15871573

15881574
const Consensus::Params& consensusParams = Params().GetConsensus();
1589-
UniValue softforks(UniValue::VARR);
1590-
UniValue bip9_softforks(UniValue::VOBJ);
1575+
UniValue softforks(UniValue::VOBJ);
15911576
// sorted by activation block
1592-
softforks.push_back(SoftForkDesc("bip34", 2, tip, consensusParams));
1593-
softforks.push_back(SoftForkDesc("bip66", 3, tip, consensusParams));
1594-
softforks.push_back(SoftForkDesc("bip65", 4, tip, consensusParams));
1595-
for (int pos = Consensus::DEPLOYMENT_CSV; pos != Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++pos) {
1596-
BIP9SoftForkDescPushBack(bip9_softforks, consensusParams, static_cast<Consensus::DeploymentPos>(pos));
1597-
}
1577+
BuriedForkDescPushBack(softforks,"bip34", consensusParams.BIP34Height);
1578+
BuriedForkDescPushBack(softforks,"bip66", consensusParams.BIP66Height);
1579+
BuriedForkDescPushBack(softforks,"bip65", consensusParams.BIP65Height);
1580+
BuriedForkDescPushBack(softforks, "csv", consensusParams.CSVHeight);
1581+
for (int pos = Consensus::DEPLOYMENT_TESTDUMMY + 1; pos != Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++pos) {
1582+
BIP9SoftForkDescPushBack(softforks, VersionBitsDeploymentInfo[pos].name, consensusParams, static_cast<Consensus::DeploymentPos>(pos));
1583+
}
1584+
BIP9SoftForkDescPushBack(softforks, "testdummy", consensusParams, Consensus::DEPLOYMENT_TESTDUMMY);
15981585
obj.pushKV("softforks", softforks);
1599-
obj.pushKV("bip9_softforks", bip9_softforks);
16001586

16011587
obj.pushKV("warnings", GetWarnings(false));
16021588
return obj;

0 commit comments

Comments
 (0)