Skip to content

Commit

Permalink
Add integration test for JSON Genesis (Fantom-foundation#339)
Browse files Browse the repository at this point in the history
  • Loading branch information
HerbertJordan authored Nov 26, 2024
1 parent d2c069b commit acda55c
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 28 deletions.
2 changes: 1 addition & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pipeline {

stage('Run tests') {
steps {
sh 'go test ./...'
sh 'go test ./... --timeout 30m'
}
}

Expand Down
1 change: 1 addition & 0 deletions example-genesis.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"Sonic": true
}
},
"blockZeroTime": "2024-11-01T00:00:00+01:00",
"accounts": [
{
"name": "Network initializer",
Expand Down
15 changes: 8 additions & 7 deletions integration/makefakegenesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ func FakeGenesisStoreWithRulesAndStart(num idx.Validator, balance, stake *big.In
// set non-zero code for pre-compiled contracts
builder.SetCode(evmwriter.ContractAddress, []byte{0})

_, genesisStateRoot, err := builder.FinalizeBlockZero(rules, FakeGenesisTime)
if err != nil {
panic(err)
}

builder.SetCurrentEpoch(ier.LlrIdxFullEpochRecord{
LlrFullEpochRecord: ier.LlrFullEpochRecord{
BlockState: iblockproc.BlockState{
Expand All @@ -92,7 +97,7 @@ func FakeGenesisStoreWithRulesAndStart(num idx.Validator, balance, stake *big.In
Time: FakeGenesisTime,
Atropos: hash.Event{},
},
FinalizedStateRoot: hash.Hash{},
FinalizedStateRoot: hash.Hash(genesisStateRoot),
EpochGas: 0,
EpochCheaters: lachesis.Cheaters{},
CheatersWritten: 0,
Expand All @@ -105,7 +110,7 @@ func FakeGenesisStoreWithRulesAndStart(num idx.Validator, balance, stake *big.In
Epoch: epoch - 1,
EpochStart: FakeGenesisTime,
PrevEpochStart: FakeGenesisTime - 1,
EpochStateRoot: hash.Zero,
EpochStateRoot: hash.Hash(genesisStateRoot),
Validators: pos.NewBuilder().Build(),
ValidatorStates: make([]iblockproc.ValidatorEpochState, 0),
ValidatorProfiles: make(map[idx.ValidatorID]drivertype.Validator),
Expand All @@ -120,13 +125,9 @@ func FakeGenesisStoreWithRulesAndStart(num idx.Validator, balance, stake *big.In
owner = validators[0].Address
}

if err := builder.FinalizeBlockZero(rules, FakeGenesisTime); err != nil {
panic(err)
}

blockProc := makegenesis.DefaultBlockProc()
genesisTxs := GetGenesisTxs(epoch-2, validators, builder.TotalSupply(), delegations, owner)
err := builder.ExecuteGenesisTxs(blockProc, genesisTxs)
err = builder.ExecuteGenesisTxs(blockProc, genesisTxs)
if err != nil {
panic(err)
}
Expand Down
39 changes: 27 additions & 12 deletions integration/makefakegenesis/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"os"
"time"

"github.com/Fantom-foundation/go-opera/integration/makegenesis"
"github.com/Fantom-foundation/go-opera/inter"
"github.com/Fantom-foundation/go-opera/inter/drivertype"
"github.com/Fantom-foundation/go-opera/inter/iblockproc"
"github.com/Fantom-foundation/go-opera/inter/ier"
Expand All @@ -18,14 +23,13 @@ import (
"github.com/Fantom-foundation/lachesis-base/lachesis"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"math/big"
"os"
)

type GenesisJson struct {
Rules opera.Rules
Accounts []Account `json:",omitempty"`
Txs []Transaction `json:",omitempty"`
Rules opera.Rules
BlockZeroTime time.Time
Accounts []Account `json:",omitempty"`
Txs []Transaction `json:",omitempty"`
}

type Account struct {
Expand Down Expand Up @@ -57,6 +61,10 @@ func LoadGenesisJson(filename string) (*GenesisJson, error) {
}

func ApplyGenesisJson(json *GenesisJson) (*genesisstore.Store, error) {
if json.BlockZeroTime.IsZero() {
return nil, fmt.Errorf("block zero time must be set")
}

builder := makegenesis.NewGenesisBuilder()

fmt.Printf("Building genesis file - rules: %+v\n", json.Rules)
Expand All @@ -75,15 +83,22 @@ func ApplyGenesisJson(json *GenesisJson) (*genesisstore.Store, error) {
}
}

genesisTime := inter.Timestamp(json.BlockZeroTime.UnixNano())

_, genesisStateRoot, err := builder.FinalizeBlockZero(json.Rules, genesisTime)
if err != nil {
return nil, err
}

builder.SetCurrentEpoch(ier.LlrIdxFullEpochRecord{
LlrFullEpochRecord: ier.LlrFullEpochRecord{
BlockState: iblockproc.BlockState{
LastBlock: iblockproc.BlockCtx{
Idx: 0,
Time: FakeGenesisTime,
Time: genesisTime,
Atropos: hash.Event{},
},
FinalizedStateRoot: hash.Hash{},
FinalizedStateRoot: hash.Hash(genesisStateRoot),
EpochGas: 0,
EpochCheaters: lachesis.Cheaters{},
CheatersWritten: 0,
Expand All @@ -94,9 +109,9 @@ func ApplyGenesisJson(json *GenesisJson) (*genesisstore.Store, error) {
},
EpochState: iblockproc.EpochState{
Epoch: 1,
EpochStart: FakeGenesisTime,
PrevEpochStart: FakeGenesisTime - 1,
EpochStateRoot: hash.Zero,
EpochStart: genesisTime + 1,
PrevEpochStart: genesisTime,
EpochStateRoot: hash.Hash(genesisStateRoot),
Validators: pos.NewBuilder().Build(),
ValidatorStates: make([]iblockproc.ValidatorEpochState, 0),
ValidatorProfiles: make(map[idx.ValidatorID]drivertype.Validator),
Expand All @@ -112,7 +127,7 @@ func ApplyGenesisJson(json *GenesisJson) (*genesisstore.Store, error) {
for _, tx := range json.Txs {
genesisTxs = append(genesisTxs, buildTx(tx.Data, tx.To))
}
err := builder.ExecuteGenesisTxs(blockProc, genesisTxs)
err = builder.ExecuteGenesisTxs(blockProc, genesisTxs)
if err != nil {
return nil, fmt.Errorf("failed to execute json genesis txs; %v", err)
}
Expand All @@ -130,7 +145,7 @@ func (c *VariableLenCode) MarshalJSON() ([]byte, error) {
out := make([]byte, hex.EncodedLen(len(*c))+4)
out[0], out[1], out[2] = '"', '0', 'x'
hex.Encode(out[3:], *c)
out[len(*c)-1] = '"'
out[len(out)-1] = '"'
return out, nil
}

Expand Down
10 changes: 7 additions & 3 deletions integration/makegenesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,13 @@ func (d dummyHeaderReturner) GetHeader(_ common.Hash, position uint64) *evmcore.
func (b *GenesisBuilder) FinalizeBlockZero(
rules opera.Rules,
genesisTime inter.Timestamp,
) error {
) (
blockHash common.Hash,
stateRoot common.Hash,
err error,
) {
if len(b.blocks) > 0 {
return errors.New("block zero already finalized")
return common.Hash{}, common.Hash{}, errors.New("block zero already finalized")
}

// construct state root of initial state
Expand All @@ -186,7 +190,7 @@ func (b *GenesisBuilder) FinalizeBlockZero(
Idx: 0,
})

return nil
return common.Hash(b.blocks[0].BlockHash), genesisStateRoot, nil
}

func (b *GenesisBuilder) ExecuteGenesisTxs(blockProc BlockProc, genesisTxs types.Transactions) error {
Expand Down
18 changes: 15 additions & 3 deletions tests/block_header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,25 @@ import (
"github.com/stretchr/testify/require"
)

func TestBlockHeader_SatisfiesInvariants(t *testing.T) {
const numBlocks = 10
func TestBlockHeader_FakeGenesis_SatisfiesInvariants(t *testing.T) {
require := require.New(t)

net, err := StartIntegrationTestNet(t.TempDir())
require.NoError(err)
defer net.Stop()
testBlockHeadersOnNetwork(t, net)
}

func TestBlockHeader_JsonGenesis_SatisfiesInvariants(t *testing.T) {
require := require.New(t)
net, err := StartIntegrationTestNetFromJsonGenesis(t.TempDir())
require.NoError(err)
defer net.Stop()
testBlockHeadersOnNetwork(t, net)
}

func testBlockHeadersOnNetwork(t *testing.T, net *IntegrationTestNet) {
const numBlocks = 10
require := require.New(t)

// Produce a few blocks on the network.
for range numBlocks {
Expand Down
106 changes: 104 additions & 2 deletions tests/integration_test_net.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tests

import (
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
Expand All @@ -13,6 +14,15 @@ import (
sonicd "github.com/Fantom-foundation/go-opera/cmd/sonicd/app"
sonictool "github.com/Fantom-foundation/go-opera/cmd/sonictool/app"
"github.com/Fantom-foundation/go-opera/evmcore"
"github.com/Fantom-foundation/go-opera/integration/makefakegenesis"
"github.com/Fantom-foundation/go-opera/opera"
"github.com/Fantom-foundation/go-opera/opera/contracts/driver"
"github.com/Fantom-foundation/go-opera/opera/contracts/driver/drivercall"
"github.com/Fantom-foundation/go-opera/opera/contracts/driverauth"
"github.com/Fantom-foundation/go-opera/opera/contracts/evmwriter"
"github.com/Fantom-foundation/go-opera/opera/contracts/netinit"
"github.com/Fantom-foundation/go-opera/opera/contracts/sfc"
futils "github.com/Fantom-foundation/go-opera/utils"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -55,7 +65,96 @@ type IntegrationTestNet struct {
// is intended to facilitate debugging of client code in the context of a running
// node.
func StartIntegrationTestNet(directory string) (*IntegrationTestNet, error) {
return startIntegrationTestNet(directory, []string{
"genesis", "fake", "1",
})
}

func StartIntegrationTestNetFromJsonGenesis(directory string) (*IntegrationTestNet, error) {
jsonGenesis := makefakegenesis.GenesisJson{
Rules: opera.FakeNetRules(),
BlockZeroTime: time.Now(),
}

// Create infrastructure contracts.
jsonGenesis.Accounts = []makefakegenesis.Account{
{
Name: "NetworkInitializer",
Address: netinit.ContractAddress,
Code: netinit.GetContractBin(),
},
{
Name: "NodeDriver",
Address: driver.ContractAddress,
Code: driver.GetContractBin(),
},
{
Name: "NodeDriverAuth",
Address: driverauth.ContractAddress,
Code: driverauth.GetContractBin(),
},
{
Name: "SFC",
Address: sfc.ContractAddress,
Code: sfc.GetContractBin(),
},
{
Name: "ContractAddress",
Address: evmwriter.ContractAddress,
Code: []byte{0},
},
}

// Create the validator account and provide some tokens.
totalSupply := futils.ToFtm(1000000000)
validators := makefakegenesis.GetFakeValidators(1)
for _, validator := range validators {
jsonGenesis.Accounts = append(jsonGenesis.Accounts, makefakegenesis.Account{
Address: validator.Address,
Balance: totalSupply,
})
}

var delegations []drivercall.Delegation
for _, val := range validators {
delegations = append(delegations, drivercall.Delegation{
Address: val.Address,
ValidatorID: val.ID,
Stake: futils.ToFtm(5000000),
LockedStake: new(big.Int),
LockupFromEpoch: 0,
LockupEndTime: 0,
LockupDuration: 0,
EarlyUnlockPenalty: new(big.Int),
Rewards: new(big.Int),
})
}

// Create the genesis transactions.
genesisTxs := makefakegenesis.GetGenesisTxs(0, validators, totalSupply, delegations, validators[0].Address)
for _, tx := range genesisTxs {
jsonGenesis.Txs = append(jsonGenesis.Txs, makefakegenesis.Transaction{
To: *tx.To(),
Data: tx.Data(),
})
}

encoded, err := json.MarshalIndent(jsonGenesis, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to encode genesis json: %w", err)
}

jsonFile := filepath.Join(directory, "genesis.json")
err = os.WriteFile(jsonFile, encoded, 0644)
if err != nil {
return nil, fmt.Errorf("failed to write genesis.json file: %w", err)
}
return startIntegrationTestNet(directory, []string{
"genesis", "json", "--experimental", jsonFile,
})
}

func startIntegrationTestNet(directory string, args []string) (*IntegrationTestNet, error) {
// start the fakenet sonic node
result := &IntegrationTestNet{
directory: directory,
Expand All @@ -65,8 +164,11 @@ func StartIntegrationTestNet(directory string) (*IntegrationTestNet, error) {
// initialize the data directory for the single node on the test network
// equivalent to running `sonictool --datadir <dataDir> genesis fake 1`
originalArgs := os.Args
os.Args = []string{"sonictool", "--datadir", result.stateDir(), "genesis", "fake", "1"}
sonictool.Run()
os.Args = append([]string{"sonictool", "--datadir", result.stateDir()}, args...)
if err := sonictool.Run(); err != nil {
os.Args = originalArgs
return nil, fmt.Errorf("failed to initialize the test network: %w", err)
}
os.Args = originalArgs

if err := result.start(); err != nil {
Expand Down

0 comments on commit acda55c

Please sign in to comment.