mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-05 14:06:27 -05:00
Merge bitcoin/bitcoin#30352: policy: Add PayToAnchor(P2A), OP_1 <0x4e73>
as a standard output script for spending
75648cea5a
test: add P2A ProduceSignature coverage (Greg Sanders)7998ce6b20
Add release note for P2A output feature (Greg Sanders)71c9b02a04
test: add P2A coverage for decodescript (Greg Sanders)1349e9ec15
test: Add anchor mempool acceptance test (Greg Sanders)9d89209937
policy: stop 3rd party wtxid malleability of anchor spend (Greg Sanders)b60aaf8b23
policy: make anchor spend standard (Greg Sanders)455fca86cf
policy: Add OP_1 <0x4e73> as a standard output type (Greg Sanders) Pull request description: This is a sub-feature taken out of the original proposal for ephemeral anchors #30239 This PR makes *spending* of `OP_1 <0x4e73>` (i.e. `bc1pfeessrawgf`) standard. Creation of this output type is already standard. Any future witness output types are considered relay-standard to create, but not to spend. This preserves upgrade hooks, such as a completely new output type for a softfork such as BIP341. It also gives us a bit of room to use a new output type for policy uses. This particular sized witness program has no other known use-cases (https://bitcoin.stackexchange.com/a/110664/17078), s it affords insufficient cryptographic security for a secure commitment to data, such as a script or a public key. This makes this type of output "keyless", or unauthenticated. As a witness program, the `scriptSig` of the input MUST be blank, by BIP141. This helps ensure txid-stability of the spending transaction, which may be required for smart contracting wallets. If we do not use segwit, a miner can simply insert an `OP_NOP` in the `scriptSig` without effecting the result of program execution. An additional relay restriction is to disallow non-empty witness data, which an adversary may use to penalize the "honest" transactor when RBF'ing the transaction due to the incremental fee requirement of RBF rules. The intended use-case for this output type is to "anchor" the transaction with a spending child to bring exogenous CPFP fees into the transaction package, encouraging the inclusion of the package in a block. The minimal size of creation and spending of this output makes it an attractive contrast to outputs like `p2sh(OP_TRUE)` and `p2wsh(OP_TRUE)` which are significantly larger in vbyte terms. Combined with TRUC transactions which limits the size of child transactions significantly, this is an attractive option for presigned transactions that need to be fee-bumped after the fact. ACKs for top commit: sdaftuar: utACK75648cea5a
theStack: re-ACK75648cea5a
ismaelsadeeq: re-ACK75648cea5a
via [diff](e7ce6dc070..75648cea5a
) glozow: ACK75648cea5a
tdb3: ACK75648cea5a
Tree-SHA512: d529de23d20857e6cdb40fa611d0446b49989eaafed06c28280e8fd1897f1ed8d89a4eabbec1bbf8df3d319910066c3dbbba5a70a87ff0b2967d5205db32ad1e
This commit is contained in:
commit
2aff9a36c3
24 changed files with 200 additions and 3 deletions
10
doc/release-notes-30352.md
Normal file
10
doc/release-notes-30352.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
P2P and network changes
|
||||
-----------------------
|
||||
|
||||
- Pay To Anchor(P2A) is a new standard witness output type for spending,
|
||||
a newly recognised output template. This allows for key-less anchor
|
||||
outputs, with compact spending conditions for additional efficiencies on
|
||||
top of an equivalent `sh(OP_TRUE)` output, in addition to the txid stability
|
||||
of the spending transaction.
|
||||
N.B. propagation of this output spending on the network will be limited
|
||||
until a sufficient number of nodes on the network adopt this upgrade.
|
|
@ -87,6 +87,10 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
|
|||
addressRet = tap;
|
||||
return true;
|
||||
}
|
||||
case TxoutType::ANCHOR: {
|
||||
addressRet = PayToAnchor();
|
||||
return true;
|
||||
}
|
||||
case TxoutType::WITNESS_UNKNOWN: {
|
||||
addressRet = WitnessUnknown{vSolutions[0][0], vSolutions[1]};
|
||||
return true;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <pubkey.h>
|
||||
#include <script/script.h>
|
||||
#include <uint256.h>
|
||||
#include <util/check.h>
|
||||
#include <util/hash_type.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -116,6 +117,13 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
struct PayToAnchor : public WitnessUnknown
|
||||
{
|
||||
PayToAnchor() : WitnessUnknown(1, {0x4e, 0x73}) {
|
||||
Assume(CScript::IsPayToAnchor(1, {0x4e, 0x73}));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* A txout script categorized into standard templates.
|
||||
* * CNoDestination: Optionally a script, no corresponding address.
|
||||
|
@ -125,10 +133,11 @@ public:
|
|||
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH address)
|
||||
* * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH address)
|
||||
* * WitnessV1Taproot: TxoutType::WITNESS_V1_TAPROOT destination (P2TR address)
|
||||
* * PayToAnchor: TxoutType::ANCHOR destination (P2A address)
|
||||
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? address)
|
||||
* A CTxDestination is the internal data type encoded in a bitcoin address
|
||||
*/
|
||||
using CTxDestination = std::variant<CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown>;
|
||||
using CTxDestination = std::variant<CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown>;
|
||||
|
||||
/** Check whether a CTxDestination corresponds to one with an address. */
|
||||
bool IsValidDestination(const CTxDestination& dest);
|
||||
|
|
|
@ -181,6 +181,10 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
|
|||
return tap;
|
||||
}
|
||||
|
||||
if (CScript::IsPayToAnchor(version, data)) {
|
||||
return PayToAnchor();
|
||||
}
|
||||
|
||||
if (version > 16) {
|
||||
error_str = "Invalid Bech32 address witness version";
|
||||
return CNoDestination();
|
||||
|
|
|
@ -225,6 +225,11 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
|
|||
// get the scriptPubKey corresponding to this input:
|
||||
CScript prevScript = prev.scriptPubKey;
|
||||
|
||||
// witness stuffing detected
|
||||
if (prevScript.IsPayToAnchor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool p2sh = false;
|
||||
if (prevScript.IsPayToScriptHash()) {
|
||||
std::vector <std::vector<unsigned char> > stack;
|
||||
|
|
|
@ -557,6 +557,7 @@ static RPCHelpMan decodescript()
|
|||
case TxoutType::SCRIPTHASH:
|
||||
case TxoutType::WITNESS_UNKNOWN:
|
||||
case TxoutType::WITNESS_V1_TAPROOT:
|
||||
case TxoutType::ANCHOR:
|
||||
// Should not be wrapped
|
||||
return false;
|
||||
} // no default case, so the compiler can warn about missing cases
|
||||
|
@ -599,6 +600,7 @@ static RPCHelpMan decodescript()
|
|||
case TxoutType::WITNESS_V0_KEYHASH:
|
||||
case TxoutType::WITNESS_V0_SCRIPTHASH:
|
||||
case TxoutType::WITNESS_V1_TAPROOT:
|
||||
case TxoutType::ANCHOR:
|
||||
// Should not be wrapped
|
||||
return false;
|
||||
} // no default case, so the compiler can warn about missing cases
|
||||
|
|
|
@ -332,6 +332,14 @@ public:
|
|||
return obj;
|
||||
}
|
||||
|
||||
UniValue operator()(const PayToAnchor& anchor) const
|
||||
{
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("isscript", true);
|
||||
obj.pushKV("iswitness", true);
|
||||
return obj;
|
||||
}
|
||||
|
||||
UniValue operator()(const WitnessUnknown& id) const
|
||||
{
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
|
|
|
@ -1943,6 +1943,8 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
|
|||
}
|
||||
return set_success(serror);
|
||||
}
|
||||
} else if (!is_p2sh && CScript::IsPayToAnchor(witversion, program)) {
|
||||
return true;
|
||||
} else {
|
||||
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) {
|
||||
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM);
|
||||
|
|
|
@ -204,6 +204,23 @@ unsigned int CScript::GetSigOpCount(const CScript& scriptSig) const
|
|||
return subscript.GetSigOpCount(true);
|
||||
}
|
||||
|
||||
bool CScript::IsPayToAnchor() const
|
||||
{
|
||||
return (this->size() == 4 &&
|
||||
(*this)[0] == OP_1 &&
|
||||
(*this)[1] == 0x02 &&
|
||||
(*this)[2] == 0x4e &&
|
||||
(*this)[3] == 0x73);
|
||||
}
|
||||
|
||||
bool CScript::IsPayToAnchor(int version, const std::vector<unsigned char>& program)
|
||||
{
|
||||
return version == 1 &&
|
||||
program.size() == 2 &&
|
||||
program[0] == 0x4e &&
|
||||
program[1] == 0x73;
|
||||
}
|
||||
|
||||
bool CScript::IsPayToScriptHash() const
|
||||
{
|
||||
// Extra-fast test for pay-to-script-hash CScripts:
|
||||
|
|
|
@ -533,6 +533,14 @@ public:
|
|||
*/
|
||||
unsigned int GetSigOpCount(const CScript& scriptSig) const;
|
||||
|
||||
/*
|
||||
* OP_1 <0x4e73>
|
||||
*/
|
||||
bool IsPayToAnchor() const;
|
||||
/** Checks if output of IsWitnessProgram comes from a P2A output script
|
||||
*/
|
||||
static bool IsPayToAnchor(int version, const std::vector<unsigned char>& program);
|
||||
|
||||
bool IsPayToScriptHash() const;
|
||||
bool IsPayToWitnessScriptHash() const;
|
||||
bool IsWitnessProgram(int& version, std::vector<unsigned char>& program) const;
|
||||
|
|
|
@ -475,6 +475,9 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
|
|||
|
||||
case TxoutType::WITNESS_V1_TAPROOT:
|
||||
return SignTaproot(provider, creator, WitnessV1Taproot(XOnlyPubKey{vSolutions[0]}), sigdata, ret);
|
||||
|
||||
case TxoutType::ANCHOR:
|
||||
return true;
|
||||
} // no default case, so the compiler can warn about missing cases
|
||||
assert(false);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ std::string GetTxnOutputType(TxoutType t)
|
|||
case TxoutType::SCRIPTHASH: return "scripthash";
|
||||
case TxoutType::MULTISIG: return "multisig";
|
||||
case TxoutType::NULL_DATA: return "nulldata";
|
||||
case TxoutType::ANCHOR: return "anchor";
|
||||
case TxoutType::WITNESS_V0_KEYHASH: return "witness_v0_keyhash";
|
||||
case TxoutType::WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash";
|
||||
case TxoutType::WITNESS_V1_TAPROOT: return "witness_v1_taproot";
|
||||
|
@ -165,6 +166,9 @@ TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned c
|
|||
vSolutionsRet.push_back(std::move(witnessprogram));
|
||||
return TxoutType::WITNESS_V1_TAPROOT;
|
||||
}
|
||||
if (scriptPubKey.IsPayToAnchor()) {
|
||||
return TxoutType::ANCHOR;
|
||||
}
|
||||
if (witnessversion != 0) {
|
||||
vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
|
||||
vSolutionsRet.push_back(std::move(witnessprogram));
|
||||
|
|
|
@ -22,6 +22,7 @@ template <typename C> class Span;
|
|||
enum class TxoutType {
|
||||
NONSTANDARD,
|
||||
// 'standard' transaction types:
|
||||
ANCHOR, //!< anyone can spend script
|
||||
PUBKEY,
|
||||
PUBKEYHASH,
|
||||
SCRIPTHASH,
|
||||
|
|
|
@ -76,11 +76,13 @@ FUZZ_TARGET(script, .init = initialize_script)
|
|||
assert(which_type == TxoutType::PUBKEY ||
|
||||
which_type == TxoutType::NONSTANDARD ||
|
||||
which_type == TxoutType::NULL_DATA ||
|
||||
which_type == TxoutType::MULTISIG);
|
||||
which_type == TxoutType::MULTISIG ||
|
||||
which_type == TxoutType::ANCHOR);
|
||||
}
|
||||
if (which_type == TxoutType::NONSTANDARD ||
|
||||
which_type == TxoutType::NULL_DATA ||
|
||||
which_type == TxoutType::MULTISIG) {
|
||||
which_type == TxoutType::MULTISIG ||
|
||||
which_type == TxoutType::ANCHOR) {
|
||||
assert(!extract_destination_ret);
|
||||
}
|
||||
|
||||
|
@ -94,6 +96,7 @@ FUZZ_TARGET(script, .init = initialize_script)
|
|||
(void)Solver(script, solutions);
|
||||
|
||||
(void)script.HasValidOps();
|
||||
(void)script.IsPayToAnchor();
|
||||
(void)script.IsPayToScriptHash();
|
||||
(void)script.IsPayToWitnessScriptHash();
|
||||
(void)script.IsPushOnly();
|
||||
|
|
|
@ -213,6 +213,9 @@ CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) no
|
|||
[&] {
|
||||
tx_destination = WitnessV1Taproot{XOnlyPubKey{ConsumeUInt256(fuzzed_data_provider)}};
|
||||
},
|
||||
[&] {
|
||||
tx_destination = PayToAnchor{};
|
||||
},
|
||||
[&] {
|
||||
std::vector<unsigned char> program{ConsumeRandomLengthByteVector(fuzzed_data_provider, /*max_length=*/40)};
|
||||
if (program.size() < 2) {
|
||||
|
|
|
@ -128,6 +128,20 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_success)
|
|||
BOOST_CHECK(solutions[0] == std::vector<unsigned char>{16});
|
||||
BOOST_CHECK(solutions[1] == ToByteVector(uint256::ONE));
|
||||
|
||||
// TxoutType::ANCHOR
|
||||
std::vector<unsigned char> anchor_bytes{0x4e, 0x73};
|
||||
s.clear();
|
||||
s << OP_1 << anchor_bytes;
|
||||
BOOST_CHECK_EQUAL(Solver(s, solutions), TxoutType::ANCHOR);
|
||||
BOOST_CHECK(solutions.empty());
|
||||
|
||||
// Sanity-check IsPayToAnchor
|
||||
int version{-1};
|
||||
std::vector<unsigned char> witness_program;
|
||||
BOOST_CHECK(s.IsPayToAnchor());
|
||||
BOOST_CHECK(s.IsWitnessProgram(version, witness_program));
|
||||
BOOST_CHECK(CScript::IsPayToAnchor(version, witness_program));
|
||||
|
||||
// TxoutType::NONSTANDARD
|
||||
s.clear();
|
||||
s << OP_9 << OP_ADD << OP_11 << OP_EQUAL;
|
||||
|
@ -186,6 +200,18 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_failure)
|
|||
s.clear();
|
||||
s << OP_0 << std::vector<unsigned char>(19, 0x01);
|
||||
BOOST_CHECK_EQUAL(Solver(s, solutions), TxoutType::NONSTANDARD);
|
||||
|
||||
// TxoutType::ANCHOR but wrong witness version
|
||||
s.clear();
|
||||
s << OP_2 << std::vector<unsigned char>{0x4e, 0x73};
|
||||
BOOST_CHECK(!s.IsPayToAnchor());
|
||||
BOOST_CHECK_EQUAL(Solver(s, solutions), TxoutType::WITNESS_UNKNOWN);
|
||||
|
||||
// TxoutType::ANCHOR but wrong 2-byte data push
|
||||
s.clear();
|
||||
s << OP_1 << std::vector<unsigned char>{0xff, 0xff};
|
||||
BOOST_CHECK(!s.IsPayToAnchor());
|
||||
BOOST_CHECK_EQUAL(Solver(s, solutions), TxoutType::WITNESS_UNKNOWN);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(script_standard_ExtractDestination)
|
||||
|
|
|
@ -1277,6 +1277,19 @@ BOOST_AUTO_TEST_CASE(sign_invalid_miniscript)
|
|||
BOOST_CHECK(!SignSignature(keystore, CTransaction(prev), curr, 0, SIGHASH_ALL, sig_data));
|
||||
}
|
||||
|
||||
/* P2A input should be considered signed. */
|
||||
BOOST_AUTO_TEST_CASE(sign_paytoanchor)
|
||||
{
|
||||
FillableSigningProvider keystore;
|
||||
SignatureData sig_data;
|
||||
CMutableTransaction prev, curr;
|
||||
prev.vout.emplace_back(0, GetScriptForDestination(PayToAnchor{}));
|
||||
|
||||
curr.vin.emplace_back(COutPoint{prev.GetHash(), 0});
|
||||
|
||||
BOOST_CHECK(SignSignature(keystore, CTransaction(prev), curr, 0, SIGHASH_ALL, sig_data));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(script_standard_push)
|
||||
{
|
||||
ScriptError err;
|
||||
|
|
|
@ -1026,6 +1026,14 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
|
|||
t.vout[0].nValue = 239;
|
||||
CheckIsNotStandard(t, "dust");
|
||||
}
|
||||
|
||||
// Check anchor outputs
|
||||
t.vout[0].scriptPubKey = CScript() << OP_1 << std::vector<unsigned char>{0x4e, 0x73};
|
||||
BOOST_CHECK(t.vout[0].scriptPubKey.IsPayToAnchor());
|
||||
t.vout[0].nValue = 240;
|
||||
CheckIsStandard(t);
|
||||
t.vout[0].nValue = 239;
|
||||
CheckIsNotStandard(t, "dust");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
@ -501,6 +501,7 @@ public:
|
|||
}
|
||||
|
||||
UniValue operator()(const WitnessV1Taproot& id) const { return UniValue(UniValue::VOBJ); }
|
||||
UniValue operator()(const PayToAnchor& id) const { return UniValue(UniValue::VOBJ); }
|
||||
UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); }
|
||||
};
|
||||
|
||||
|
|
|
@ -910,6 +910,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d
|
|||
case TxoutType::NONSTANDARD:
|
||||
case TxoutType::WITNESS_UNKNOWN:
|
||||
case TxoutType::WITNESS_V1_TAPROOT:
|
||||
case TxoutType::ANCHOR:
|
||||
return "unrecognized script";
|
||||
} // no default case, so the compiler can warn about missing cases
|
||||
NONFATAL_UNREACHABLE();
|
||||
|
|
|
@ -115,6 +115,7 @@ IsMineResult IsMineInner(const LegacyDataSPKM& keystore, const CScript& scriptPu
|
|||
case TxoutType::NULL_DATA:
|
||||
case TxoutType::WITNESS_UNKNOWN:
|
||||
case TxoutType::WITNESS_V1_TAPROOT:
|
||||
case TxoutType::ANCHOR:
|
||||
break;
|
||||
case TxoutType::PUBKEY:
|
||||
keyID = CPubKey(vSolutions[0]).GetID();
|
||||
|
|
|
@ -37,6 +37,7 @@ from test_framework.script_util import (
|
|||
keys_to_multisig_script,
|
||||
MIN_PADDING,
|
||||
MIN_STANDARD_TX_NONWITNESS_SIZE,
|
||||
PAY_TO_ANCHOR,
|
||||
script_to_p2sh_script,
|
||||
script_to_p2wsh_script,
|
||||
)
|
||||
|
@ -389,6 +390,56 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
|
|||
maxfeerate=0,
|
||||
)
|
||||
|
||||
self.log.info('OP_1 <0x4e73> is able to be created and spent')
|
||||
anchor_value = 10000
|
||||
create_anchor_tx = self.wallet.send_to(from_node=node, scriptPubKey=PAY_TO_ANCHOR, amount=anchor_value)
|
||||
self.generate(node, 1)
|
||||
|
||||
# First spend has non-empty witness, will be rejected to prevent third party wtxid malleability
|
||||
anchor_nonempty_wit_spend = CTransaction()
|
||||
anchor_nonempty_wit_spend.vin.append(CTxIn(COutPoint(int(create_anchor_tx["txid"], 16), create_anchor_tx["sent_vout"]), b""))
|
||||
anchor_nonempty_wit_spend.vout.append(CTxOut(anchor_value - int(fee*COIN), script_to_p2wsh_script(CScript([OP_TRUE]))))
|
||||
anchor_nonempty_wit_spend.wit.vtxinwit.append(CTxInWitness())
|
||||
anchor_nonempty_wit_spend.wit.vtxinwit[0].scriptWitness.stack.append(b"f")
|
||||
anchor_nonempty_wit_spend.rehash()
|
||||
|
||||
self.check_mempool_result(
|
||||
result_expected=[{'txid': anchor_nonempty_wit_spend.rehash(), 'allowed': False, 'reject-reason': 'bad-witness-nonstandard'}],
|
||||
rawtxs=[anchor_nonempty_wit_spend.serialize().hex()],
|
||||
maxfeerate=0,
|
||||
)
|
||||
|
||||
# Clear witness stuffing
|
||||
anchor_spend = anchor_nonempty_wit_spend
|
||||
anchor_spend.wit.vtxinwit[0].scriptWitness.stack = []
|
||||
anchor_spend.rehash()
|
||||
|
||||
self.check_mempool_result(
|
||||
result_expected=[{'txid': anchor_spend.rehash(), 'allowed': True, 'vsize': anchor_spend.get_vsize(), 'fees': { 'base': Decimal('0.00000700')}}],
|
||||
rawtxs=[anchor_spend.serialize().hex()],
|
||||
maxfeerate=0,
|
||||
)
|
||||
|
||||
self.log.info('But cannot be spent if nested sh()')
|
||||
nested_anchor_tx = self.wallet.create_self_transfer(sequence=SEQUENCE_FINAL)['tx']
|
||||
nested_anchor_tx.vout[0].scriptPubKey = script_to_p2sh_script(PAY_TO_ANCHOR)
|
||||
nested_anchor_tx.rehash()
|
||||
self.generateblock(node, self.wallet.get_address(), [nested_anchor_tx.serialize().hex()])
|
||||
|
||||
nested_anchor_spend = CTransaction()
|
||||
nested_anchor_spend.vin.append(CTxIn(COutPoint(nested_anchor_tx.sha256, 0), b""))
|
||||
nested_anchor_spend.vin[0].scriptSig = CScript([bytes(PAY_TO_ANCHOR)])
|
||||
nested_anchor_spend.vout.append(CTxOut(nested_anchor_tx.vout[0].nValue - int(fee*COIN), script_to_p2wsh_script(CScript([OP_TRUE]))))
|
||||
nested_anchor_spend.rehash()
|
||||
|
||||
self.check_mempool_result(
|
||||
result_expected=[{'txid': nested_anchor_spend.rehash(), 'allowed': False, 'reject-reason': 'non-mandatory-script-verify-flag (Witness version reserved for soft-fork upgrades)'}],
|
||||
rawtxs=[nested_anchor_spend.serialize().hex()],
|
||||
maxfeerate=0,
|
||||
)
|
||||
# but is consensus-legal
|
||||
self.generateblock(node, self.wallet.get_address(), [nested_anchor_spend.serialize().hex()])
|
||||
|
||||
self.log.info('Spending a confirmed bare multisig is okay')
|
||||
address = self.wallet.get_address()
|
||||
tx = tx_from_hex(raw_tx_reference)
|
||||
|
|
|
@ -187,6 +187,16 @@ class DecodeScriptTest(BitcoinTestFramework):
|
|||
assert_equal('1 ' + xonly_public_key, rpc_result['asm'])
|
||||
assert 'segwit' not in rpc_result
|
||||
|
||||
self.log.info("- P2A (anchor)")
|
||||
# 1 <4e73>
|
||||
witprog_hex = '4e73'
|
||||
rpc_result = self.nodes[0].decodescript('5102' + witprog_hex)
|
||||
assert_equal('anchor', rpc_result['type'])
|
||||
# in the disassembly, the witness program is shown as single decimal due to its small size
|
||||
witprog_as_decimal = int.from_bytes(bytes.fromhex(witprog_hex), 'little')
|
||||
assert_equal(f'1 {witprog_as_decimal}', rpc_result['asm'])
|
||||
assert_equal('bcrt1pfeesnyr2tx', rpc_result['address'])
|
||||
|
||||
def decoderawtransaction_asm_sighashtype(self):
|
||||
"""Test decoding scripts via RPC command "decoderawtransaction".
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import unittest
|
|||
from test_framework.script import (
|
||||
CScript,
|
||||
OP_0,
|
||||
OP_1,
|
||||
OP_15,
|
||||
OP_16,
|
||||
OP_CHECKMULTISIG,
|
||||
|
@ -42,6 +43,8 @@ assert MIN_PADDING == 5
|
|||
DUMMY_MIN_OP_RETURN_SCRIPT = CScript([OP_RETURN] + ([OP_0] * (MIN_PADDING - 1)))
|
||||
assert len(DUMMY_MIN_OP_RETURN_SCRIPT) == MIN_PADDING
|
||||
|
||||
PAY_TO_ANCHOR = CScript([OP_1, bytes.fromhex("4e73")])
|
||||
|
||||
def key_to_p2pk_script(key):
|
||||
key = check_key(key)
|
||||
return CScript([key, OP_CHECKSIG])
|
||||
|
|
Loading…
Add table
Reference in a new issue