Skip to content

re-introduce dedicated function to deserialize outputs from NewTemplate message#1777

Merged
plebhash merged 1 commit into
stratum-mining:mainfrom
plebhash:2025-06-30-deserialize-template-outputs
Jul 8, 2025
Merged

re-introduce dedicated function to deserialize outputs from NewTemplate message#1777
plebhash merged 1 commit into
stratum-mining:mainfrom
plebhash:2025-06-30-deserialize-template-outputs

Conversation

@plebhash

Copy link
Copy Markdown
Member

as the outcome of Sjors/bitcoin#92


to be merged after #1775


in #1772 we realized that deserialize_outputs was introducing issues into the code

that's because this function can only deserialize outputs that were serialized into an opaque blob, not following Bitcoin consensus rules (setting a VarInt prefix as the size of the array, followed by the serialized bytes... or in other words, rust-bitcoin serializing a Vec<TxOut>)

this function was being used to deserialize outputs from SetCustomMiningJob, which are serialized under Bitcoin consensus rules (Vec<TxOut>)

NewTemplate.coinbase_tx_outputs however follows a different rule, where NewTemplate.coinbase_tx_outputs_count carries the size of the array, and NewTemplate.coinbase_tx_outputs simply has the raw bytes (no VarInt prefix to indicate the size of the array)

in other words, NewTemplate.coinbase_tx_outputs MUST NOT be deserialized via Vec::<TxOut>::consensus_decode, and it needs a dedicated function to do so

but SetCustomMiningJob.coinbase_tx_outpus MUST be deserialized via Vec::<TxOut>::consensus_decode (and this is what was causing problems on #1771)

@plebhash plebhash force-pushed the 2025-06-30-deserialize-template-outputs branch 2 times, most recently from 7ff316c to bedb09a Compare July 4, 2025 13:59

@GitGab19 GitGab19 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK

@plebhash plebhash force-pushed the 2025-06-30-deserialize-template-outputs branch from bedb09a to 9633259 Compare July 8, 2025 14:12
@plebhash plebhash merged commit bd03176 into stratum-mining:main Jul 8, 2025
11 checks passed
@plebhash plebhash deleted the 2025-06-30-deserialize-template-outputs branch July 8, 2025 14:35
Comment on lines +70 to +99
let mut deserialized_outputs: Vec<TxOut> = vec![];

// The serialized outputs are in Bitcoin consensus format
// We need to parse them one by one, keeping track of cursor position
let mut cursor = 0;
let mut txouts = &serialized_outputs[cursor..];

// Iteratively decode each TxOut until we can't decode any more
while let Ok(out) = TxOut::consensus_decode(&mut txouts) {
// Calculate the size of this TxOut based on its script_pubkey length
// 8 bytes for value + variable bytes for script_pubkey length
// For small scripts (0-252 bytes): 1 byte length prefix
// For medium scripts (253-1000000 bytes): 3 byte length prefix (1 marker + 2 byte
// length)
let len = match out.script_pubkey.len() {
a @ 0..=252 => 8 + 1 + a, // 8 (value) + 1 (compact size) + script_len
a @ 253..=1000000 => 8 + 3 + a, // 8 (value) + 3 (compact size) + script_len
_ => break, // Unreasonably large script, likely an error
};

// Move the cursor forward by the size of this TxOut
cursor += len;
deserialized_outputs.push(out);
}

if deserialized_outputs.len() != coinbase_tx_outputs_count as usize {
return Err(Error::FailedToDeserializeCoinbaseOutputs);
}

Ok(deserialized_outputs)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, can we use a Cursor directly here, since consensus_decode already accepts any type that implements Read? This way, we wouldn't need to manually update the buffer position, as consensus_decode would advance the cursor on its own.

Something like this:

pub fn deserialize_template_outputs_1(
    serialized_outputs: Vec<u8>,
    coinbase_tx_outputs_count: u32,
) -> Result<Vec<TxOut>, Error> {
    let mut cursor = Cursor::new(serialized_outputs);
    let mut deserialized_outputs = Vec::new();

    while let Ok(out) = TxOut::consensus_decode(&mut cursor) {
        deserialized_outputs.push(out);
    }

    Ok(deserialized_outputs)
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, feel free to send a PR with this improvement

but please make sure this is not left out, because that's the reason coinbase_tx_outputs_count is used as an input parameter for this function:

    if deserialized_outputs.len() != coinbase_tx_outputs_count as usize {
        return Err(Error::FailedToDeserializeCoinbaseOutputs);
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants