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
1722use 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) ]
63163mod 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}
0 commit comments