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:
parent
5b054c968e
commit
12ff32f445
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
mod address;
|
mod address;
|
||||||
mod keys;
|
mod keys;
|
||||||
|
mod opcodes;
|
||||||
mod script;
|
mod script;
|
||||||
mod serialize;
|
mod serialize;
|
||||||
mod utxo;
|
mod utxo;
|
||||||
|
|
|
@ -10,7 +10,7 @@ use sha2::Sha256;
|
||||||
use crate::{
|
use crate::{
|
||||||
parameters::Network,
|
parameters::Network,
|
||||||
serialization::{SerializationError, ZcashDeserialize, ZcashSerialize},
|
serialization::{SerializationError, ZcashDeserialize, ZcashSerialize},
|
||||||
transparent::Script,
|
transparent::{opcodes::OpCode, Script},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -254,6 +254,33 @@ impl Address {
|
||||||
payload[..].copy_from_slice(&ripe_hash[..]);
|
payload[..].copy_from_slice(&ripe_hash[..]);
|
||||||
payload
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ use zebra_chain::{
|
||||||
amount::{Amount, Error, NonNegative},
|
amount::{Amount, Error, NonNegative},
|
||||||
block::Height,
|
block::Height,
|
||||||
parameters::{Network, NetworkUpgrade::*},
|
parameters::{Network, NetworkUpgrade::*},
|
||||||
serialization::ZcashSerialize,
|
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
transparent::{Address, Output, Script},
|
transparent::{Address, Output, Script},
|
||||||
};
|
};
|
||||||
|
@ -141,25 +140,14 @@ pub fn new_coinbase_script(address: Address) -> Script {
|
||||||
assert!(
|
assert!(
|
||||||
address.is_script_hash(),
|
address.is_script_hash(),
|
||||||
"incorrect coinbase script address: {address} \
|
"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
|
// > 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.
|
// > of the form OP_HASH160 fs.RedeemScriptHash(height) OP_EQUAL as the scriptPubKey.
|
||||||
//
|
//
|
||||||
// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
|
// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
|
||||||
let mut script_bytes = Vec::new();
|
address.create_script_from_address()
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of outputs in `Transaction`, which have a script address equal to `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()
|
.cloned()
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Script opcodes needed to compare the `lock_script` with the funding stream reward address.
|
|
||||||
pub enum OpCode {
|
|
||||||
Equal = 0x87,
|
|
||||||
Hash160 = 0xa9,
|
|
||||||
Push20Bytes = 0x14,
|
|
||||||
}
|
|
||||||
|
|
|
@ -21,10 +21,12 @@ use zebra_chain::{
|
||||||
transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
|
transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
|
||||||
transparent,
|
transparent,
|
||||||
};
|
};
|
||||||
|
|
||||||
use zebra_consensus::{
|
use zebra_consensus::{
|
||||||
funding_stream_address, funding_stream_values, miner_subsidy, new_coinbase_script,
|
funding_stream_address, funding_stream_values, miner_subsidy, VerifyChainError,
|
||||||
VerifyChainError, MAX_BLOCK_SIGOPS,
|
MAX_BLOCK_SIGOPS,
|
||||||
};
|
};
|
||||||
|
|
||||||
use zebra_node_services::mempool;
|
use zebra_node_services::mempool;
|
||||||
|
|
||||||
use zebra_state::{ReadRequest, ReadResponse};
|
use zebra_state::{ReadRequest, ReadResponse};
|
||||||
|
@ -659,7 +661,7 @@ pub fn standard_coinbase_outputs(
|
||||||
|
|
||||||
coinbase_outputs
|
coinbase_outputs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(amount, address)| (*amount, new_coinbase_script(*address)))
|
.map(|(amount, address)| (*amount, address.create_script_from_address()))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use zebra_chain::transparent;
|
||||||
#[serde(deny_unknown_fields, default)]
|
#[serde(deny_unknown_fields, default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// The address used for miner payouts.
|
/// 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
|
/// Zebra sends mining fees and miner rewards to this address in the
|
||||||
/// `getblocktemplate` RPC coinbase transaction.
|
/// `getblocktemplate` RPC coinbase transaction.
|
||||||
|
|
|
@ -877,12 +877,20 @@ async fn rpc_getnetworksolps() {
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn rpc_getblocktemplate() {
|
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 std::panic;
|
||||||
|
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::TimeZone;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::{Amount, NonNegative},
|
amount::NonNegative,
|
||||||
block::{Hash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION},
|
block::{Hash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION},
|
||||||
chain_tip::mock::MockChainTip,
|
chain_tip::mock::MockChainTip,
|
||||||
work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256},
|
work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256},
|
||||||
|
@ -911,10 +919,13 @@ async fn rpc_getblocktemplate() {
|
||||||
let mut mock_sync_status = MockSyncStatus::default();
|
let mut mock_sync_status = MockSyncStatus::default();
|
||||||
mock_sync_status.set_is_close_to_tip(true);
|
mock_sync_status.set_is_close_to_tip(true);
|
||||||
|
|
||||||
let mining_config = get_block_template_rpcs::config::Config {
|
let miner_address = match use_p2pkh {
|
||||||
miner_address: Some(transparent::Address::from_script_hash(Mainnet, [0x7e; 20])),
|
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
|
// nu5 block height
|
||||||
let fake_tip_height = NetworkUpgrade::Nu5.activation_height(Mainnet).unwrap();
|
let fake_tip_height = NetworkUpgrade::Nu5.activation_height(Mainnet).unwrap();
|
||||||
// nu5 block hash
|
// nu5 block hash
|
||||||
|
@ -1007,8 +1018,13 @@ async fn rpc_getblocktemplate() {
|
||||||
assert!(get_block_template.coinbase_txn.required);
|
assert!(get_block_template.coinbase_txn.required);
|
||||||
assert!(!get_block_template.coinbase_txn.data.as_ref().is_empty());
|
assert!(!get_block_template.coinbase_txn.data.as_ref().is_empty());
|
||||||
assert_eq!(get_block_template.coinbase_txn.depends.len(), 0);
|
assert_eq!(get_block_template.coinbase_txn.depends.len(), 0);
|
||||||
// TODO: should a coinbase transaction have sigops?
|
if use_p2pkh {
|
||||||
assert_eq!(get_block_template.coinbase_txn.sigops, 0);
|
// 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.
|
// Coinbase transaction checks for empty blocks.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_block_template.coinbase_txn.fee,
|
get_block_template.coinbase_txn.fee,
|
||||||
|
|
|
@ -133,11 +133,6 @@ impl RpcServer {
|
||||||
network.network {network} and miner address network {} must match",
|
network.network {network} and miner address network {} must match",
|
||||||
miner_address.network(),
|
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
|
// Initialize the getblocktemplate rpc method handler
|
||||||
|
|
Loading…
Reference in New Issue