change(rpc): support transparent p2pkh miner addresses (#5827)

* support p2pkh miner address

* Tweak comments and log messages

* removes duplicate/unused method

Co-authored-by: teor <teor@riseup.net>
Co-authored-by: Arya <aryasolhi@gmail.com>
This commit is contained in:
Alfredo Garcia 2022-12-09 02:17:55 -03:00 committed by GitHub
parent 5b054c968e
commit 12ff32f445
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 74 additions and 37 deletions

View File

@ -2,6 +2,7 @@
mod address;
mod keys;
mod opcodes;
mod script;
mod serialize;
mod utxo;

View File

@ -10,7 +10,7 @@ use sha2::Sha256;
use crate::{
parameters::Network,
serialization::{SerializationError, ZcashDeserialize, ZcashSerialize},
transparent::Script,
transparent::{opcodes::OpCode, Script},
};
#[cfg(test)]
@ -254,6 +254,33 @@ impl Address {
payload[..].copy_from_slice(&ripe_hash[..]);
payload
}
/// Given a transparent address (P2SH or a P2PKH), create a script that can be used in a coinbase
/// transaction output.
pub fn create_script_from_address(&self) -> Script {
let mut script_bytes = Vec::new();
match self {
// https://developer.bitcoin.org/devguide/transactions.html#pay-to-script-hash-p2sh
Address::PayToScriptHash { .. } => {
script_bytes.push(OpCode::Hash160 as u8);
script_bytes.push(OpCode::Push20Bytes as u8);
script_bytes.extend(self.hash_bytes());
script_bytes.push(OpCode::Equal as u8);
}
// https://developer.bitcoin.org/devguide/transactions.html#pay-to-public-key-hash-p2pkh
Address::PayToPublicKeyHash { .. } => {
script_bytes.push(OpCode::Dup as u8);
script_bytes.push(OpCode::Hash160 as u8);
script_bytes.push(OpCode::Push20Bytes as u8);
script_bytes.extend(self.hash_bytes());
script_bytes.push(OpCode::EqualVerify as u8);
script_bytes.push(OpCode::CheckSig as u8);
}
};
Script::new(&script_bytes)
}
}
#[cfg(test)]

View File

@ -0,0 +1,15 @@
//! Zebra script opcodes.
/// Supported opcodes
///
/// <https://github.com/zcash/zcash/blob/8b16094f6672d8268ff25b2d7bddd6a6207873f7/src/script/script.h#L39>
pub enum OpCode {
// Opcodes used to generate P2SH scripts.
Equal = 0x87,
Hash160 = 0xa9,
Push20Bytes = 0x14,
// Additional opcodes used to generate P2PKH scripts.
Dup = 0x76,
EqualVerify = 0x88,
CheckSig = 0xac,
}

View File

@ -6,7 +6,6 @@ use zebra_chain::{
amount::{Amount, Error, NonNegative},
block::Height,
parameters::{Network, NetworkUpgrade::*},
serialization::ZcashSerialize,
transaction::Transaction,
transparent::{Address, Output, Script},
};
@ -141,25 +140,14 @@ pub fn new_coinbase_script(address: Address) -> Script {
assert!(
address.is_script_hash(),
"incorrect coinbase script address: {address} \
Zebra only supports transparent 'pay to script hash' (P2SH) addresses",
Funding streams only support transparent 'pay to script hash' (P2SH) addresses",
);
let address_hash = address
.zcash_serialize_to_vec()
.expect("we should get address bytes here");
// > The “prescribed way” to pay a transparent P2SH address is to use a standard P2SH script
// > of the form OP_HASH160 fs.RedeemScriptHash(height) OP_EQUAL as the scriptPubKey.
//
// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
let mut script_bytes = Vec::new();
script_bytes.push(OpCode::Hash160 as u8);
script_bytes.push(OpCode::Push20Bytes as u8);
script_bytes.extend(&address_hash[2..22]);
script_bytes.push(OpCode::Equal as u8);
Script::new(&script_bytes)
address.create_script_from_address()
}
/// Returns a list of outputs in `Transaction`, which have a script address equal to `Address`.
@ -171,10 +159,3 @@ pub fn filter_outputs_by_address(transaction: &Transaction, address: Address) ->
.cloned()
.collect()
}
/// Script opcodes needed to compare the `lock_script` with the funding stream reward address.
pub enum OpCode {
Equal = 0x87,
Hash160 = 0xa9,
Push20Bytes = 0x14,
}

