Skip to content

Commit cc0c2d9

Browse files
committed
Implement unit tests
1 parent a77524f commit cc0c2d9

File tree

4 files changed

+253
-2
lines changed

4 files changed

+253
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package system_contract
2+
3+
import (
4+
"context"
5+
"github.com/scroll-tech/go-ethereum"
6+
"github.com/scroll-tech/go-ethereum/accounts"
7+
"github.com/scroll-tech/go-ethereum/common"
8+
"github.com/scroll-tech/go-ethereum/core/types"
9+
"github.com/scroll-tech/go-ethereum/log"
10+
"github.com/scroll-tech/go-ethereum/params"
11+
"github.com/scroll-tech/go-ethereum/rollup/sync_service"
12+
"github.com/scroll-tech/go-ethereum/trie"
13+
"github.com/stretchr/testify/require"
14+
"math/big"
15+
"sync"
16+
"testing"
17+
"time"
18+
)
19+
20+
var _ sync_service.EthClient = &fakeEthClient{}
21+
22+
func TestSystemContract_FetchSigner(t *testing.T) {
23+
// Turn off logging for tests to keep output clean.
24+
log.Root().SetHandler(log.DiscardHandler())
25+
26+
// Expected signer address returned by our fake EthClient.
27+
expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
28+
29+
// Create our fake client to always return expectedSigner.
30+
fakeClient := &fakeEthClient{value: expectedSigner}
31+
32+
config := &params.SystemContractConfig{
33+
SystemContractAddress: common.HexToAddress("0xFAKE"),
34+
// The slot number can be arbitrary – fake client doesn't use it.
35+
Period: 10,
36+
RelaxedPeriod: false,
37+
}
38+
39+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
40+
defer cancel()
41+
42+
// Create a new SystemContract instance.
43+
sys := New(ctx, config, fakeClient)
44+
defer sys.Close()
45+
46+
// Since the SystemContract's Start() routine fetches and updates s.signerAddressL1
47+
// in a separate goroutine, wait a bit for that to complete.
48+
time.Sleep(2 * time.Second)
49+
50+
// Acquire a read lock to safely read the value.
51+
sys.lock.RLock()
52+
actualSigner := sys.signerAddressL1
53+
sys.lock.RUnlock()
54+
55+
// Verify that the fetched signer equals the expectedSigner from our fake client.
56+
require.Equal(t, expectedSigner, actualSigner, "The SystemContract should update signerAddressL1 to the value provided by the client")
57+
}
58+
59+
func TestSystemContract_AuthorizeCheck(t *testing.T) {
60+
// This test verifies that if the local signer does not match the authorized signer,
61+
// then the Seal() function returns an error.
62+
63+
// Set the expected signer.
64+
expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
65+
66+
fakeClient := &fakeEthClient{value: expectedSigner}
67+
config := &params.SystemContractConfig{
68+
SystemContractAddress: common.HexToAddress("0xFAKE"),
69+
Period: 10,
70+
RelaxedPeriod: false,
71+
}
72+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
73+
defer cancel()
74+
75+
sys := New(ctx, config, fakeClient)
76+
defer sys.Close()
77+
78+
// Wait to ensure that the background routine has updated signerAddressL1.
79+
time.Sleep(2 * time.Second)
80+
81+
// Authorize with a different signer than expected.
82+
differentSigner := common.HexToAddress("0xABCDEFabcdefABCDEFabcdefabcdefABCDEFABCD")
83+
sys.Authorize(differentSigner, func(acc accounts.Account, mimeType string, message []byte) ([]byte, error) {
84+
// For testing, return a dummy signature
85+
return []byte("dummy_sig"), nil
86+
})
87+
88+
// Create a dummy block header (with dummy values).
89+
// We only need the block number and extra data length for this test.
90+
header := &types.Header{
91+
Number: big.NewInt(100),
92+
// We use an extra slice with length equal to extraSeal
93+
Extra: make([]byte, extraSeal),
94+
// Set other fields minimally for the test:
95+
Time: uint64(time.Now().Unix() + 10),
96+
GasLimit: 10000000,
97+
Nonce: types.BlockNonce{}, // zero nonce as required
98+
MixDigest: common.Hash{}, // dummy; in a PoA system this should be zero
99+
Difficulty: big.NewInt(1),
100+
}
101+
102+
// Call Seal() and expect an error since local signer != authorized signer.
103+
results := make(chan *types.Block)
104+
stop := make(chan struct{})
105+
err := sys.Seal(nil, (&types.Block{}).WithSeal(header), results, stop)
106+
107+
require.Error(t, err, "Seal should return an error when the local signer is not authorized")
108+
}
109+
110+
// TestSystemContract_SignsAfterUpdate simulates:
111+
// 1. Initially, the SystemContract authorized signer (from StorageAt) is not the signer of the Block.
112+
// 2. Later, after updating the fake client to the correct signer, the background
113+
// poll updates the SystemContract.
114+
// 3. Once updated, if the local signing key is set to match, Seal() should succeed.
115+
func TestSystemContract_SignsAfterUpdate(t *testing.T) {
116+
// Silence logging during tests.
117+
log.Root().SetHandler(log.DiscardHandler())
118+
119+
// Define two addresses: one "wrong" and one "correct".
120+
oldSigner := common.HexToAddress("0x1111111111111111111111111111111111111111")
121+
updatedSigner := common.HexToAddress("0x2222222222222222222222222222222222222222")
122+
123+
// Create a fake client that starts by returning the wrong signer.
124+
fakeClient := &fakeEthClient{
125+
value: oldSigner,
126+
}
127+
128+
config := &params.SystemContractConfig{
129+
SystemContractAddress: common.HexToAddress("0xFAKE"), // Dummy value
130+
Period: 10, // arbitrary non-zero value
131+
RelaxedPeriod: false,
132+
}
133+
134+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
135+
defer cancel()
136+
137+
// Create the SystemContract with our fake client.
138+
sys := New(ctx, config, fakeClient)
139+
defer sys.Close()
140+
141+
// Wait for the background routine to poll at least once.
142+
time.Sleep(2 * time.Second)
143+
144+
// Verify that initially the fetched signer equals oldSigner.
145+
sys.lock.RLock()
146+
initialSigner := sys.signerAddressL1
147+
sys.lock.RUnlock()
148+
require.Equal(t, oldSigner, initialSigner, "Initial signerAddressL1 should be oldSigner")
149+
150+
// Now, simulate an update: change the fake client's returned value to updatedSigner.
151+
fakeClient.mu.Lock()
152+
fakeClient.value = updatedSigner
153+
fakeClient.mu.Unlock()
154+
155+
// Wait enough for the background polling routine to fetch the new value.
156+
time.Sleep(1 + time.Second + defaultSyncInterval)
157+
158+
// Verify that system contract's signerAddressL1 is now updated to updatedSigner.
159+
sys.lock.RLock()
160+
newSigner := sys.signerAddressL1
161+
sys.lock.RUnlock()
162+
require.Equal(t, newSigner, updatedSigner, "SignerAddressL1 should update to updatedSigner after polling")
163+
164+
// Now simulate authorizing with the correct local signer.
165+
sys.Authorize(updatedSigner, func(acc accounts.Account, mimeType string, message []byte) ([]byte, error) {
166+
// For testing, return a dummy signature.
167+
return []byte("dummy_signature"), nil
168+
})
169+
170+
// Create a dummy header for sealing.
171+
header := &types.Header{
172+
Number: big.NewInt(100),
173+
Extra: make([]byte, extraSeal),
174+
Time: uint64(time.Now().Add(10 * time.Second).Unix()),
175+
GasLimit: 10000000,
176+
Nonce: types.BlockNonce{}, // must be zero as per rules
177+
MixDigest: common.Hash{}, // for PoA, typically zero
178+
Difficulty: big.NewInt(1),
179+
}
180+
181+
// Construct a new block from the header using NewBlock constructor.
182+
block := types.NewBlock(header, nil, nil, nil, trie.NewStackTrie(nil))
183+
184+
// Set up channels required for Seal.
185+
results := make(chan *types.Block)
186+
stop := make(chan struct{})
187+
188+
// Call Seal. It should succeed (i.e. return no error) because local signer now equals authorized signer.
189+
err := sys.Seal(nil, block, results, stop)
190+
require.NoError(t, err, "Seal should succeed when the local signer is authorized after update")
191+
192+
// Wait for the result from Seal's goroutine.
193+
select {
194+
case sealedBlock := <-results:
195+
require.NotNil(t, sealedBlock, "Seal should eventually return a sealed block")
196+
// Optionally, you may log or further inspect sealedBlock here.
197+
case <-time.After(15 * time.Second):
198+
t.Fatal("Timed out waiting for Seal to return a sealed block")
199+
}
200+
}
201+
202+
// fakeEthClient implements a minimal version of sync_service.EthClient for testing purposes.
203+
type fakeEthClient struct {
204+
mu sync.Mutex
205+
// value is the fixed value to return from StorageAt.
206+
// We'll assume StorageAt returns a 32-byte value representing an Ethereum address.
207+
value common.Address
208+
}
209+
210+
// BlockNumber returns 0.
211+
func (f *fakeEthClient) BlockNumber(ctx context.Context) (uint64, error) {
212+
return 0, nil
213+
}
214+
215+
// ChainID returns a zero-value chain ID.
216+
func (f *fakeEthClient) ChainID(ctx context.Context) (*big.Int, error) {
217+
return big.NewInt(0), nil
218+
}
219+
220+
// FilterLogs returns an empty slice of logs.
221+
func (f *fakeEthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
222+
return []types.Log{}, nil
223+
}
224+
225+
// HeaderByNumber returns nil.
226+
func (f *fakeEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
227+
return nil, nil
228+
}
229+
230+
// SubscribeFilterLogs returns a nil subscription.
231+
func (f *fakeEthClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
232+
return nil, nil
233+
}
234+
235+
// TransactionByHash returns (nil, false, nil).
236+
func (f *fakeEthClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
237+
return nil, false, nil
238+
}
239+
240+
// BlockByHash returns nil.
241+
func (f *fakeEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
242+
return nil, nil
243+
}
244+
245+
// StorageAt returns the byte representation of f.value.
246+
func (f *fakeEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
247+
f.mu.Lock()
248+
defer f.mu.Unlock()
249+
return f.value.Bytes(), nil
250+
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/edsrzf/mmap-go v1.0.0
2222
github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4
2323
github.com/fatih/color v1.7.0
24-
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
24+
github.com/fjl/memsize v0.0.2
2525
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
2626
github.com/go-stack/stack v1.8.1
2727
github.com/golang/protobuf v1.4.3

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
136136
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
137137
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
138138
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
139+
github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA=
140+
github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
139141
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
140142
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
141143
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=

rollup/rollup_sync_service/l1client_test.go

-1
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,3 @@ func (m *mockEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*typ
7676
func (m *mockEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
7777
return nil, nil
7878
}
79-

0 commit comments

Comments
 (0)