Skip to content

Commit

Permalink
Add a "governance" system contract that can perform rollbacks.
Browse files Browse the repository at this point in the history
  • Loading branch information
avm committed Mar 5, 2025
1 parent 31c4b71 commit 7e2da06
Show file tree
Hide file tree
Showing 22 changed files with 300 additions and 50 deletions.
40 changes: 24 additions & 16 deletions nil/cmd/nild/devnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
package main

import (
"crypto/ecdsa"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -131,25 +130,13 @@ func (devnet devnet) generateZeroState(nShards uint32, servers []server) (*execu
})
}
}

mainKeyPath := devnet.spec.NildCredentialsDir + "/keys.yaml"
mainPrivateKey, err := execution.LoadMainKeys(mainKeyPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
mainPublicKey, err := ensurePublicKey(mainKeyPath)
if err != nil {
return nil, err
}
var mainPublicKey []byte
if err != nil {
var mainPrivateKey *ecdsa.PrivateKey

mainPrivateKey, mainPublicKey, err = nilcrypto.GenerateKeyPair()
if err != nil {
return nil, err
}
if err := execution.DumpMainKeys(mainKeyPath, mainPrivateKey); err != nil {
return nil, err
}
} else {
mainPublicKey = crypto.CompressPubkey(&mainPrivateKey.PublicKey)
}
zeroState, err := execution.CreateDefaultZeroStateConfig(mainPublicKey)
if err != nil {
return nil, err
Expand All @@ -159,6 +146,27 @@ func (devnet devnet) generateZeroState(nShards uint32, servers []server) (*execu
return zeroState, nil
}

func ensurePublicKey(keyPath string) ([]byte, error) {
privateKey, err := execution.LoadMainKeys(keyPath)
if err == nil {
publicKey := crypto.CompressPubkey(&privateKey.PublicKey)
return publicKey, nil
}
if !errors.Is(err, os.ErrNotExist) {
// if the file exists but is invalid, return the error
return nil, err
}

privateKey, publicKey, err := nilcrypto.GenerateKeyPair()
if err != nil {
return nil, err
}
if err := execution.DumpMainKeys(keyPath, privateKey); err != nil {
return nil, err
}
return publicKey, nil
}

func genDevnet(cmd *cobra.Command, args []string) error {
baseDir, err := cmd.Flags().GetString("basedir")
if err != nil {
Expand Down
40 changes: 40 additions & 0 deletions nil/contracts/solidity/system/Governance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import "../lib/Nil.sol";

contract Governance is NilBase {
address public constant SELF_ADDRESS =
address(0x777777777777777777777777777777777777);

function rollback(
uint32 version,
uint32 counter,
uint32 patchLevel,
uint64 mainBlockId,
uint32 /*replayDepth*/,
uint32 /*searchDepth*/
) external onlyExternal {
Nil.rollback(
version,
counter,
patchLevel,
mainBlockId /*,
replayDepth,
searchDepth */
);
}

bytes pubkey;

constructor(bytes memory _pubkey) payable {
pubkey = _pubkey;
}

function verifyExternal(
uint256 hash,
bytes memory authData
) external view returns (bool) {
return Nil.validateSignature(pubkey, hash, authData);
}
}
29 changes: 29 additions & 0 deletions nil/internal/collate/proposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const (
defaultMaxGasInBlock = types.DefaultMaxGasInBlock
maxTxnsFromPool = 1000
defaultMaxForwardTransactionsInBlock = 200

validatorPatchLevel = 1
)

type proposer struct {
Expand Down Expand Up @@ -77,6 +79,10 @@ func (p *proposer) GenerateProposal(ctx context.Context, txFabric db.DB) (*execu
return nil, fmt.Errorf("failed to fetch previous block: %w", err)
}

if block.PatchLevel > validatorPatchLevel {
return nil, fmt.Errorf("block patch level %d is higher than supported %d", block.PatchLevel, validatorPatchLevel)
}

configAccessor, err := config.NewConfigAccessorFromBlockWithTx(tx, block, p.params.ShardId)
if err != nil {
return nil, fmt.Errorf("failed to create config accessor: %w", err)
Expand Down Expand Up @@ -110,6 +116,11 @@ func (p *proposer) GenerateProposal(ctx context.Context, txFabric db.DB) (*execu
return nil, fmt.Errorf("failed to handle transactions from pool: %w", err)
}

if rollback := p.executionState.GetRollback(); rollback != nil {
// TODO: verify mainBlockId, actually perform rollback
p.proposal.PatchLevel = rollback.PatchLevel
}

if len(p.proposal.InternalTxnRefs) == 0 && len(p.proposal.ExternalTxns) == 0 && len(p.proposal.ForwardTxnRefs) == 0 {
p.logger.Trace().Msg("No transactions collected")
} else {
Expand Down Expand Up @@ -195,6 +206,24 @@ func (p *proposer) handleL1Attributes(tx db.RoTx) error {
return nil
}

func CreateRollbackCalldata(params *execution.RollbackParams) ([]byte, error) {
abi, err := contracts.GetAbi(contracts.NameGovernance)
if err != nil {
return nil, fmt.Errorf("failed to get Governance ABI: %w", err)
}
calldata, err := abi.Pack("rollback",
params.Version,
params.Counter,
params.PatchLevel,
params.MainBlockId,
params.ReplayDepth,
params.SearchDepth)
if err != nil {
return nil, fmt.Errorf("failed to pack rollback calldata: %w", err)
}
return calldata, nil
}

func CreateL1BlockUpdateTransaction(header *l1types.Header) (*types.Transaction, error) {
abi, err := contracts.GetAbi(contracts.NameL1BlockInfo)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion nil/internal/config/generate.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package config

//go:generate go run github.com/NilFoundation/fastssz/sszgen --path params.go -include ../types/address.go,../types/uint256.go,../types/transaction.go,../../common/hash.go,../../common/length.go --objs ListValidators,ParamValidators,ValidatorInfo,ParamGasPrice,ParamFees,ParamL1BlockInfo,WorkaroundToImportTypes
//go:generate go run github.com/NilFoundation/fastssz/sszgen --path params.go -include ../types/address.go,../types/uint256.go,../types/transaction.go,../../common/hash.go,../../common/length.go --objs ListValidators,ParamValidators,ValidatorInfo,ParamGasPrice,ParamFees,ParamL1BlockInfo,ParamSudoKey,WorkaroundToImportTypes
20 changes: 20 additions & 0 deletions nil/internal/config/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ const (
NameValidators = "curr_validators"
NameGasPrice = "gas_price"
NameL1Block = "l1block"
NameSudoKey = "sudo_key"
)

var ParamsList = []IConfigParam{
new(ParamValidators),
new(ParamGasPrice),
new(ParamL1BlockInfo),
new(ParamSudoKey),
}

type Pubkey [ValidatorPubkeySize]byte
Expand Down Expand Up @@ -111,6 +113,20 @@ func (p *ParamL1BlockInfo) Accessor() *ParamAccessor {
return CreateAccessor[ParamL1BlockInfo]()
}

type ParamSudoKey struct {
PublicKey []byte `json:"pubKey" yaml:"pubKey" ssz-max:"65"`
}

var _ IConfigParam = new(ParamSudoKey)

func (p *ParamSudoKey) Name() string {
return NameSudoKey
}

func (p *ParamSudoKey) Accessor() *ParamAccessor {
return CreateAccessor[ParamSudoKey]()
}

func CreateAccessor[T any, paramPtr IConfigParamPointer[T]]() *ParamAccessor {
return &ParamAccessor{
func(c ConfigAccessor) (any, error) {
Expand Down Expand Up @@ -281,6 +297,10 @@ func SetParamGasPrice(c ConfigAccessor, params *ParamGasPrice) error {
return setParamImpl(c, params)
}

func SetParamSudoKey(c ConfigAccessor, params *ParamSudoKey) error {
return setParamImpl(c, params)
}

func GetParamL1Block(c ConfigAccessor) (*ParamL1BlockInfo, error) {
return getParamImpl[ParamL1BlockInfo](c)
}
Expand Down
1 change: 1 addition & 0 deletions nil/internal/contracts/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
NameNilBounceable = "NilBounceable"
NameNilConfigAbi = "NilConfigAbi"
NameL1BlockInfo = "system/L1BlockInfo"
NameGovernance = "system/Governance"
)

var (
Expand Down
3 changes: 2 additions & 1 deletion nil/internal/crypto/secp256k1.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package crypto

import (
"github.com/NilFoundation/nil/nil/common"
"github.com/holiman/uint256"
)

Expand All @@ -14,7 +15,7 @@ func TransactionSignatureIsValid(v byte, r, s *uint256.Int) bool {
}

func TransactionSignatureIsValidBytes(sign []byte) bool {
if len(sign) != 65 {
if len(sign) != common.SignatureSize {
return false
}

Expand Down
1 change: 1 addition & 0 deletions nil/internal/execution/block_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ func (g *BlockGenerator) prepareExecutionState(proposal *Proposal, gasPrices []t
}

g.executionState.MainChainHash = proposal.MainChainHash
g.executionState.PatchLevel = proposal.PatchLevel

for _, txn := range proposal.InternalTxns {
if err := g.handleTxn(txn); err != nil {
Expand Down
2 changes: 2 additions & 0 deletions nil/internal/execution/proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type InternalTxnReference struct {
type Proposal struct {
PrevBlockId types.BlockNumber `json:"prevBlockId"`
PrevBlockHash common.Hash `json:"prevBlockHash"`
PatchLevel uint32 `json:"patchLevel"`
CollatorState types.CollatorState `json:"collatorState"`
MainChainHash common.Hash `json:"mainChainHash"`
ShardHashes []common.Hash `json:"shardHashes"`
Expand All @@ -45,6 +46,7 @@ type Proposal struct {
type ProposalSSZ struct {
PrevBlockId types.BlockNumber
PrevBlockHash common.Hash
PatchLevel uint32
CollatorState types.CollatorState
MainChainHash common.Hash
ShardHashes []common.Hash `ssz-max:"4096"`
Expand Down
26 changes: 26 additions & 0 deletions nil/internal/execution/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ func init() {
}
}

type RollbackParams struct {
Version uint32
Counter uint32
PatchLevel uint32
MainBlockId uint64
ReplayDepth uint32
SearchDepth uint32
}

type ExecutionState struct {
tx db.RwTx
ContractTree *ContractTrie
Expand All @@ -58,6 +67,7 @@ type ExecutionState struct {
ChildChainBlocks map[types.ShardId]common.Hash
GasPrice types.Value // Current gas price including priority fee
BaseFee types.Value
PatchLevel uint32

InTransactionHash common.Hash
Logs map[common.Hash][]*types.Log
Expand Down Expand Up @@ -109,6 +119,8 @@ type ExecutionState struct {
isReadOnly bool

FeeCalculator FeeCalculator

rollback *RollbackParams
}

type ExecutionResult struct {
Expand Down Expand Up @@ -1414,6 +1426,7 @@ func (es *ExecutionState) BuildBlock(blockId types.BlockNumber) (*BlockGeneratio
BaseFee: es.BaseFee,
GasUsed: es.GasUsed,
L1BlockNumber: l1BlockNumber,
PatchLevel: es.PatchLevel,
// TODO(@klonD90): remove this field after changing explorer
Timestamp: 0,
},
Expand Down Expand Up @@ -1701,6 +1714,19 @@ func (es *ExecutionState) GetGasPrice(shardId types.ShardId) (types.Value, error
return types.Value{Uint256: &prices.Shards[shardId]}, nil
}

func (es *ExecutionState) Rollback(counter, patchLevel uint32, mainBlock uint64) error {
es.rollback = &RollbackParams{
Counter: counter,
PatchLevel: patchLevel,
MainBlockId: mainBlock,
}
return nil
}

func (es *ExecutionState) GetRollback() *RollbackParams {
return es.rollback
}

func (es *ExecutionState) SetTokenTransfer(tokens []types.TokenBalance) {
es.evm.SetTokenTransfer(tokens)
}
Expand Down
1 change: 1 addition & 0 deletions nil/internal/execution/zerostate.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func CreateDefaultZeroStateConfig(mainPublicKey []byte) (*ZeroStateConfig, error
{Name: "BtcFaucet", Contract: "FaucetToken", Address: types.BtcFaucetAddress, Value: tokenValue},
{Name: "UsdcFaucet", Contract: "FaucetToken", Address: types.UsdcFaucetAddress, Value: tokenValue},
{Name: "L1BlockInfo", Contract: "system/L1BlockInfo", Address: types.L1BlockInfoAddress, Value: types.Value0},
{Name: "Governance", Contract: "system/Governance", Address: types.GovernanceAddress, Value: smartAccountValue, CtorArgs: []any{mainPublicKey}},
},
}
return zeroStateConfig, nil
Expand Down
11 changes: 1 addition & 10 deletions nil/internal/types/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
BtcFaucetAddress = ShardAndHexToAddress(BaseShardId, "111111111111111111111111111111111114")
UsdcFaucetAddress = ShardAndHexToAddress(BaseShardId, "111111111111111111111111111111111115")
L1BlockInfoAddress = ShardAndHexToAddress(MainShardId, "222222222222222222222222222222222222")
GovernanceAddress = ShardAndHexToAddress(MainShardId, "777777777777777777777777777777777777")
)

func GetTokenName(addr TokenId) string {
Expand Down Expand Up @@ -98,16 +99,6 @@ func ShardAndHexToAddress(shardId ShardId, s string) Address {
return addr
}

// IsHexAddress verifies whether a string can represent a valid hex-encoded
// Ethereum address or not.
func IsHexAddress(s string) bool {
if len(s) >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') {
s = s[2:]
}
_, err := hex.DecodeString(s)
return err == nil
}

// Bytes gets the string representation of the underlying address.
func (a Address) Bytes() []byte { return a[:] }

Expand Down
2 changes: 2 additions & 0 deletions nil/internal/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ type BlockData struct {
BaseFee Value `json:"gasPrice" ch:"gas_price"`
GasUsed Gas `json:"gasUsed" ch:"gas_used"`
L1BlockNumber uint64 `json:"l1BlockNumber" ch:"l1_block_number"`
RollbackCounter uint32 `json:"rollbackCounter" ch:"rollback_counter"`
PatchLevel uint32 `json:"patchLevel" ch:"patch_level"`
}

type ConsensusParams struct {
Expand Down
6 changes: 5 additions & 1 deletion nil/internal/types/exec_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,12 @@ const (
// ErrorPrecompileStateDbReturnedError is an internal error indicating that the Execution returned an error
ErrorPrecompileStateDbReturnedError
// ErrorOnlyMainShardContractsCanChangeConfig is returned when a contract from a shard other than the main one tries
// to change on-cahin config
// to change on-chain config
ErrorOnlyMainShardContractsCanChangeConfig
// ErrorPrecompileWrongCaller is returned when the caller of the precompile is not the expected one
ErrorPrecompileWrongCaller
// ErrorPrecompileWrongVersion is returned when the version of the precompile is not the expected one
ErrorPrecompileWrongVersion
// ErrorPrecompileConfigSetParamFailed is returned when the precompile fails to set the config parameter
ErrorPrecompileConfigSetParamFailed
// ErrorPrecompileConfigGetParamFailed is returned when the precompile fails to get the config parameter
Expand Down
2 changes: 2 additions & 0 deletions nil/internal/vm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ type StateDB interface {
SaveVmState(state *types.EvmState, continuationGasCredit types.Gas) error

GetConfigAccessor() config.ConfigAccessor

Rollback(counter, patchLevel uint32, mainBlock uint64) error
}

// CallContext provides a basic interface for the EVM calling conventions. The EVM
Expand Down
Loading

0 comments on commit 7e2da06

Please sign in to comment.