Fantom-foundation/go-lachesis

View on GitHub
cmd/lachesis/opera_export_state.go

Summary

Maintainability
F
6 days
Test Coverage
package main

import (
    "bytes"
    "context"
    "fmt"
    "math"
    "math/big"
    "os"
    "path"
    "time"

    ointer "github.com/Fantom-foundation/go-opera/inter"
    "github.com/Fantom-foundation/go-opera/inter/drivertype"
    "github.com/Fantom-foundation/go-opera/inter/validatorpk"
    "github.com/Fantom-foundation/go-opera/opera"
    "github.com/Fantom-foundation/go-opera/opera/genesis"
    "github.com/Fantom-foundation/go-opera/opera/genesis/driver"
    "github.com/Fantom-foundation/go-opera/opera/genesis/driverauth"
    "github.com/Fantom-foundation/go-opera/opera/genesis/evmwriter"
    "github.com/Fantom-foundation/go-opera/opera/genesis/gpos"
    "github.com/Fantom-foundation/go-opera/opera/genesis/netinit"
    osfc "github.com/Fantom-foundation/go-opera/opera/genesis/sfc"
    "github.com/Fantom-foundation/go-opera/opera/genesisstore"
    futils "github.com/Fantom-foundation/go-opera/utils"
    "github.com/Fantom-foundation/lachesis-base/hash"
    oidx "github.com/Fantom-foundation/lachesis-base/inter/idx"
    "github.com/Fantom-foundation/lachesis-base/kvdb/leveldb"
    "github.com/ethereum/go-ethereum"
    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/cmd/utils"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/rawdb"
    "github.com/ethereum/go-ethereum/core/state"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/core/vm"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/log"
    ethparams "github.com/ethereum/go-ethereum/params"
    "github.com/ethereum/go-ethereum/rlp"
    "github.com/pkg/errors"
    "github.com/syndtr/goleveldb/leveldb/opt"
    "gopkg.in/urfave/cli.v1"

    "github.com/Fantom-foundation/go-lachesis/evmcore"
    "github.com/Fantom-foundation/go-lachesis/gossip"
    "github.com/Fantom-foundation/go-lachesis/gossip/sfc204"
    "github.com/Fantom-foundation/go-lachesis/inter"
    "github.com/Fantom-foundation/go-lachesis/inter/idx"
    "github.com/Fantom-foundation/go-lachesis/inter/sfctype"
    "github.com/Fantom-foundation/go-lachesis/lachesis"
    "github.com/Fantom-foundation/go-lachesis/lachesis/genesis/sfc"
    "github.com/Fantom-foundation/go-lachesis/lachesis/genesis/sfc/sfcpos"
    "github.com/Fantom-foundation/go-lachesis/poset"
)

var (
    emptyCodeHash = common.BytesToHash(crypto.Keccak256(nil))
    emptyRoot     = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
    emptyNodeHash = common.Hash{}
)

type evmCaller struct {
    statedb     *state.StateDB
    header      *evmcore.EvmHeader
    chainConfig *ethparams.ChainConfig
}

func (c *evmCaller) CodeAt(_ context.Context, contract common.Address, _ *big.Int) ([]byte, error) {
    return c.statedb.GetCode(contract), nil
}

func (c *evmCaller) CallContract(_ context.Context, call ethereum.CallMsg, _ *big.Int) ([]byte, error) {
    msg := types.NewMessage(call.From, call.To, 0, new(big.Int), 1e12, new(big.Int), call.Data, false)

    // Create a new context to be used in the EVM environment
    econtext := evmcore.NewEVMContext(msg, c.header, nil, nil)
    // Create a new environment which holds all relevant information
    // about the transaction and calling mechanisms.
    vmenv := vm.NewEVM(econtext, c.statedb, c.chainConfig, vm.Config{})

    gp := new(evmcore.GasPool).AddGas(math.MaxUint64)
    result, err := evmcore.ApplyMessage(vmenv, msg, gp)
    if err != nil {
        return nil, err
    }
    if result.Err != nil {
        return nil, fmt.Errorf("revert err: %v", result.Err)
    }
    return result.Return(), nil
}

