Skip to content

Commit cfd710d

Browse files
committed
feat: add a transaction api
Signed-off-by: Eric Torreborre <etorreborre@yahoo.com>
1 parent 1de248b commit cfd710d

File tree

14 files changed

+661
-13
lines changed

14 files changed

+661
-13
lines changed

Cargo.lock

Lines changed: 78 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ acto = { version = "0.8.0", features = ["tokio"] }
2323
anyhow = "1.0.100"
2424
async-compression = { version = "0.4.32", features = ["tokio", "gzip"] }
2525
async-trait = "0.1.83"
26+
axum = "0.8"
2627
assert-json-diff = "2.0.2"
2728
bech32 = "0.11.0"
2829
binrw = "0.15.0"

crates/amaru-kernel/src/cardano/block.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ pub struct Block {
5050
}
5151

5252
impl Block {
53+
pub const CBOR_FIELD_COUNT: u64 = 5;
54+
5355
/// Get the size in bytes of the serialised block.
5456
pub fn body_len(&self) -> u64 {
5557
self.original_body_size
@@ -103,7 +105,7 @@ impl IntoIterator for Block {
103105
impl<'b, C> cbor::Decode<'b, C> for Block {
104106
fn decode(d: &mut cbor::Decoder<'b>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
105107
cbor::heterogeneous_array(d, |d, assert_len| {
106-
assert_len(5)?;
108+
assert_len(Block::CBOR_FIELD_COUNT)?;
107109

108110
let (header, header_bytes) = cbor::tee(d, |d| d.decode_with(ctx))?;
109111

crates/amaru-kernel/src/cardano/network_block.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ impl NetworkBlock {
5151
RawBlock::from(to_cbor(self).as_slice())
5252
}
5353

54+
pub fn encoded_block(&self) -> &[u8] {
55+
&self.encoded_block
56+
}
57+
5458
/// Decode the inner block from its raw CBOR representation.
5559
pub fn decode_block(&self) -> Result<Block, minicbor::decode::Error> {
5660
minicbor::decode(&self.encoded_block)

crates/amaru-kernel/src/cardano/raw_block.rs

Lines changed: 121 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use std::{fmt, ops::Deref, sync::Arc};
15+
use std::{
16+
collections::{BTreeMap, BTreeSet},
17+
fmt,
18+
ops::Deref,
19+
sync::Arc,
20+
};
1621

1722
use minicbor::decode;
1823

19-
use crate::{Block, cardano::network_block::NetworkBlock, utils::debug_bytes};
24+
use crate::{Block, cardano::network_block::NetworkBlock, cbor, utils::debug_bytes};
2025

2126
/// Cheaply cloneable block bytes
2227
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
@@ -57,16 +62,111 @@ impl RawBlock {
5762
let network_block: NetworkBlock = minicbor::decode(&self.0)?;
5863
network_block.decode_block()
5964
}
65+
66+
/// Return an iterator over standalone CBOR-encoded transactions extracted from the block.
67+
pub fn transactions(&self) -> Result<RawBlockTransactions, decode::Error> {
68+
let network_block = NetworkBlock::try_from(self.clone())?;
69+
let mut decoder = cbor::Decoder::new(network_block.encoded_block());
70+
71+
let len = decoder.array()?;
72+
if len != Some(Block::CBOR_FIELD_COUNT) {
73+
return Err(decode::Error::message(format!(
74+
"invalid Block array length. Expected {}, got {len:?}",
75+
Block::CBOR_FIELD_COUNT
76+
)));
77+
}
78+
79+
decoder.skip()?;
80+
let bodies = cbor::collect_array_item_bytes(&mut decoder)?;
81+
let witnesses = cbor::collect_array_item_bytes(&mut decoder)?;
82+
let auxiliary_data = cbor::collect_map_value_bytes(&mut decoder, |d| d.u32())?;
83+
let invalid_transactions: Option<BTreeSet<u32>> = decoder.decode()?;
84+
85+
if bodies.len() != witnesses.len() {
86+
return Err(decode::Error::message(format!(
87+
"inconsistent block: {} transaction bodies but {} witness sets",
88+
bodies.len(),
89+
witnesses.len()
90+
)));
91+
}
92+
93+
Ok(RawBlockTransactions {
94+
bodies: bodies.into_iter(),
95+
witnesses: witnesses.into_iter(),
96+
auxiliary_data,
97+
invalid_transactions,
98+
index: 0,
99+
})
100+
}
101+
}
102+
103+
/// This struct supports the iteration over serialized transactions contained in a block
104+
pub struct RawBlockTransactions {
105+
bodies: std::vec::IntoIter<Vec<u8>>,
106+
witnesses: std::vec::IntoIter<Vec<u8>>,
107+
auxiliary_data: BTreeMap<u32, Vec<u8>>,
108+
invalid_transactions: Option<BTreeSet<u32>>,
109+
index: u32,
110+
}
111+
112+
impl Iterator for RawBlockTransactions {
113+
type Item = Vec<u8>;
114+
115+
fn next(&mut self) -> Option<Self::Item> {
116+
let body = self.bodies.next()?;
117+
let witnesses = self.witnesses.next()?;
118+
let tx_index = self.index;
119+
self.index += 1;
120+
121+
Some(Self::serialize_transaction_from_components(
122+
&body,
123+
&witnesses,
124+
!self.invalid_transactions.as_ref().is_some_and(|set| set.contains(&tx_index)),
125+
self.auxiliary_data.get(&tx_index).map(Vec::as_slice),
126+
))
127+
}
128+
129+
fn size_hint(&self) -> (usize, Option<usize>) {
130+
self.bodies.size_hint()
131+
}
132+
}
133+
134+
impl ExactSizeIterator for RawBlockTransactions {
135+
fn len(&self) -> usize {
136+
self.bodies.len()
137+
}
138+
}
139+
140+
impl RawBlockTransactions {
141+
fn serialize_transaction_from_components(
142+
body: &[u8],
143+
witnesses: &[u8],
144+
is_expected_valid: bool,
145+
auxiliary_data: Option<&[u8]>,
146+
) -> Vec<u8> {
147+
let mut tx_bytes = Vec::with_capacity(body.len() + witnesses.len() + auxiliary_data.map_or(1, |x| x.len()) + 3);
148+
149+
tx_bytes.push(0x84);
150+
tx_bytes.extend_from_slice(body);
151+
tx_bytes.extend_from_slice(witnesses);
152+
tx_bytes.push(if is_expected_valid { 0xf5 } else { 0xf4 });
153+
match auxiliary_data {
154+
Some(aux) => tx_bytes.extend_from_slice(aux),
155+
None => tx_bytes.push(0xf6),
156+
}
157+
158+
tx_bytes
159+
}
60160
}
61161

62162
#[cfg(test)]
63163
mod tests {
64164
use amaru_minicbor_extra::{from_cbor, to_cbor};
65165

66166
use crate::{
67-
BlockHeader, TESTNET_ERA_HISTORY,
167+
Block, BlockHeader, TESTNET_ERA_HISTORY, Transaction,
68168
cardano::network_block::{NetworkBlock, make_block_with_header},
69-
make_header,
169+
include_cbor, make_header,
70170
};
71171

72172
#[test]
@@ -90,4 +190,21 @@ mod tests {
90190
let decoded_block = raw_block.decode().expect("raw block should decode");
91191
assert_eq!(decoded_block, block);
92192
}
193+
194+
#[test]
195+
fn iterate_over_serialized_transactions() {
196+
let (_era, block): (crate::EraName, Block) = include_cbor!(
197+
"cbor.decode/block/b9bef52dd8dedf992837d20c18399a284d80fde0ae9435f2a33649aaee7c5698/sample.cbor"
198+
);
199+
let raw_block = NetworkBlock::new(&crate::PREPROD_ERA_HISTORY, &block).expect("make network block").raw_block();
200+
let mut txs = raw_block.transactions().expect("extract transactions");
201+
202+
assert_eq!(txs.len(), 1);
203+
204+
let tx_bytes = txs.next().expect("the first transaction");
205+
let tx: Transaction = minicbor::decode(&tx_bytes).expect("decode extracted transaction");
206+
207+
assert!(!tx_bytes.is_empty());
208+
assert_eq!(tx.body.id().to_string(), "3741dc1d8f14f938904388bb257a05b361ac1e9f447db11032bb2577ff0cbd38");
209+
}
93210
}

crates/amaru-kernel/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,10 @@ pub use cardano::{
177177

178178
pub mod cbor {
179179
pub use amaru_minicbor_extra::{
180-
TAG_MAP_259, TAG_SET_258, allow_tag, check_tagged_array_length, decode_break, expect_tag, from_cbor,
181-
from_cbor_no_leftovers, from_cbor_no_leftovers_with, heterogeneous_array, heterogeneous_map, lazy,
182-
missing_field, tee, to_cbor, unexpected_field,
180+
TAG_MAP_259, TAG_SET_258, allow_tag, check_tagged_array_length, collect_array_item_bytes,
181+
collect_map_value_bytes, decode_break, expect_tag, from_cbor, from_cbor_no_leftovers,
182+
from_cbor_no_leftovers_with, heterogeneous_array, heterogeneous_map, lazy, missing_field, tee, to_cbor,
183+
unexpected_field,
183184
};
184185
pub use minicbor::{
185186
CborLen, Decode, Decoder, Encode, Encoder, bytes,

0 commit comments

Comments
 (0)