@@ -30,11 +30,11 @@ import (
3030 "github.com/ava-labs/libevm/rlp"
3131)
3232
33- func TestBodyRLPBackwardsCompatibility (t * testing.T ) {
34- newTx := func (nonce uint64 ) * Transaction { return NewTx (& LegacyTx {Nonce : nonce }) }
35- newHdr := func (hashLow byte ) * Header { return & Header {ParentHash : common.Hash {hashLow }} }
36- newWithdraw := func (idx uint64 ) * Withdrawal { return & Withdrawal {Index : idx } }
33+ func newTx (nonce uint64 ) * Transaction { return NewTx (& LegacyTx {Nonce : nonce }) }
34+ func newHdr (parentHashHigh byte ) * Header { return & Header {ParentHash : common.Hash {parentHashHigh }} }
35+ func newWithdraw (idx uint64 ) * Withdrawal { return & Withdrawal {Index : idx } }
3736
37+ func blockBodyRLPTestInputs () []* Body {
3838 // We build up test-case [Body] instances from the Cartesian product of each
3939 // of these components.
4040 txMatrix := [][]* Transaction {
@@ -61,8 +61,11 @@ func TestBodyRLPBackwardsCompatibility(t *testing.T) {
6161 }
6262 }
6363 }
64+ return bodies
65+ }
6466
65- for _ , body := range bodies {
67+ func TestBodyRLPBackwardsCompatibility (t * testing.T ) {
68+ for _ , body := range blockBodyRLPTestInputs () {
6669 t .Run ("" , func (t * testing.T ) {
6770 t .Cleanup (func () {
6871 if t .Failed () {
@@ -86,8 +89,10 @@ func TestBodyRLPBackwardsCompatibility(t *testing.T) {
8689 t .Run ("Decode" , func (t * testing.T ) {
8790 got := new (Body )
8891 err := rlp .DecodeBytes (wantRLP , got )
89- require .NoErrorf (t , err , "rlp.DecodeBytes(rlp.EncodeToBytes(%T), %T) resulted in %s" ,
90- (* withoutMethods )(body ), got , pretty .Sprint (got ))
92+ require .NoErrorf (
93+ t , err , "rlp.DecodeBytes(rlp.EncodeToBytes(%T), %T) resulted in %s" ,
94+ (* withoutMethods )(body ), got , pretty .Sprint (got ),
95+ )
9196
9297 want := body
9398 // Regular RLP decoding will never leave these non-optional
@@ -112,17 +117,94 @@ func TestBodyRLPBackwardsCompatibility(t *testing.T) {
112117 }
113118}
114119
120+ func TestBlockRLPBackwardsCompatibility (t * testing.T ) {
121+ TestOnlyClearRegisteredExtras ()
122+ t .Cleanup (TestOnlyClearRegisteredExtras )
123+
124+ RegisterExtras [
125+ NOOPHeaderHooks , * NOOPHeaderHooks ,
126+ NOOPBlockBodyHooks , * NOOPBlockBodyHooks , // types under test
127+ struct {},
128+ ]()
129+
130+ // Note that there are also a number of tests in `block_test.go` that ensure
131+ // backwards compatibility as [NOOPBlockBodyHooks] are used by default when
132+ // nothing is registered (the above registration is only for completeness).
133+
134+ for _ , body := range blockBodyRLPTestInputs () {
135+ t .Run ("" , func (t * testing.T ) {
136+ // [Block] doesn't export most of its fields so uses [extblock] as a
137+ // proxy for RLP encoding, which is what we therefore use as the
138+ // backwards-compatible gold standard.
139+ hdr := newHdr (99 )
140+ block := extblock {
141+ Header : hdr ,
142+ Txs : body .Transactions ,
143+ Uncles : body .Uncles ,
144+ Withdrawals : body .Withdrawals ,
145+ }
146+
147+ // We've added [extblock.EncodeRLP] and [extblock.DecodeRLP] for our
148+ // hooks.
149+ type withoutMethods extblock
150+
151+ wantRLP , err := rlp .EncodeToBytes (withoutMethods (block ))
152+ require .NoErrorf (t , err , "rlp.EncodeToBytes([%T with methods stripped])" , block )
153+
154+ // Our input to RLP might not be the canonical RLP output.
155+ var wantBlock extblock
156+ err = rlp .DecodeBytes (wantRLP , (* withoutMethods )(& wantBlock ))
157+ require .NoErrorf (t , err , "rlp.DecodeBytes(..., [%T with methods stripped])" , & wantBlock )
158+
159+ t .Run ("Encode" , func (t * testing.T ) {
160+ b := NewBlockWithHeader (hdr ).WithBody (* body ).WithWithdrawals (body .Withdrawals )
161+ got , err := rlp .EncodeToBytes (b )
162+ require .NoErrorf (t , err , "rlp.EncodeToBytes(%T)" , b )
163+
164+ assert .Equalf (t , wantRLP , got , "expect %T RLP identical to that from %T struct stripped of methods" , got , extblock {})
165+ })
166+
167+ t .Run ("Decode" , func (t * testing.T ) {
168+ var gotBlock Block
169+ err := rlp .DecodeBytes (wantRLP , & gotBlock )
170+ require .NoErrorf (t , err , "rlp.DecodeBytes(..., %T)" , & gotBlock )
171+
172+ got := extblock {
173+ gotBlock .Header (),
174+ gotBlock .Transactions (),
175+ gotBlock .Uncles (),
176+ gotBlock .Withdrawals (),
177+ nil , // unexported libevm hooks
178+ }
179+
180+ opts := cmp.Options {
181+ cmp .Comparer ((* Header ).equalHash ),
182+ cmp .Comparer ((* Transaction ).equalHash ),
183+ cmpopts .IgnoreUnexported (extblock {}),
184+ }
185+ if diff := cmp .Diff (wantBlock , got , opts ); diff != "" {
186+ t .Errorf ("rlp.DecodeBytes([RLP from %T stripped of methods], ...) diff (-want +got):\n %s" , extblock {}, diff )
187+ }
188+ })
189+ })
190+ }
191+ }
192+
115193// cChainBodyExtras carries the same additional fields as the Avalanche C-Chain
116- // (ava-labs/coreth) [Body] and implements [BodyHooks] to achieve equivalent RLP
117- // {en,de}coding.
194+ // (ava-labs/coreth) [Body] and implements [BlockBodyHooks] to achieve
195+ // equivalent RLP {en,de}coding.
196+ //
197+ // It is not intended as a full test of ava-labs/coreth existing functionality,
198+ // which should be implemented when that module consumes libevm, but as proof of
199+ // equivalence of the [rlp.Fields] approach.
118200type cChainBodyExtras struct {
119201 Version uint32
120202 ExtData * []byte
121203}
122204
123- var _ BodyHooks = (* cChainBodyExtras )(nil )
205+ var _ BlockBodyHooks = (* cChainBodyExtras )(nil )
124206
125- func (e * cChainBodyExtras ) RLPFieldsForEncoding (b * Body ) * rlp.Fields {
207+ func (e * cChainBodyExtras ) BodyRLPFieldsForEncoding (b * Body ) * rlp.Fields {
126208 // The Avalanche C-Chain uses all of the geth required fields (but none of
127209 // the optional ones) so there's no need to explicitly list them. This
128210 // pattern might not be ideal for readability but is used here for
@@ -132,13 +214,13 @@ func (e *cChainBodyExtras) RLPFieldsForEncoding(b *Body) *rlp.Fields {
132214 // compatibility so this is safe to do, but only for the required fields.
133215 return & rlp.Fields {
134216 Required : append (
135- NOOPBodyHooks {}.RLPFieldsForEncoding (b ).Required ,
217+ NOOPBlockBodyHooks {}.BodyRLPFieldsForEncoding (b ).Required ,
136218 e .Version , e .ExtData ,
137219 ),
138220 }
139221}
140222
141- func (e * cChainBodyExtras ) RLPFieldPointersForDecoding (b * Body ) * rlp.Fields {
223+ func (e * cChainBodyExtras ) BodyRLPFieldPointersForDecoding (b * Body ) * rlp.Fields {
142224 // An alternative to the pattern used above is to explicitly list all
143225 // fields for better introspection.
144226 return & rlp.Fields {
@@ -151,6 +233,20 @@ func (e *cChainBodyExtras) RLPFieldPointersForDecoding(b *Body) *rlp.Fields {
151233 }
152234}
153235
236+ // See [cChainBodyExtras] intent.
237+
238+ func (e * cChainBodyExtras ) Copy () * cChainBodyExtras {
239+ panic ("unimplemented" )
240+ }
241+
242+ func (e * cChainBodyExtras ) BlockRLPFieldsForEncoding (b * BlockRLPProxy ) * rlp.Fields {
243+ panic ("unimplemented" )
244+ }
245+
246+ func (e * cChainBodyExtras ) BlockRLPFieldPointersForDecoding (b * BlockRLPProxy ) * rlp.Fields {
247+ panic ("unimplemented" )
248+ }
249+
154250func TestBodyRLPCChainCompat (t * testing.T ) {
155251 // The inputs to this test were used to generate the expected RLP with
156252 // ava-labs/coreth. This serves as both an example of how to use [BodyHooks]
0 commit comments