func addDelegation(genStore *genesisstore.Store, addr common.Address, to oidx.ValidatorID, added genesis.Delegation) {
    delegation := genStore.GetDelegation(addr, to)
    delegation.Stake.Add(delegation.Stake, added.Stake)
    delegation.Rewards.Add(delegation.Rewards, added.Rewards)
    delegation.LockedStake.Add(delegation.LockedStake, added.LockedStake)
    delegation.EarlyUnlockPenalty.Add(delegation.EarlyUnlockPenalty, added.EarlyUnlockPenalty)
    if delegation.LockupFromEpoch == 0 {
        delegation.LockupFromEpoch = added.LockupFromEpoch
    }
    if delegation.LockupDuration == 0 {
        delegation.LockupDuration = added.LockupDuration
    }
    if delegation.LockupEndTime == 0 {
        delegation.LockupEndTime = added.LockupEndTime
    }
    genStore.SetDelegation(addr, to, delegation)
}

func addBalance(genStore *genesisstore.Store, statedb *state.StateDB, addr common.Address, added *big.Int) {
    acc := genStore.GetEvmAccount(addr)
    if acc.Balance.Sign() == 0 {
        acc.Balance = statedb.GetBalance(addr)
    }
    acc.Balance = new(big.Int).Add(acc.Balance, added)
    genStore.SetEvmAccount(addr, acc)
}

type withdrawalRequestID struct {
    from common.Address
    wrID string
}

type rawEvmItemsToEthdb struct {
    *genesisstore.Store
}

// Put inserts the given value into the key-value data store.
func (db *rawEvmItemsToEthdb) Put(key []byte, value []byte) error {
    db.Store.SetRawEvmItem(key, value)
    return nil
}

// Delete removes the key from the key-value data store.
func (db *rawEvmItemsToEthdb) Delete(key []byte) error {
    return errors.New("not supported")
}