View File

@ -21,10 +21,12 @@ use zebra_chain::{
transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
transparent,
};
use zebra_consensus::{
funding_stream_address, funding_stream_values, miner_subsidy, new_coinbase_script,
VerifyChainError, MAX_BLOCK_SIGOPS,
funding_stream_address, funding_stream_values, miner_subsidy, VerifyChainError,
MAX_BLOCK_SIGOPS,
};
use zebra_node_services::mempool;
use zebra_state::{ReadRequest, ReadResponse};
@ -659,7 +661,7 @@ pub fn standard_coinbase_outputs(
coinbase_outputs
.iter()
.map(|(amount, address)| (*amount, new_coinbase_script(*address)))
.map(|(amount, address)| (*amount, address.create_script_from_address()))
.collect()
}

View File

@ -9,7 +9,7 @@ use zebra_chain::transparent;
#[serde(deny_unknown_fields, default)]
pub struct Config {
/// The address used for miner payouts.
/// Zebra currently only supports single-signature P2SH transparent addresses.
/// Zebra currently only supports P2SH and P2PKH transparent addresses.
///
/// Zebra sends mining fees and miner rewards to this address in the
/// `getblocktemplate` RPC coinbase transaction.

View File

@ -877,12 +877,20 @@ async fn rpc_getnetworksolps() {
#[cfg(feature = "getblocktemplate-rpcs")]
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getblocktemplate() {
// test getblocktemplate with a miner P2SH address
rpc_getblocktemplate_mining_address(true).await;
// test getblocktemplate with a miner P2PKH address
rpc_getblocktemplate_mining_address(false).await;
}
#[cfg(feature = "getblocktemplate-rpcs")]
async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
use std::panic;
use chrono::{TimeZone, Utc};
use chrono::TimeZone;
use zebra_chain::{
amount::{Amount, NonNegative},
amount::NonNegative,
block::{Hash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION},
chain_tip::mock::MockChainTip,
work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256},
@ -911,10 +919,13 @@ async fn rpc_getblocktemplate() {
let mut mock_sync_status = MockSyncStatus::default();
mock_sync_status.set_is_close_to_tip(true);
let mining_config = get_block_template_rpcs::config::Config {
miner_address: Some(transparent::Address::from_script_hash(Mainnet, [0x7e; 20])),
let miner_address = match use_p2pkh {
false => Some(transparent::Address::from_script_hash(Mainnet, [0x7e; 20])),
true => Some(transparent::Address::from_pub_key_hash(Mainnet, [0x7e; 20])),
};
let mining_config = get_block_template_rpcs::config::Config { miner_address };
// nu5 block height
let fake_tip_height = NetworkUpgrade::Nu5.activation_height(Mainnet).unwrap();
// nu5 block hash
@ -1007,8 +1018,13 @@ async fn rpc_getblocktemplate() {
assert!(get_block_template.coinbase_txn.required);
assert!(!get_block_template.coinbase_txn.data.as_ref().is_empty());
assert_eq!(get_block_template.coinbase_txn.depends.len(), 0);
// TODO: should a coinbase transaction have sigops?
assert_eq!(get_block_template.coinbase_txn.sigops, 0);
if use_p2pkh {
// there is one sig operation if miner address is p2pkh.
assert_eq!(get_block_template.coinbase_txn.sigops, 1);
} else {
// everything in the coinbase is p2sh.
assert_eq!(get_block_template.coinbase_txn.sigops, 0);
}
// Coinbase transaction checks for empty blocks.
assert_eq!(
get_block_template.coinbase_txn.fee,

View File

@ -133,11 +133,6 @@ impl RpcServer {
network.network {network} and miner address network {} must match",
miner_address.network(),
);
assert!(
miner_address.is_script_hash(),
"incorrect miner address config: {miner_address} \
Zebra only supports transparent 'pay to script hash' (P2SH) addresses",
);
}
// Initialize the getblocktemplate rpc method handler