Skip to content

Commit

Permalink
Implement unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ranchalp committed Jan 17, 2025
1 parent a77524f commit cc0c2d9
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 2 deletions.
250 changes: 250 additions & 0 deletions consensus/system_contract/system_contract_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package system_contract

import (
"context"
"github.com/scroll-tech/go-ethereum"
"github.com/scroll-tech/go-ethereum/accounts"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/params"
"github.com/scroll-tech/go-ethereum/rollup/sync_service"
"github.com/scroll-tech/go-ethereum/trie"
"github.com/stretchr/testify/require"
"math/big"
"sync"
"testing"
"time"
)

var _ sync_service.EthClient = &fakeEthClient{}

func TestSystemContract_FetchSigner(t *testing.T) {
// Turn off logging for tests to keep output clean.
log.Root().SetHandler(log.DiscardHandler())

// Expected signer address returned by our fake EthClient.
expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")

// Create our fake client to always return expectedSigner.
fakeClient := &fakeEthClient{value: expectedSigner}

config := &params.SystemContractConfig{
SystemContractAddress: common.HexToAddress("0xFAKE"),
// The slot number can be arbitrary – fake client doesn't use it.
Period: 10,
RelaxedPeriod: false,
}

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// Create a new SystemContract instance.
sys := New(ctx, config, fakeClient)
defer sys.Close()

// Since the SystemContract's Start() routine fetches and updates s.signerAddressL1
// in a separate goroutine, wait a bit for that to complete.
time.Sleep(2 * time.Second)

// Acquire a read lock to safely read the value.
sys.lock.RLock()
actualSigner := sys.signerAddressL1
sys.lock.RUnlock()

// Verify that the fetched signer equals the expectedSigner from our fake client.
require.Equal(t, expectedSigner, actualSigner, "The SystemContract should update signerAddressL1 to the value provided by the client")
}

