Skip to content

Commit

Permalink
multi: adjust OP_RETURN message construction, add deserialization of …
Browse files Browse the repository at this point in the history
…sidechain proposal data (#52)

* types: add deserialization of M1 sidechain proposals

* multi: construct/parse coinbase messages with valid OP_RETURNs

Instead of creating `ScriptBuf`s directly from raw bytes (with OP_RETURN
at the start), we do it via `PushBytesBuf::try_from(Vec<u8>)` +
`ScriptBuf::new_op_return`. This adds the correct pushdata instruction
to the script.

Coinbase message parsing logic is also adjusted accordingly.

* validator: adjust M1 message construction

1. Fix the version at `0`
2. Encode the title length, as specified by the V0 serialization scheme.

* server: add deserialized sidechain proposals to GetSidechainProposalsResponse

* server: update cusf_sidechain_proto

* server: update cusf_sidechain_proto
  • Loading branch information
torkelrogstad authored Oct 22, 2024
1 parent e93930e commit febc0cb
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 114 deletions.
2 changes: 1 addition & 1 deletion cusf_sidechain_proto
100 changes: 54 additions & 46 deletions src/messages.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use bitcoin::opcodes::{
all::{OP_NOP5, OP_PUSHBYTES_1, OP_RETURN},
OP_TRUE,
};
use bitcoin::script::{Instruction, Instructions};
use bitcoin::{hashes::Hash, Amount, Opcode, Script, ScriptBuf, Transaction, TxOut};
use bitcoin::{
opcodes::{
all::{OP_NOP5, OP_PUSHBYTES_1, OP_RETURN},
OP_TRUE,
},
script::PushBytesBuf,
};
use byteorder::{ByteOrder, LittleEndian};
use nom::branch::alt;
use nom::bytes::complete::{tag, take};
Expand All @@ -25,12 +29,15 @@ impl CoinbaseBuilder {
CoinbaseBuilder { messages: vec![] }
}

pub fn build(self) -> Vec<TxOut> {
pub fn build(self) -> Result<Vec<TxOut>, bitcoin::script::PushBytesError> {
self.messages
.into_iter()
.map(|message| TxOut {
value: Amount::from_sat(0),
script_pubkey: message.into(),
.map(|message| {
let script_pubkey = message.try_into()?;
Ok(TxOut {
value: Amount::from_sat(0),
script_pubkey,
})
})
.collect()
}
Expand Down Expand Up @@ -153,8 +160,27 @@ impl M4AckBundles {
}

pub fn parse_coinbase_script(script: &Script) -> IResult<&[u8], CoinbaseMessage> {
let script = script.as_bytes();
let (input, _) = tag(&[OP_RETURN.to_u8()])(script)?;
let mut instructions = script.instructions();

// Return a nom parsing failure. Would be nice to include a message
// about what went wrong? Is that possible?
fn instruction_failure(instructions: Instructions) -> nom::Err<nom::error::Error<&[u8]>> {
nom::Err::Failure(nom::error::Error {
input: instructions.as_script().as_bytes(),
code: nom::error::ErrorKind::Fail,
})
}

if instructions.next() != Some(Ok(Instruction::Op(OP_RETURN))) {
return Err(instruction_failure(instructions));
}

let Some(Ok(Instruction::PushBytes(data))) = instructions.next() else {
return Err(instruction_failure(instructions));
};

let input = data.as_bytes();

let (input, message_tag) = alt((
tag(M1_PROPOSE_SIDECHAIN_TAG),
tag(M2_ACK_SIDECHAIN_TAG),
Expand Down Expand Up @@ -288,50 +314,37 @@ pub fn parse_m8_bmm_request(input: &[u8]) -> IResult<&[u8], M8BmmRequest> {
Ok((input, message))
}

impl From<CoinbaseMessage> for ScriptBuf {
fn from(val: CoinbaseMessage) -> Self {
impl TryFrom<CoinbaseMessage> for ScriptBuf {
type Error = bitcoin::script::PushBytesError;

fn try_from(val: CoinbaseMessage) -> Result<Self, Self::Error> {
match val {
CoinbaseMessage::M1ProposeSidechain {
sidechain_number,
data,
} => {
let message = [
&[OP_RETURN.to_u8()],
M1_PROPOSE_SIDECHAIN_TAG,
&[sidechain_number],
&data,
]
.concat();
let message = [M1_PROPOSE_SIDECHAIN_TAG, &[sidechain_number], &data].concat();

ScriptBuf::from_bytes(message)
let data = PushBytesBuf::try_from(message)?;
Ok(ScriptBuf::new_op_return(&data))
}
CoinbaseMessage::M2AckSidechain {
sidechain_number,
data_hash,
} => {
let message = [
&[OP_RETURN.to_u8()],
M2_ACK_SIDECHAIN_TAG,
&[sidechain_number],
&data_hash,
]
.concat();
let message = [M2_ACK_SIDECHAIN_TAG, &[sidechain_number], &data_hash].concat();

ScriptBuf::from_bytes(message)
let data = PushBytesBuf::try_from(message)?;
Ok(ScriptBuf::new_op_return(&data))
}
CoinbaseMessage::M3ProposeBundle {
sidechain_number,
bundle_txid,
} => {
let message = [
&[OP_RETURN.to_u8()],
M3_PROPOSE_BUNDLE_TAG,
&[sidechain_number],
&bundle_txid,
]
.concat();
let message = [M3_PROPOSE_BUNDLE_TAG, &[sidechain_number], &bundle_txid].concat();

ScriptBuf::from_bytes(message)
let data = PushBytesBuf::try_from(message)?;
Ok(ScriptBuf::new_op_return(&data))
}
CoinbaseMessage::M4AckBundles(m4_ack_bundles) => {
let upvotes = match &m4_ack_bundles {
Expand All @@ -342,29 +355,24 @@ impl From<CoinbaseMessage> for ScriptBuf {
.collect(),
_ => vec![],
};
let message = [
&[OP_RETURN.to_u8()],
M4_ACK_BUNDLES_TAG,
&[m4_ack_bundles.tag()],
&upvotes,
]
.concat();
let message = [M4_ACK_BUNDLES_TAG, &[m4_ack_bundles.tag()], &upvotes].concat();

ScriptBuf::from_bytes(message)
let data = PushBytesBuf::try_from(message)?;
Ok(ScriptBuf::new_op_return(&data))
}
CoinbaseMessage::M7BmmAccept {
sidechain_number,
sidechain_block_hash,
} => {
let message = [
&[OP_RETURN.to_u8()],
M7_BMM_ACCEPT_TAG,
&[sidechain_number],
&sidechain_block_hash,
]
.concat();

ScriptBuf::from_bytes(message)
let data = PushBytesBuf::try_from(message)?;
Ok(ScriptBuf::new_op_return(&data))
}
}
}
Expand Down
56 changes: 30 additions & 26 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use std::sync::Arc;

use crate::proto::mainchain::{
sidechain_declaration, SidechainDeclaration, SidechainDeclarationV0,
};
use crate::proto::mainchain::{
wallet_service_server::WalletService, BroadcastWithdrawalBundleRequest,
BroadcastWithdrawalBundleResponse, CreateBmmCriticalDataTransactionRequest,
Expand Down Expand Up @@ -31,7 +34,7 @@ use async_broadcast::RecvError;
use bdk::bitcoin::hashes::Hash as _;
use bitcoin::{self, absolute::Height, Amount, BlockHash, Transaction, TxOut};
use futures::{stream::BoxStream, StreamExt, TryStreamExt as _};
use miette::Result;
use miette::{IntoDiagnostic, Result};
use tonic::{Request, Response, Status};

use crate::types;
Expand Down Expand Up @@ -255,19 +258,22 @@ impl ValidatorService for Validator {
}
let output = messages
.into_iter()
.map(|message| TxOut {
value: Amount::ZERO,
script_pubkey: message.into(),
.map(|message| {
Ok(TxOut {
value: Amount::ZERO,
script_pubkey: message.try_into().into_diagnostic()?,
})
})
.collect();
let transasction = Transaction {
.collect::<Result<Vec<_>>>()
.map_err(|err| tonic::Status::internal(err.to_string()))?;
let transaction = Transaction {
output,
input: vec![],
lock_time: bitcoin::absolute::LockTime::Blocks(Height::ZERO),
version: bitcoin::transaction::Version::TWO,
};
let response = GetCoinbasePsbtResponse {
psbt: Some(ConsensusHex::encode(&transasction)),
psbt: Some(ConsensusHex::encode(&transaction)),
};
Ok(Response::new(response))
}
Expand Down Expand Up @@ -341,26 +347,24 @@ impl ValidatorService for Validator {
.map_err(|err| err.into_status())?;
let sidechain_proposals = sidechain_proposals
.into_iter()
.map(
|(
data_hash,
types::SidechainProposal {
sidechain_number,
data,
vote_count,
proposal_height,
},
)| {
SidechainProposal {
sidechain_number: u8::from(sidechain_number) as u32,
data: Some(data),
data_hash: Some(ConsensusHex::encode(&data_hash)),
vote_count: vote_count as u32,
proposal_height,
proposal_age: 0,
.map(|(data_hash, proposal)| SidechainProposal {
sidechain_number: u8::from(proposal.sidechain_number) as u32,
data: Some(proposal.data.clone()),
declaration: proposal.try_deserialize().ok().map(|(_, deserialized)| {
SidechainDeclaration {
version: Some(sidechain_declaration::Version::V0(SidechainDeclarationV0 {
title: Some(deserialized.title),
description: Some(deserialized.description),
hash_id_1: Some(ConsensusHex::encode(&deserialized.hash_id_1)),
hash_id_2: Some(ConsensusHex::encode(&deserialized.hash_id_2.to_vec())),
})),
}
},
)
}),
data_hash: Some(ConsensusHex::encode(&data_hash)),
vote_count: proposal.vote_count as u32,
proposal_height: proposal.proposal_height,
proposal_age: 0,
})
.collect();
let response = GetSidechainProposalsResponse {
sidechain_proposals,
Expand Down
Loading

0 comments on commit febc0cb

Please sign in to comment.