go-ethereum/consensus/system_contract/system_contract_test.go
Morty 365b82953d
feat: add system config consensus to deprecate clique (#1102)
* feat: add system config consensus to deprecate poa

* fix: check extra field lens

* Finish implementation with one signer only

* Implement unit tests

* Only check signature if block not requested

* Remove Extra from rlp encoding (and hashing)

* Implement UpgradableEngine as middleware for Clique/SystemContract

* Use isEuclid, add json tag to Requested, make go idiomatic

* Changes post integration test

* Remove comment

* Address comments

* Merge

* New field BlockSignature (not used in hashing/JSON)

* Enforce .Extra to be an empty slice

* replace header.Requested for header.IsNewBlock

* mark new block as IsNewBlock

* Make new RLP optional fields always default for legacy/reth compatibility

* Placing new fields as last non-zero rlp:optional values used by Scroll

* Penalize nodes that send non-zero Euclid V2 header field values

* Bring back .Requested to downloader instead of .IsNewBlock

* Replace IsNewBlock for Requested

* Address comments

* merge

* prevent timing issues in tests

* fix ci

* chore: auto version bump [bot]

* Update consensus/system_contract/consensus.go

Co-authored-by: Morty <70688412+yiweichi@users.noreply.github.com>

* chore: auto version bump [bot]

* Update consensus/system_contract/consensus.go

Co-authored-by: Jonas Theis <4181434+jonastheis@users.noreply.github.com>

* Remove whitespaces and merge version number

* chore: auto version bump [bot]

* validate that the read address from L1 is not empty and improved error handling when fetching address

* Fix indentation issue

* Fix test

* goimports fix

* goimports

---------

Co-authored-by: Alejandro Ranchal-Pedrosa <a.ranchalpedrosa@gmail.com>
Co-authored-by: jonastheis <4181434+jonastheis@users.noreply.github.com>
Co-authored-by: jonastheis <jonastheis@users.noreply.github.com>
Co-authored-by: ranchalp <ranchalp@users.noreply.github.com>
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
2025-02-28 15:53:32 +01:00

223 lines
7.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package system_contract
import (
"context"
"math/big"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"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"
)
var _ sync_service.EthClient = &fakeEthClient{}
func TestSystemContract_FetchSigner(t *testing.T) {
log.Root().SetHandler(log.DiscardHandler())
expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
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()
sys := New(ctx, config, fakeClient)
defer sys.Close()
require.NoError(t, sys.fetchAddressFromL1())
actualSigner := sys.currentSignerAddressL1()
// 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.
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()
require.NoError(t, sys.fetchAddressFromL1())
// 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.
// We only need the block number and blocksignature data length for this test.
header := &types.Header{
Number: big.NewInt(100),
// We use an extra slice with length equal to extraSeal
BlockSignature: make([]byte, extraSeal),
}
// 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()
sys := New(ctx, config, fakeClient)
defer sys.Close()
require.NoError(t, sys.fetchAddressFromL1())
// Verify that initially the fetched signer equals oldSigner.
initialSigner := sys.currentSignerAddressL1()
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()
// fetch new value from L1 (simulating a background poll)
require.NoError(t, sys.fetchAddressFromL1())
// Verify that system contract's signerAddressL1 is now updated to updatedSigner.
newSigner := sys.currentSignerAddressL1()
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),
BlockSignature: make([]byte, extraSeal),
}
// Construct a new block from the header using NewBlock constructor.
block := types.NewBlock(header, nil, nil, nil, trie.NewStackTrie(nil))
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
}