func ExportState(path string, gdb *gossip.Store, cdb *poset.Store, net *lachesis.Config) (*genesisstore.Store, error) {
    _ = os.RemoveAll(path)

    err := os.MkdirAll(path, 0700)
    if err != nil {
        return nil, err
    }
    db, err := leveldb.New(path, 2*opt.MiB, 0, nil, nil)
    if err != nil {
        return nil, err
    }

    genStore := genesisstore.NewStore(db)

    start, reported := time.Now(), time.Time{}

    // export blocks, transactions and receipts
    var lastBlock *inter.Block
    gdb.ForEachBlock(func(index idx.Block, block *inter.Block) {
        lastBlock = block
        receipts := gdb.App().GetReceipts(index)
        receiptsStorage := make([]*types.ReceiptForStorage, len(receipts))
        for i, r := range receipts {
            receiptsStorage[i] = (*types.ReceiptForStorage)(r)
        }
        genStore.SetBlock(oidx.Block(index), genesis.Block{
            Time:        ointer.Timestamp(block.Time),
            Atropos:     hash.Event(block.Atropos),
            Txs:         gdb.GetBlockTransactions(block),
            InternalTxs: types.Transactions{},
            Root:        hash.Hash(block.Root),
            Receipts:    receiptsStorage,
        })
        if time.Since(reported) >= statsReportLimit {
            log.Info("Exporting blocks", "last", lastBlock.Index, "elapsed", common.PrettyDuration(time.Since(start)))
            reported = time.Now()
        }
    })
    log.Info("Exported blocks", "last", lastBlock.Index, "elapsed", common.PrettyDuration(time.Since(start)))

    // export EVM state
    log.Info("Exporting EVM state", "root", lastBlock.Root.String())
    statedb, _ := gdb.App().StateDB(lastBlock.Root)

    stateTr, err := statedb.Database().OpenTrie(lastBlock.Root)
    if err != nil {
        return genStore, errors.Wrap(err, "failed to open EVM trie")
    }

    // export EVM tries
    destDb := &rawEvmItemsToEthdb{genStore}
    stateIt := stateTr.NodeIterator(nil)
    for stateIt.Next(true) {
        if stateIt.Leaf() {
            addrHash := common.BytesToHash(stateIt.LeafKey())

            var account state.Account
            if err := rlp.Decode(bytes.NewReader(stateIt.LeafBlob()), &account); err != nil {
                return genStore, errors.Wrap(err, "failed to decode account")
            }

            codeHash := common.BytesToHash(account.CodeHash)
            if codeHash != emptyCodeHash {
                code, err := statedb.Database().ContractCode(addrHash, codeHash)
                if err != nil {
                    return genStore, errors.Wrap(err, "failed to open EVM trie")
                }
                rawdb.WriteCode(destDb, codeHash, code)
            }

            if account.Root != emptyRoot {
                dataTr, err := statedb.Database().OpenStorageTrie(addrHash, account.Root)
                if err != nil {
                    return genStore, errors.Wrap(err, "failed to open EVM trie")
                }
                dataIt := dataTr.NodeIterator(nil)
                for dataIt.Next(true) {
                    if dataIt.Leaf() || dataIt.Hash() == emptyNodeHash {
                        continue
                    }
                    nodeBody, err := gdb.App().EvmTable().Get(dataIt.Hash().Bytes())
                    if err != nil {
                        return genStore, errors.Wrap(err, "failed to get storage trie node")
                    }
                    rawdb.WriteTrieNode(destDb, dataIt.Hash(), nodeBody)
                }
            }
        } else if stateIt.Hash() != emptyNodeHash {
            nodeBody, err := gdb.App().EvmTable().Get(stateIt.Hash().Bytes())
            if err != nil {
                return genStore, errors.Wrap(err, "failed to get trie node")
            }
            rawdb.WriteTrieNode(destDb, stateIt.Hash(), nodeBody)
        }
    }

    // SFC 3.x
    genStore.SetEvmAccount(osfc.ContractAddress, genesis.Account{
        Code:         osfc.GetContractBin(),
        Balance:      new(big.Int),
        Nonce:        0,
        SelfDestruct: true, // erase SFCv2 storage
    })
    // NodeDriverAuth
    genStore.SetEvmAccount(driverauth.ContractAddress, genesis.Account{
        Code:    driverauth.GetContractBin(),
        Balance: new(big.Int),
        Nonce:   0,
    })
    // NodeDriver
    genStore.SetEvmAccount(driver.ContractAddress, genesis.Account{
        Code:    driver.GetContractBin(),
        Balance: new(big.Int),
        Nonce:   0,
    })
    // NetworkInitializer
    genStore.SetEvmAccount(netinit.ContractAddress, genesis.Account{
        Code:    netinit.GetContractBin(),
        Balance: new(big.Int),
        Nonce:   0,
    })
    // EvmWriter
    genStore.SetEvmAccount(evmwriter.ContractAddress, genesis.Account{
        Code:    []byte{0},
        Balance: new(big.Int),
        Nonce:   0,
    })
    log.Info("Exported EVM state", "elapsed", common.PrettyDuration(time.Since(start)))

    // export network rules
    var rules = opera.TestNetRules()
    if net.NetworkID == lachesis.MainNetworkID {
        rules = opera.MainNetRules()
    } else {
        rules.NetworkID = net.NetworkID
        rules.Name = net.Name
    }
    genStore.SetRules(rules)

    // export metadata
    metadata := genesisstore.Metadata{}

    lastStakerID := idx.StakerID(statedb.GetState(sfc.ContractAddress, sfcpos.StakersLastID()).Big().Uint64())
    metadata.Validators = make(gpos.Validators, lastStakerID)

    caller := &evmCaller{
        statedb:     statedb,
        header:      evmcore.ToEvmHeader(lastBlock),
        chainConfig: net.EvmChainConfig(),
    }

    sfcCaller, err := sfc204.NewContractCaller(sfc.ContractAddress, caller)
    if err != nil {
        return genStore, err
    }

    callsOpts := &bind.CallOpts{
        Pending:     false,
        From:        common.Address{},
        BlockNumber: futils.U64toBig(uint64(lastBlock.Index)),
        Context:     context.TODO(),
    }

    // export validators
    log.Info("Exporting validators")

    epoch := cdb.GetEpoch().EpochN
    for stakerID := idx.StakerID(1); stakerID <= lastStakerID; stakerID++ {
        vpos := sfcpos.Staker(stakerID)
        status := statedb.GetState(sfc.ContractAddress, vpos.Status()).Big().Uint64()
        stake := statedb.GetState(sfc.ContractAddress, vpos.StakeAmount()).Big()
        createdTime := inter.Timestamp(statedb.GetState(sfc.ContractAddress, vpos.CreatedTime()).Big().Uint64() * uint64(time.Second))
        createdEpoch := idx.Epoch(statedb.GetState(sfc.ContractAddress, vpos.CreatedEpoch()).Big().Uint64())
        deactivatedTime := inter.Timestamp(statedb.GetState(sfc.ContractAddress, vpos.DeactivatedTime()).Big().Uint64() * uint64(time.Second))
        deactivatedEpoch := idx.Epoch(statedb.GetState(sfc.ContractAddress, vpos.DeactivatedEpoch()).Big().Uint64())
        addr := common.BytesToAddress(statedb.GetState(sfc.ContractAddress, vpos.Address()).Bytes()[12:])

        // retrieve rewards
        pendingRewards, _, _, _ := sfcCaller.CalcValidatorRewards(callsOpts, futils.U64toBig(uint64(stakerID)), futils.U64toBig(0), futils.U64toBig(uint64(epoch)))
        if pendingRewards == nil {
            pendingRewards = new(big.Int)
        }

        oDeactivatedEpoch := deactivatedEpoch
        oDeactivatedTime := deactivatedTime
        oStatus := status

        if stake.Sign() != 0 {
            if oDeactivatedEpoch != 0 && status != sfctype.ForkBit {
                // finalize withdrawal balance if validator isn't a cheater
                addBalance(genStore, statedb, addr, stake)
            } else {
                // retrieve lockup info
                info, _ := sfcCaller.LockedStakes(callsOpts, futils.U64toBig(uint64(stakerID)))
                if info.Duration == nil {
                    info.Duration = new(big.Int)
                }
                if info.EndTime == nil {
                    info.EndTime = new(big.Int)
                }
                if info.FromEpoch == nil {
                    info.FromEpoch = new(big.Int)
                }
                earlyUnlockPenalty, _ := sfcCaller.CalcValidatorLockupPenalty(callsOpts, futils.U64toBig(uint64(stakerID)), stake)
                if earlyUnlockPenalty == nil {
                    earlyUnlockPenalty = new(big.Int)
                }
                lockedStake := stake
                if info.Duration.Sign() == 0 {
                    lockedStake = new(big.Int)
                }
                // add validator self-delegation
                addDelegation(genStore, addr, oidx.ValidatorID(stakerID), genesis.Delegation{
                    Stake:              stake,
                    Rewards:            pendingRewards,
                    LockedStake:        lockedStake,
                    LockupFromEpoch:    oidx.Epoch(info.FromEpoch.Uint64()),
                    LockupEndTime:      ointer.Timestamp(info.EndTime.Uint64()),
                    LockupDuration:     ointer.Timestamp(info.Duration.Uint64()),
                    EarlyUnlockPenalty: earlyUnlockPenalty,
                })
            }
        } else {
            // recover creation time/epoch/address from logs and genesis data
            if v, ok := net.Genesis.Alloc.Validators.Map()[stakerID]; ok {
                addr = v.Address
                createdEpoch = 0
                createdTime = net.Genesis.Time
            } else {
                // event CreatedStake(uint256 indexed stakerID, address indexed dagSfcAddress, uint256 amount)
                creationLog, err := gdb.App().EvmLogs().Find([][]common.Hash{{sfcpos.Topics.CreatedStake}, {futils.U64to256(uint64(stakerID))}})
                if err != nil {
                    return genStore, err
                }
                for _, l := range creationLog {
                    if l.Address != sfc.ContractAddress {
                        continue
                    }
                    addr = common.BytesToAddress(l.Topics[2][12:])
                    block := gdb.GetBlock(idx.Block(l.BlockNumber))
                    createdTime = block.Time
                }
                var e = idx.Epoch(0)
                for ; e < epoch; e++ {
                    stats := gdb.GetEpochStats(e)
                    if createdTime <= stats.End {
                        createdEpoch = e
                        break
                    }
                }
                if e == epoch {
                    createdEpoch = epoch
                }
            }
            // recover deactivation time/epoch from logs
            {
                // event WithdrawnStake(uint256 indexed stakerID, uint256 penalty)
                withdrawalLog, err := gdb.App().EvmLogs().Find([][]common.Hash{{sfcpos.Topics.WithdrawnStake}, {futils.U64to256(uint64(stakerID))}})
                if err != nil {
                    return genStore, err
                }
                for _, l := range withdrawalLog {
                    if l.Address != sfc.ContractAddress {
                        continue
                    }
                    block := gdb.GetBlock(idx.Block(l.BlockNumber))
                    oDeactivatedTime = block.Time
                }
                var e = idx.Epoch(0)
                for ; e < epoch; e++ {
                    stats := gdb.GetEpochStats(e)
                    if oDeactivatedTime <= stats.End {
                        oDeactivatedEpoch = e
                        break
                    }
                }
                if e == epoch {
                    oDeactivatedEpoch = epoch
                }
            }
        }

        if oDeactivatedEpoch != 0 {
            // withdrawn
            oStatus = 1
        }
        if status == sfctype.OfflineBit {
            oStatus = 1 << 3
        }
        if status == sfctype.ForkBit {
            oStatus = drivertype.DoublesignBit
        }
        if oStatus != 0 && oDeactivatedEpoch == 0 {
            // find highest epoch where validator was active
            for e := epoch; e > createdEpoch; e-- {
                if !gdb.App().HasEpochValidator(e, stakerID) {
                    oDeactivatedEpoch = e - 1
                    oDeactivatedTime = gdb.GetEpochStats(e - 1).End
                }
            }
        }

        metadata.Validators[stakerID-1] = gpos.Validator{
            ID:               oidx.ValidatorID(stakerID),
            Address:          addr,
            PubKey:           validatorpk.PubKey{},
            CreationTime:     ointer.Timestamp(createdTime),
            CreationEpoch:    oidx.Epoch(createdEpoch),
            DeactivatedTime:  ointer.Timestamp(oDeactivatedTime),
            DeactivatedEpoch: oidx.Epoch(oDeactivatedEpoch),
            Status:           oStatus,
        }
    }

    log.Info("Recovering validators public keys")
    isEpochInterested := func(e idx.Epoch, metadata *genesisstore.Metadata) bool {
        for _, v := range metadata.Validators {
            if v.PubKey.Empty() && oidx.Epoch(e) >= v.CreationEpoch && (v.DeactivatedEpoch == 0 || oidx.Epoch(e) <= v.DeactivatedEpoch) {
                return true
            }
        }
        return false
    }
    // find latest secp256k1 public key
    for e := epoch; e > 0; e-- {
        if !isEpochInterested(e, &metadata) {
            continue
        }
        gdb.ForEachEpochEvent(e, func(event *inter.Event) bool {
            if event.Lamport > 1000 {
                return false
            }
            if !metadata.Validators[event.Creator-1].PubKey.Empty() {
                return true
            }
            if cdb.GetEventConfirmedOn(event.Hash()) == 0 {
                // cannot use non-confirmed events for the sake of determinism
                return true
            }
            pk := event.RecoverPubkey()
            if pk != nil {
                metadata.Validators[event.Creator-1].PubKey.Type = validatorpk.Types.Secp256k1
                metadata.Validators[event.Creator-1].PubKey.Raw = crypto.FromECDSAPub(pk)
                return isEpochInterested(e, &metadata)
            }
            return true
        })
    }

    log.Info("Exported validators", "elapsed", common.PrettyDuration(time.Since(start)))

    // export delegations
    log.Info("Exporting delegations")
    validatorsMap := metadata.Validators.Map()
    gdb.App().ForEachSfcDelegation(func(it sfctype.SfcDelegationAndID) {
        // retrieve stake amount
        delegation, _ := sfcCaller.Delegations(callsOpts, it.ID.Delegator, futils.U64toBig(uint64(it.ID.StakerID)))
        if delegation.Amount == nil {
            return
        }
        if delegation.Amount.Sign() == 0 {
            return
        }

        // retrieve rewards
        var pendingRewards *big.Int
        if delegation.DeactivatedTime.Sign() == 0 {
            pendingRewards, _, _, _ = sfcCaller.CalcDelegationRewards(callsOpts, it.ID.Delegator, futils.U64toBig(uint64(it.ID.StakerID)), futils.U64toBig(0), futils.U64toBig(uint64(epoch)))
        }
        if pendingRewards == nil {
            pendingRewards = new(big.Int)
        }

        v := validatorsMap[oidx.ValidatorID(it.ID.StakerID)]
        if delegation.DeactivatedTime.Sign() != 0 && v.Status != drivertype.DoublesignBit {
            // finalize withdrawal balance if validator isn't a cheater
            addBalance(genStore, statedb, it.ID.Delegator, delegation.Amount)
        } else {
            // retrieve lockup info
            info, _ := sfcCaller.LockedDelegations(callsOpts, it.ID.Delegator, futils.U64toBig(uint64(it.ID.StakerID)))
            if info.Duration == nil {
                info.Duration = new(big.Int)
            }
            if info.EndTime == nil {
                info.EndTime = new(big.Int)
            }
            if info.FromEpoch == nil {
                info.FromEpoch = new(big.Int)
            }
            earlyUnlockPenalty, _ := sfcCaller.DelegationEarlyWithdrawalPenalty(callsOpts, it.ID.Delegator, futils.U64toBig(uint64(it.ID.StakerID)))
            if earlyUnlockPenalty == nil {
                earlyUnlockPenalty = new(big.Int)
            }
            lockedStake := delegation.Amount
            if info.Duration.Sign() == 0 {
                lockedStake = new(big.Int)
            }
            // add delegation
            addDelegation(genStore, it.ID.Delegator, oidx.ValidatorID(it.ID.StakerID), genesis.Delegation{
                Stake:              delegation.Amount,
                Rewards:            pendingRewards,
                LockedStake:        lockedStake,
                LockupFromEpoch:    oidx.Epoch(info.FromEpoch.Uint64()),
                LockupEndTime:      ointer.Timestamp(info.EndTime.Uint64()),
                LockupDuration:     ointer.Timestamp(info.Duration.Uint64()),
                EarlyUnlockPenalty: earlyUnlockPenalty,
            })
        }
    })
    log.Info("Exported delegations", "elapsed", common.PrettyDuration(time.Since(start)))

    withdrawalRequestLogs, err := gdb.App().EvmLogs().Find([][]common.Hash{{sfcpos.Topics.CreatedWithdrawRequest}})
    if err != nil {
        return genStore, err
    }
    paidWrs := map[withdrawalRequestID]bool{}
    for _, l := range withdrawalRequestLogs {
        if l.Address != sfc.ContractAddress {
            continue
        }
        receiver := common.BytesToAddress(l.Topics[2].Bytes()[12:])
        wrID := new(big.Int).SetBytes(l.Data[0:32])
        fullWrID := withdrawalRequestID{receiver, wrID.String()}
        if paidWrs[fullWrID] {
            continue
        }
        res, _ := sfcCaller.WithdrawalRequests(callsOpts, receiver, wrID)
        if res.Amount == nil || res.Amount.Sign() == 0 {
            continue
        }
        stakerID := oidx.ValidatorID(res.StakerID.Uint64())
        v := validatorsMap[stakerID]
        if v.Status != drivertype.DoublesignBit {
            // finalize withdrawal balance if validator isn't a cheater
            addBalance(genStore, statedb, receiver, res.Amount)
        } else {
            addDelegation(genStore, receiver, stakerID, genesis.Delegation{
                Stake:              res.Amount,
                Rewards:            new(big.Int),
                LockedStake:        new(big.Int),
                LockupFromEpoch:    0,
                LockupEndTime:      0,
                LockupDuration:     0,
                EarlyUnlockPenalty: new(big.Int),
            })
        }
        paidWrs[fullWrID] = true
    }

    metadata.ExtraData = []byte{}
    metadata.FirstEpoch = oidx.Epoch(epoch + 1)
    metadata.Time = ointer.Timestamp(lastBlock.Time + inter.Timestamp(time.Second))
    metadata.PrevEpochTime = ointer.Timestamp(gdb.GetEpochStats(epoch - 1).End)
    metadata.DriverOwner, _ = sfcCaller.Owner(callsOpts)
    metadata.TotalSupply = gdb.App().GetTotalSupply()
    genStore.SetMetadata(metadata)

    return genStore, nil
}

