Skip to content

Commit

Permalink
feat: parsing for NakamotoCoinbase tx type
Browse files Browse the repository at this point in the history
  • Loading branch information
zone117x committed Nov 30, 2023
1 parent bcce7de commit 7defe1e
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 2 deletions.
22 changes: 21 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,16 @@ export interface DecodedTxResult {
post_conditions: TxPostCondition[];
/** Hex string */
post_conditions_buffer: string;
payload: TxPayloadTokenTransfer | TxPayloadSmartContract | TxPayloadContractCall | TxPayloadPoisonMicroblock | TxPayloadCoinbase | TxPayloadCoinbaseToAltRecipient | TxPayloadVersionedSmartContract | TxPayloadTenureChange;
payload:
| TxPayloadTokenTransfer
| TxPayloadSmartContract
| TxPayloadContractCall
| TxPayloadPoisonMicroblock
| TxPayloadCoinbase
| TxPayloadCoinbaseToAltRecipient
| TxPayloadVersionedSmartContract
| TxPayloadTenureChange
| TxPayloadNakamotoCoinbase;
}

export enum PostConditionAssetInfoID {
Expand Down Expand Up @@ -188,6 +197,16 @@ export interface TxPayloadCoinbaseToAltRecipient {
recipient: PrincipalStandardData | PrincipalContractData;
}

export interface TxPayloadNakamotoCoinbase {
type_id: TxPayloadTypeID.NakamotoCoinbase;
/** Hex string */
payload_buffer: string;
/** Optional, null if not specified */
recipient: PrincipalStandardData | PrincipalContractData | null;
/** Hex string */
vrf_proof: string;
}

export interface TxPayloadVersionedSmartContract {
type_id: TxPayloadTypeID.VersionedSmartContract;
clarity_version: ClarityVersion;
Expand Down Expand Up @@ -229,6 +248,7 @@ export enum TxPayloadTypeID {
CoinbaseToAltRecipient = 5,
VersionedSmartContract = 6,
TenureChange = 7,
NakamotoCoinbase = 8,
}

export enum PostConditionAuthFlag {
Expand Down
42 changes: 41 additions & 1 deletion src/stacks_tx/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,19 @@ impl TransactionPayload {
let payload = TransactionTenureChange::deserialize(fd)?;
TransactionPayload::TenureChange(payload)
}
x if x == TransactionPayloadID::NakamotoCoinbase as u8 => {
let mut payload_bytes = [0u8; 32];
fd.read_exact(&mut payload_bytes)?;
let payload = CoinbasePayload(payload_bytes);

let principal = PrincipalData::deserialize_optional(fd)?;

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

TransactionPayload::NakamotoCoinbase(payload, principal, VRFProof(vrf_proof))
}
_ => {
return Err(format!(
"Failed to parse transaction -- unknown payload ID {}",
Expand Down Expand Up @@ -531,6 +544,21 @@ impl PrincipalData {
_ => Err("Bad principal prefix".into()),
}
}

pub fn deserialize_optional(fd: &mut Cursor<&[u8]>) -> Result<Option<Self>, DeserializeError> {
let mut header = [0];
fd.read_exact(&mut header)?;
let prefix =
TypePrefix::from_u8(header[0]).ok_or_else(|| "Bad optional PrincipalData prefix")?;
match prefix {
TypePrefix::OptionalNone => Ok(None),
TypePrefix::OptionalSome => {
let principal_data = PrincipalData::deserialize(fd)?;
Ok(Some(principal_data))
}
_ => Err("Bad optional PrincipalData prefix".into()),
}
}
}

impl StandardPrincipalData {
Expand Down Expand Up @@ -663,6 +691,7 @@ pub enum TransactionPayloadID {
CoinbaseToAltRecipient = 5,
VersionedSmartContract = 6,
TenureChange = 7,
NakamotoCoinbase = 8,
}

pub enum TransactionPayload {
Expand All @@ -674,10 +703,13 @@ pub enum TransactionPayload {
CoinbaseToAltRecipient(CoinbasePayload, PrincipalData),
VersionedSmartContract(TransactionSmartContract, ClarityVersion),
TenureChange(TransactionTenureChange),
NakamotoCoinbase(CoinbasePayload, Option<PrincipalData>, VRFProof),
}

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

pub struct VRFProof(pub Vec<u8>);

pub struct TransactionTenureChange {
pub previous_tenure_end: [u8; 32],
pub previous_tenure_blocks: u32,
Expand Down Expand Up @@ -756,7 +788,15 @@ mod tests {

#[test]
fn test_decode_bug() {
let input = b"0x00000000010400982f3ec112a5f5928a5c96a914bd733793b896a5000000000000053000000000000002290000c85889dad0d5b08a997a93a28a7c93eb22c324e5f8992dc93e37865ef4f3e0d65383beefeffc4871a2facbc4b590ddf887c80de6638ed4e2ec0e633d1e130f230301000000000216982f3ec112a5f5928a5c96a914bd733793b896a51861726b6164696b6f2d676f7665726e616e63652d76332d310770726f706f7365000000060616982f3ec112a5f5928a5c96a914bd733793b896a51d61726b6164696b6f2d7374616b652d706f6f6c2d64696b6f2d76312d32010000000000000000000000000000ef8801000000000000000000000000000003f00e00000028414950313020557064617465204c54567320616e64204c69717569646174696f6e20526174696f730e0000003168747470733a2f2f6769746875622e636f6d2f61726b6164696b6f2d64616f2f61726b6164696b6f2f70756c6c2f3439330b000000010c0000000507616464726573730516982f3ec112a5f5928a5c96a914bd733793b896a50863616e2d6275726e040863616e2d6d696e7404046e616d650d0000002b61697031302d61726b6164696b6f2d7570646174652d74766c2d6c69717569646174696f6e2d726174696f0e7175616c69666965642d6e616d650616982f3ec112a5f5928a5c96a914bd733793b896a52b61697031302d61726b6164696b6f2d7570646174652d74766c2d6c69717569646174696f6e2d726174696f";
// 07c15258750a06e6ddae0320f978e5d86973933f1803d5bbd35213b54e75d2310f006402e97fca6444b0dc98f6f9a1013c5554975c7ce1c7954135949e6af4b9c56ed9cbf1a61dc83d054fa9cc699c9918af44a9b9ab2e5ccaf9611b86e963f139c49a6c546a8e94d67bb21cda0aa3b05364960e91d4281e7000000015124b91930cea290260f27dd56093f0dbefc4e6c5fa
// pre-payload byte length: 115
// let input = b"0x00000000010400982f3ec112a5f5928a5c96a914bd733793b896a5000000000000053000000000000002290000c85889dad0d5b08a997a93a28a7c93eb22c324e5f8992dc93e37865ef4f3e0d65383beefeffc4871a2facbc4b590ddf887c80de6638ed4e2ec0e633d1e130f230301000000000216982f3ec112a5f5928a5c96a914bd733793b896a51861726b6164696b6f2d676f7665726e616e63652d76332d310770726f706f7365000000060616982f3ec112a5f5928a5c96a914bd733793b896a51d61726b6164696b6f2d7374616b652d706f6f6c2d64696b6f2d76312d32010000000000000000000000000000ef8801000000000000000000000000000003f00e00000028414950313020557064617465204c54567320616e64204c69717569646174696f6e20526174696f730e0000003168747470733a2f2f6769746875622e636f6d2f61726b6164696b6f2d64616f2f61726b6164696b6f2f70756c6c2f3439330b000000010c0000000507616464726573730516982f3ec112a5f5928a5c96a914bd733793b896a50863616e2d6275726e040863616e2d6d696e7404046e616d650d0000002b61697031302d61726b6164696b6f2d7570646174652d74766c2d6c69717569646174696f6e2d726174696f0e7175616c69666965642d6e616d650616982f3ec112a5f5928a5c96a914bd733793b896a52b61697031302d61726b6164696b6f2d7570646174652d74766c2d6c69717569646174696f6e2d726174696f";

// tx prefix (before payload):
// let input = b"00000000010400982f3ec112a5f5928a5c96a914bd733793b896a5000000000000053000000000000002290000c85889dad0d5b08a997a93a28a7c93eb22c324e5f8992dc93e37865ef4f3e0d65383beefeffc4871a2facbc4b590ddf887c80de6638ed4e2ec0e633d1e130f23030100000000";

let input = b"80800000000400ad0cc5ca0b4571dd435a9da7e16cbc662716dceb00000000000000010000000000000000000015833671ecd7432e6412423273eebf8a78d973beb08f690e58ba548f67ee26584967a5bc24d44f27ecca18e82a9956181e9d9cef7c67f718b33c5f5d0f82643801020000000008010101010101010101010101010101010101010101010101010101010101010109000000506f77e9a15503066b515060aa438ae3f5bc5207339b8e2933bdeae0891362d8e7ca2e5b047153904272d5f030ddcc83333676df6583394b0852a7e411b7c8d4c973f17fb7687601891ad7ca6707aa8408";

let bytes = decode_hex(input).unwrap();
let mut cursor = Cursor::new(bytes.as_ref());
let tx = StacksTransaction::deserialize(&mut cursor);
Expand Down
19 changes: 19 additions & 0 deletions src/stacks_tx/neon_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,25 @@ impl NeonJsSerialize for TransactionPayload {

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

let payload_buffer = cx.string(encode_hex(&buf.0));
obj.set(cx, "payload_buffer", payload_buffer)?;

if let Some(principal) = principal {
let recipient_obj = cx.empty_object();
principal.neon_js_serialize(cx, &recipient_obj, extra_ctx)?;
obj.set(cx, "recipient", recipient_obj)?;
} else {
let recipient_obj = cx.null();
obj.set(cx, "recipient", recipient_obj)?;
}

let vrf_proof_buffer = cx.string(encode_hex(&vrf_proof.0));
obj.set(cx, "vrf_proof", vrf_proof_buffer)?;
}
}
Ok(())
}
Expand Down
41 changes: 41 additions & 0 deletions tests/tx-decode-3.0.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
PostConditionModeID,
TenureChangeCause,
TransactionVersion,
TxPayloadNakamotoCoinbase,
TxPayloadTypeID,
TxPublicKeyEncoding
} from '../index.js';
Expand Down Expand Up @@ -46,3 +47,43 @@ test('stacks3.0 - decode tx - tenure change', () => {
}
});
});

test('stacks3.0 - decode tx - nakamoto coinbase - no alt recipient', () => {
const tenureChangeTx = '80800000000400ad0cc5ca0b4571dd435a9da7e16cbc662716dceb00000000000000010000000000000000000015833671ecd7432e6412423273eebf8a78d973beb08f690e58ba548f67ee26584967a5bc24d44f27ecca18e82a9956181e9d9cef7c67f718b33c5f5d0f82643801020000000008010101010101010101010101010101010101010101010101010101010101010109000000506f77e9a15503066b515060aa438ae3f5bc5207339b8e2933bdeae0891362d8e7ca2e5b047153904272d5f030ddcc83333676df6583394b0852a7e411b7c8d4c973f17fb7687601891ad7ca6707aa8408';
const decoded = decodeTransaction(tenureChangeTx);
expect(decoded).toEqual({
"tx_id": "0xa18614990f3a67b8ab13ec95846aebd409b2ef85017c900840436ac547a537aa",
"version": TransactionVersion.Testnet,
"chain_id": 0x80000000,
"auth": {
"type_id": PostConditionAuthFlag.Standard,
"origin_condition": {
"hash_mode": 0,
"signer": {
"address_version": 26,
"address_hash_bytes": "0xad0cc5ca0b4571dd435a9da7e16cbc662716dceb",
"address": "ST2PGSHEA1D2Q3QA3BAETFRBCQHK2E5PWXECD5E7T"
},
"nonce": "1",
"tx_fee": "0",
"key_encoding": TxPublicKeyEncoding.Compressed,
"signature": "0x0015833671ecd7432e6412423273eebf8a78d973beb08f690e58ba548f67ee26584967a5bc24d44f27ecca18e82a9956181e9d9cef7c67f718b33c5f5d0f826438"
}
},
"anchor_mode": AnchorModeID.OnChainOnly,
"post_condition_mode": PostConditionModeID.Deny,
"post_conditions": [],
"post_conditions_buffer": "0x0200000000",
"payload": {
"type_id": TxPayloadTypeID.NakamotoCoinbase,
"payload_buffer": "0x0101010101010101010101010101010101010101010101010101010101010101",
"recipient": null,
"vrf_proof": "0x6f77e9a15503066b515060aa438ae3f5bc5207339b8e2933bdeae0891362d8e7ca2e5b047153904272d5f030ddcc83333676df6583394b0852a7e411b7c8d4c973f17fb7687601891ad7ca6707aa8408"
}
});

const payload = decoded.payload as TxPayloadNakamotoCoinbase;
const txType: TxPayloadTypeID.NakamotoCoinbase = payload.type_id;
expect(txType).toEqual(TxPayloadTypeID.NakamotoCoinbase);
expect(payload.recipient).toBeNull();
});

0 comments on commit 7defe1e

Please sign in to comment.