Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for TenureChange transaction types #14

Merged
merged 5 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface DecodedTxResult {
post_conditions: TxPostCondition[];
/** Hex string */
post_conditions_buffer: string;
payload: TxPayloadTokenTransfer | TxPayloadSmartContract | TxPayloadContractCall | TxPayloadPoisonMicroblock | TxPayloadCoinbase | TxPayloadCoinbaseToAltRecipient | TxPayloadVersionedSmartContract;
payload: TxPayloadTokenTransfer | TxPayloadSmartContract | TxPayloadContractCall | TxPayloadPoisonMicroblock | TxPayloadCoinbase | TxPayloadCoinbaseToAltRecipient | TxPayloadVersionedSmartContract | TxPayloadTenureChange;
}

export enum PostConditionAssetInfoID {
Expand Down Expand Up @@ -195,6 +195,31 @@ export interface TxPayloadVersionedSmartContract {
code_body: string;
}

export interface TxPayloadTenureChange {
type_id: TxPayloadTypeID.TenureChange;
/** (Hex string) Stacks Block hash */
previous_tenure_end: string;
/** The number of blocks produced in the previous tenure */
previous_tenure_blocks: number;
/** Cause of change in mining tenure. Depending on cause, tenure can be ended or extended. */
cause: TenureChangeCause;
/** (Hex string) The ECDSA public key hash of the current tenure */
pubkey_hash: string;
/** (Hex string) A Schnorr signature from at least 70% of the Stackers */
signature: string;
/** (Hex string) A bitmap of which Stackers signed */
signers: string;
}

export enum TenureChangeCause {
/** A valid winning block-commit */
BlockFound = 0,
/** No winning block-commits */
NoBlockFound = 1,
/** A "null miner" won the block-commit */
NullMiner = 2,
}

export enum TxPayloadTypeID {
TokenTransfer = 0,
SmartContract = 1,
Expand All @@ -203,6 +228,7 @@ export enum TxPayloadTypeID {
Coinbase = 4,
CoinbaseToAltRecipient = 5,
VersionedSmartContract = 6,
TenureChange = 7,
}

export enum PostConditionAuthFlag {
Expand Down
81 changes: 76 additions & 5 deletions src/stacks_tx/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,13 +378,18 @@ impl TransactionPayload {
}
x if x == TransactionPayloadID::VersionedSmartContract as u8 => {
let clarity_version_u8 = fd.read_u8()?;
let clarity_version = ClarityVersion::from_u8(clarity_version_u8).ok_or(format!(
"Failed to parse smart contract Clarity version: unknown value {}",
clarity_version_u8
))?;
let clarity_version =
ClarityVersion::from_u8(clarity_version_u8).ok_or(format!(
"Failed to parse smart contract Clarity version: unknown value {}",
clarity_version_u8
))?;
let payload = TransactionSmartContract::deserialize(fd)?;
TransactionPayload::VersionedSmartContract(payload, clarity_version)
}
x if x == TransactionPayloadID::TenureChange as u8 => {
let payload = TransactionTenureChange::deserialize(fd)?;
TransactionPayload::TenureChange(payload)
}
_ => {
return Err(format!(
"Failed to parse transaction -- unknown payload ID {}",
Expand Down Expand Up @@ -428,6 +433,40 @@ impl TransactionSmartContract {
}
}

impl TransactionTenureChange {
pub fn deserialize(fd: &mut Cursor<&[u8]>) -> Result<Self, DeserializeError> {
let mut previous_tenure_end = [0u8; 32];
fd.read_exact(&mut previous_tenure_end)?;

let previous_tenure_blocks = fd.read_u16::<BigEndian>()?;

let cause_u8: u8 = fd.read_u8()?;
let cause = TenureChangeCause::from_u8(cause_u8).ok_or(format!(
"Failed to parse transaction: invalid tenure change cause {}",
cause_u8
))?;

let mut pubkey_hash = [0u8; 20];
fd.read_exact(&mut pubkey_hash)?;

let mut signature = [0u8; 65];
fd.read_exact(&mut signature)?;

let signers_len: u32 = fd.read_u32::<BigEndian>()?;
let mut signers: Vec<u8> = vec![0u8; signers_len as usize];
fd.read_exact(&mut signers)?;

Ok(TransactionTenureChange {
previous_tenure_end,
previous_tenure_blocks,
cause,
pubkey_hash,
signature,
signers,
})
}
}

impl StacksMicroblockHeader {
pub fn deserialize(fd: &mut Cursor<&[u8]>) -> Result<Self, DeserializeError> {
let cursor_pos = fd.position() as usize;
Expand Down Expand Up @@ -622,6 +661,7 @@ pub enum TransactionPayloadID {
Coinbase = 4,
CoinbaseToAltRecipient = 5,
VersionedSmartContract = 6,
TenureChange = 7,
}

pub enum TransactionPayload {
Expand All @@ -631,11 +671,42 @@ pub enum TransactionPayload {
PoisonMicroblock(StacksMicroblockHeader, StacksMicroblockHeader),
Coinbase(CoinbasePayload),
CoinbaseToAltRecipient(CoinbasePayload, PrincipalData),
VersionedSmartContract(TransactionSmartContract, ClarityVersion)
VersionedSmartContract(TransactionSmartContract, ClarityVersion),
TenureChange(TransactionTenureChange),
}

pub struct CoinbasePayload(pub [u8; 32]);

pub struct TransactionTenureChange {
pub previous_tenure_end: [u8; 32],
pub previous_tenure_blocks: u16,
pub cause: TenureChangeCause,
pub pubkey_hash: [u8; 20],
pub signature: [u8; 65],
pub signers: Vec<u8>,
}

#[repr(u8)]
#[derive(PartialEq, Copy, Clone)]
pub enum TenureChangeCause {
BlockFound = 0,
NoBlockFound = 1,
NullMiner = 2,
}

impl TenureChangeCause {
pub fn from_u8(n: u8) -> Option<TenureChangeCause> {
match n {
x if x == TenureChangeCause::BlockFound as u8 => Some(TenureChangeCause::BlockFound),
x if x == TenureChangeCause::NoBlockFound as u8 => {
Some(TenureChangeCause::NoBlockFound)
}
x if x == TenureChangeCause::NullMiner as u8 => Some(TenureChangeCause::NullMiner),
_ => None,
}
}
}

pub struct TransactionSmartContract {
pub name: ClarityName,
pub code_body: StacksString,
Expand Down
37 changes: 36 additions & 1 deletion src/stacks_tx/neon_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use super::deserialize::{
StacksTransaction, StandardPrincipalData, TransactionAuth, TransactionAuthField,
TransactionAuthFieldID, TransactionAuthFlags, TransactionContractCall, TransactionPayload,
TransactionPayloadID, TransactionPublicKeyEncoding, TransactionSmartContract,
TransactionSpendingCondition, TransactionVersion,
TransactionSpendingCondition, TransactionTenureChange, TransactionVersion,
};

struct TxSerializationContext {
Expand Down Expand Up @@ -516,6 +516,12 @@ impl NeonJsSerialize for TransactionPayload {

smart_contract.neon_js_serialize(cx, obj, extra_ctx)?;
}
TransactionPayload::TenureChange(ref tenure_change) => {
let type_id = cx.number(TransactionPayloadID::TenureChange as u8);
obj.set(cx, "type_id", type_id)?;

tenure_change.neon_js_serialize(cx, obj, extra_ctx)?;
}
}
Ok(())
}
Expand Down Expand Up @@ -629,6 +635,35 @@ impl NeonJsSerialize for TransactionSmartContract {
}
}

impl NeonJsSerialize for TransactionTenureChange {
fn neon_js_serialize(
&self,
cx: &mut FunctionContext,
obj: &Handle<JsObject>,
_extra_ctx: &(),
) -> NeonResult<()> {
let previous_tenure_end = cx.string(encode_hex(&self.previous_tenure_end));
obj.set(cx, "previous_tenure_end", previous_tenure_end)?;

let previous_tenure_blocks = cx.number(self.previous_tenure_blocks);
obj.set(cx, "previous_tenure_blocks", previous_tenure_blocks)?;

let cause = cx.number(self.cause as u8);
obj.set(cx, "cause", cause)?;

let pubkey_hash = cx.string(encode_hex(&self.pubkey_hash));
obj.set(cx, "pubkey_hash", pubkey_hash)?;

let signature = cx.string(encode_hex(&self.signature));
obj.set(cx, "signature", signature)?;

let signers = cx.string(encode_hex(&self.signers));
obj.set(cx, "signers", signers)?;
zone117x marked this conversation as resolved.
Show resolved Hide resolved

Ok(())
}
}

impl NeonJsSerialize for StacksMicroblockHeader {
fn neon_js_serialize(
&self,
Expand Down