func ExportEncodedState(encodedPath, tmpPath string, gdb *gossip.Store, cdb *poset.Store, net *lachesis.Config) (*genesisstore.Store, error) {
    _ = os.RemoveAll(tmpPath)

    log.Info("Exporting go-opera genesis state", "path", tmpPath)
    genStore, err := ExportState(tmpPath, gdb, cdb, net)
    if err != nil {
        return genStore, err
    }

    log.Info("Encoding go-opera genesis state", "path", encodedPath)
    fh, err := os.Create(encodedPath)
    if err != nil {
        return genStore, err
    }
    defer fh.Close()
    err = genesisstore.WriteGenesisStore(fh, genStore)
    if err != nil {
        return genStore, err
    }
    return genStore, nil
}

func exportState(ctx *cli.Context) error {
    if len(ctx.Args()) < 1 {
        utils.Fatalf("This command requires an argument.")
    }

    cfg := makeAllConfigs(ctx)

    gdb, cdb := makeStores(cfg.Node.DataDir, &cfg.Lachesis)
    defer gdb.Close()
    defer cdb.Close()

    genFilePath := ctx.Args().First()

    start := time.Now()

    tmpPath := path.Join(cfg.Node.DataDir, "tmp", "export")
    _ = os.RemoveAll(tmpPath)

    genStore, err := ExportEncodedState(genFilePath, tmpPath, gdb, cdb, &cfg.Lachesis.Net)
    if genStore != nil {
        defer genStore.Close()
        defer os.RemoveAll(tmpPath)
    }
    if err != nil {
        utils.Fatalf("Failed to export state: %v\n", err)
    }

    log.Info("Exported go-opera genesis state", "elapsed", common.PrettyDuration(time.Since(start)))
    return nil
}