Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a "governance" system contract that will be able to initiate rollbacks #471

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
33 changes: 33 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,13 @@ 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)
}

p.proposal.PatchLevel = block.PatchLevel
p.proposal.RollbackCounter = block.RollbackCounter

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 +119,12 @@ 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
p.proposal.RollbackCounter = rollback.Counter + 1
}

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 +210,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
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
2 changes: 2 additions & 0 deletions nil/internal/execution/block_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ func (g *BlockGenerator) prepareExecutionState(proposal *Proposal, gasPrices []t
}

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

for _, txn := range proposal.InternalTxns {
if err := g.handleTxn(txn); err != nil {
Expand Down
28 changes: 18 additions & 10 deletions nil/internal/execution/proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ type InternalTxnReference struct {
}

type Proposal struct {
PrevBlockId types.BlockNumber `json:"prevBlockId"`
PrevBlockHash common.Hash `json:"prevBlockHash"`
CollatorState types.CollatorState `json:"collatorState"`
MainChainHash common.Hash `json:"mainChainHash"`
ShardHashes []common.Hash `json:"shardHashes"`
PrevBlockId types.BlockNumber `json:"prevBlockId"`
PrevBlockHash common.Hash `json:"prevBlockHash"`
PatchLevel uint32 `json:"patchLevel"`
RollbackCounter uint32 `json:"rollbackCounter"`
CollatorState types.CollatorState `json:"collatorState"`
MainChainHash common.Hash `json:"mainChainHash"`
ShardHashes []common.Hash `json:"shardHashes"`

InternalTxns []*types.Transaction `json:"internalTxns"`
ExternalTxns []*types.Transaction `json:"externalTxns"`
Expand All @@ -45,6 +47,10 @@ type Proposal struct {
type ProposalSSZ struct {
PrevBlockId types.BlockNumber
PrevBlockHash common.Hash

PatchLevel uint32
RollbackCounter uint32

CollatorState types.CollatorState
MainChainHash common.Hash
ShardHashes []common.Hash `ssz-max:"4096"`
Expand Down Expand Up @@ -154,11 +160,13 @@ func ConvertProposal(proposal *ProposalSSZ) (*Proposal, error) {
}

return &Proposal{
PrevBlockId: proposal.PrevBlockId,
PrevBlockHash: proposal.PrevBlockHash,
CollatorState: proposal.CollatorState,
MainChainHash: proposal.MainChainHash,
ShardHashes: proposal.ShardHashes,
PrevBlockId: proposal.PrevBlockId,
PrevBlockHash: proposal.PrevBlockHash,
PatchLevel: proposal.PatchLevel,
RollbackCounter: proposal.RollbackCounter,
CollatorState: proposal.CollatorState,
MainChainHash: proposal.MainChainHash,
ShardHashes: proposal.ShardHashes,

// todo: special txns should be validated
InternalTxns: append(proposal.SpecialTxns, internalTxns...),
Expand Down
36 changes: 36 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 @@ -59,6 +68,11 @@ type ExecutionState struct {
GasPrice types.Value // Current gas price including priority fee
BaseFee types.Value

// Those fields are just copied from the proposal into the block
// and are not used in the state
PatchLevel uint32
RollbackCounter uint32

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

FeeCalculator FeeCalculator

// filled in if a rollback was requested by a transaction
rollback *RollbackParams
}

type ExecutionResult struct {
Expand Down Expand Up @@ -215,12 +232,14 @@ func NewEVMBlockContext(es *ExecutionState) (*vm.BlockContext, error) {
currentBlockId := uint64(0)
var header *types.Block
time := uint64(0)
rollbackCounter := uint32(0)
if err == nil {
header = data.Block()
currentBlockId = header.Id.Uint64() + 1
// TODO: we need to use header.Timestamp instead of but it's always zero for now.
// Let's return some kind of logical timestamp (monotonic increasing block number).
time = header.Id.Uint64()
rollbackCounter = header.RollbackCounter
}
return &vm.BlockContext{
GetHash: getHashFn(es, header),
Expand All @@ -229,6 +248,8 @@ func NewEVMBlockContext(es *ExecutionState) (*vm.BlockContext, error) {
BaseFee: big.NewInt(10),
BlobBaseFee: big.NewInt(10),
Time: time,

RollbackCounter: rollbackCounter,
}, nil
}

Expand Down Expand Up @@ -1414,6 +1435,8 @@ func (es *ExecutionState) BuildBlock(blockId types.BlockNumber) (*BlockGeneratio
BaseFee: es.BaseFee,
GasUsed: es.GasUsed,
L1BlockNumber: l1BlockNumber,
PatchLevel: es.PatchLevel,
RollbackCounter: es.RollbackCounter,
// TODO(@klonD90): remove this field after changing explorer
Timestamp: 0,
},
Expand Down Expand Up @@ -1701,6 +1724,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{hexutil.Encode(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
6 changes: 6 additions & 0 deletions nil/internal/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ 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"`

// Incremented after every rollback, used to prevent rollback replay attacks
RollbackCounter uint32 `json:"rollbackCounter" ch:"rollback_counter"`
// Required validator patchLevel, incremented if validator updates
// are required to mitigate an issue
PatchLevel uint32 `json:"patchLevel" ch:"patch_level"`
}

type ConsensusParams struct {
Expand Down
Loading
Loading