func TestSystemContract_AuthorizeCheck(t *testing.T) {
// This test verifies that if the local signer does not match the authorized signer,
// then the Seal() function returns an error.

// Set the expected signer.
expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")

fakeClient := &fakeEthClient{value: expectedSigner}
config := &params.SystemContractConfig{
SystemContractAddress: common.HexToAddress("0xFAKE"),
Period: 10,
RelaxedPeriod: false,
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

sys := New(ctx, config, fakeClient)
defer sys.Close()

// Wait to ensure that the background routine has updated signerAddressL1.
time.Sleep(2 * time.Second)

// Authorize with a different signer than expected.
differentSigner := common.HexToAddress("0xABCDEFabcdefABCDEFabcdefabcdefABCDEFABCD")
sys.Authorize(differentSigner, func(acc accounts.Account, mimeType string, message []byte) ([]byte, error) {
// For testing, return a dummy signature
return []byte("dummy_sig"), nil
})

// Create a dummy block header (with dummy values).
// We only need the block number and extra data length for this test.
header := &types.Header{
Number: big.NewInt(100),
// We use an extra slice with length equal to extraSeal
Extra: make([]byte, extraSeal),
// Set other fields minimally for the test:
Time: uint64(time.Now().Unix() + 10),
GasLimit: 10000000,
Nonce: types.BlockNonce{}, // zero nonce as required
MixDigest: common.Hash{}, // dummy; in a PoA system this should be zero
Difficulty: big.NewInt(1),
}

// Call Seal() and expect an error since local signer != authorized signer.
results := make(chan *types.Block)
stop := make(chan struct{})
err := sys.Seal(nil, (&types.Block{}).WithSeal(header), results, stop)

require.Error(t, err, "Seal should return an error when the local signer is not authorized")
}

// TestSystemContract_SignsAfterUpdate simulates:
// 1. Initially, the SystemContract authorized signer (from StorageAt) is not the signer of the Block.
// 2. Later, after updating the fake client to the correct signer, the background
// poll updates the SystemContract.
// 3. Once updated, if the local signing key is set to match, Seal() should succeed.
func TestSystemContract_SignsAfterUpdate(t *testing.T) {
// Silence logging during tests.
log.Root().SetHandler(log.DiscardHandler())

// Define two addresses: one "wrong" and one "correct".
oldSigner := common.HexToAddress("0x1111111111111111111111111111111111111111")
updatedSigner := common.HexToAddress("0x2222222222222222222222222222222222222222")

// Create a fake client that starts by returning the wrong signer.
fakeClient := &fakeEthClient{
value: oldSigner,
}

config := &params.SystemContractConfig{
SystemContractAddress: common.HexToAddress("0xFAKE"), // Dummy value
Period: 10, // arbitrary non-zero value
RelaxedPeriod: false,
}

ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()

// Create the SystemContract with our fake client.
sys := New(ctx, config, fakeClient)
defer sys.Close()

// Wait for the background routine to poll at least once.
time.Sleep(2 * time.Second)

// Verify that initially the fetched signer equals oldSigner.
sys.lock.RLock()
initialSigner := sys.signerAddressL1
sys.lock.RUnlock()
require.Equal(t, oldSigner, initialSigner, "Initial signerAddressL1 should be oldSigner")

// Now, simulate an update: change the fake client's returned value to updatedSigner.
fakeClient.mu.Lock()
fakeClient.value = updatedSigner
fakeClient.mu.Unlock()

// Wait enough for the background polling routine to fetch the new value.
time.Sleep(1 + time.Second + defaultSyncInterval)

// Verify that system contract's signerAddressL1 is now updated to updatedSigner.
sys.lock.RLock()
newSigner := sys.signerAddressL1
sys.lock.RUnlock()
require.Equal(t, newSigner, updatedSigner, "SignerAddressL1 should update to updatedSigner after polling")

// Now simulate authorizing with the correct local signer.
sys.Authorize(updatedSigner, func(acc accounts.Account, mimeType string, message []byte) ([]byte, error) {
// For testing, return a dummy signature.
return []byte("dummy_signature"), nil
})

// Create a dummy header for sealing.
header := &types.Header{
Number: big.NewInt(100),
Extra: make([]byte, extraSeal),
Time: uint64(time.Now().Add(10 * time.Second).Unix()),
GasLimit: 10000000,
Nonce: types.BlockNonce{}, // must be zero as per rules
MixDigest: common.Hash{}, // for PoA, typically zero
Difficulty: big.NewInt(1),
}

// Construct a new block from the header using NewBlock constructor.
block := types.NewBlock(header, nil, nil, nil, trie.NewStackTrie(nil))

// Set up channels required for Seal.
results := make(chan *types.Block)
stop := make(chan struct{})

// Call Seal. It should succeed (i.e. return no error) because local signer now equals authorized signer.
err := sys.Seal(nil, block, results, stop)
require.NoError(t, err, "Seal should succeed when the local signer is authorized after update")

// Wait for the result from Seal's goroutine.
select {
case sealedBlock := <-results:
require.NotNil(t, sealedBlock, "Seal should eventually return a sealed block")
// Optionally, you may log or further inspect sealedBlock here.
case <-time.After(15 * time.Second):
t.Fatal("Timed out waiting for Seal to return a sealed block")
}
}

// fakeEthClient implements a minimal version of sync_service.EthClient for testing purposes.
type fakeEthClient struct {
mu sync.Mutex
// value is the fixed value to return from StorageAt.
// We'll assume StorageAt returns a 32-byte value representing an Ethereum address.
value common.Address
}

// BlockNumber returns 0.
func (f *fakeEthClient) BlockNumber(ctx context.Context) (uint64, error) {
return 0, nil
}

// ChainID returns a zero-value chain ID.
func (f *fakeEthClient) ChainID(ctx context.Context) (*big.Int, error) {
return big.NewInt(0), nil
}

// FilterLogs returns an empty slice of logs.
func (f *fakeEthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
return []types.Log{}, nil
}

// HeaderByNumber returns nil.
func (f *fakeEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
return nil, nil
}

// SubscribeFilterLogs returns a nil subscription.
func (f *fakeEthClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
return nil, nil
}

// TransactionByHash returns (nil, false, nil).
func (f *fakeEthClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
return nil, false, nil
}

// BlockByHash returns nil.
func (f *fakeEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
return nil, nil
}

// StorageAt returns the byte representation of f.value.
func (f *fakeEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
f.mu.Lock()
defer f.mu.Unlock()
return f.value.Bytes(), nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ require (
github.com/edsrzf/mmap-go v1.0.0
github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4
github.com/fatih/color v1.7.0
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
github.com/fjl/memsize v0.0.2
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
github.com/go-stack/stack v1.8.1
github.com/golang/protobuf v1.4.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA=
github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
Expand Down
1 change: 0 additions & 1 deletion rollup/rollup_sync_service/l1client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,3 @@ func (m *mockEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*typ
func (m *mockEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
return nil, nil
}

0 comments on commit cc0c2d9

Please sign in to comment.