2018-06-13 09:56:01 +00:00
package main
import (
2018-07-02 22:28:39 +00:00
"bytes"
2018-06-24 21:58:28 +00:00
"encoding/binary"
2018-07-02 22:28:39 +00:00
"encoding/json"
2018-06-13 09:56:01 +00:00
"fmt"
2018-06-19 11:32:26 +00:00
"io"
2018-06-25 16:31:20 +00:00
"math/big"
2018-06-19 11:32:26 +00:00
"os"
2018-06-13 09:56:01 +00:00
eth_common "github.com/ethereum/go-ethereum/common"
2018-06-18 21:10:29 +00:00
eth_core "github.com/ethereum/go-ethereum/core"
2018-06-13 09:56:01 +00:00
eth_state "github.com/ethereum/go-ethereum/core/state"
2018-06-19 11:32:26 +00:00
eth_types "github.com/ethereum/go-ethereum/core/types"
2018-06-25 16:31:20 +00:00
eth_vm "github.com/ethereum/go-ethereum/core/vm"
2018-06-19 11:32:26 +00:00
eth_rlp "github.com/ethereum/go-ethereum/rlp"
2018-06-13 09:56:01 +00:00
eth_ethdb "github.com/ethereum/go-ethereum/ethdb"
2018-06-19 11:32:26 +00:00
eth_params "github.com/ethereum/go-ethereum/params"
2018-06-25 16:31:20 +00:00
eth_rpc "github.com/ethereum/go-ethereum/rpc"
2018-06-13 09:56:01 +00:00
eth_trie "github.com/ethereum/go-ethereum/trie"
2018-06-25 16:31:20 +00:00
eth_consensus "github.com/ethereum/go-ethereum/consensus"
eth_misc "github.com/ethereum/go-ethereum/consensus/misc"
2018-06-13 09:56:01 +00:00
dbm "github.com/tendermint/tmlibs/db"
2018-06-13 14:29:22 +00:00
"github.com/cosmos/cosmos-sdk/store"
2018-06-15 10:05:12 +00:00
"github.com/cosmos/cosmos-sdk/types"
)
var (
// Key for the sub-store with Ethereum accounts
2018-06-18 15:33:07 +00:00
AccountsKey = types . NewKVStoreKey ( "account" )
2018-06-15 10:05:12 +00:00
// Key for the sub-store with storage data of Ethereum contracts
StorageKey = types . NewKVStoreKey ( "storage" )
2018-06-18 15:33:07 +00:00
// Key for the sub-store with the code for contracts
CodeKey = types . NewKVStoreKey ( "code" )
2018-06-13 09:56:01 +00:00
)
2018-06-24 21:58:28 +00:00
type CommitHashPreimage struct {
2018-06-15 13:26:06 +00:00
VersionId int64
2018-06-24 21:58:28 +00:00
Prefix [ ] byte
2018-06-15 13:26:06 +00:00
}
2018-06-25 15:27:05 +00:00
var miner501 = eth_common . HexToAddress ( "0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D" )
2018-06-13 09:56:01 +00:00
// Implementation of eth_state.Database
type OurDatabase struct {
2018-06-15 10:05:12 +00:00
stateStore store . CommitMultiStore // For the history of accounts <balance, nonce, storage root hash, code hash>
// Also, for the history of contract data (effects of SSTORE instruction)
2018-06-24 21:58:28 +00:00
accountsCache store . CacheKVStore
storageCache store . CacheKVStore
2018-06-18 15:33:07 +00:00
codeDb dbm . DB // Mapping [codeHash] -> <code>
2018-06-20 04:49:52 +00:00
tracing bool
2018-06-25 16:31:20 +00:00
trieDbDummy * eth_trie . Database
2018-06-13 09:56:01 +00:00
}
2018-06-24 21:58:28 +00:00
func OurNewDatabase ( stateDb , codeDb dbm . DB ) ( * OurDatabase , error ) {
2018-06-15 10:05:12 +00:00
od := & OurDatabase { }
od . stateStore = store . NewCommitMultiStore ( stateDb )
od . stateStore . MountStoreWithDB ( AccountsKey , types . StoreTypeIAVL , nil )
od . stateStore . MountStoreWithDB ( StorageKey , types . StoreTypeIAVL , nil )
2018-06-18 15:33:07 +00:00
if err := od . stateStore . LoadLatestVersion ( ) ; err != nil {
return nil , err
}
od . codeDb = codeDb
2018-06-26 11:41:29 +00:00
od . trieDbDummy = eth_trie . NewDatabase ( & OurEthDb { codeDb : codeDb } )
2018-06-18 15:33:07 +00:00
return od , nil
2018-06-13 09:56:01 +00:00
}
2018-06-24 21:58:28 +00:00
// root is not interpreted as a hash, but as an encoding of version
2018-06-13 09:56:01 +00:00
func ( od * OurDatabase ) OpenTrie ( root eth_common . Hash ) ( eth_state . Trie , error ) {
2018-06-15 13:26:06 +00:00
// Look up version id to use
2018-06-20 04:49:52 +00:00
hasData := root != ( eth_common . Hash { } )
2018-07-02 22:28:39 +00:00
versionId := od . stateStore . LastCommitID ( ) . Version
2018-06-20 04:49:52 +00:00
if hasData {
2018-06-24 21:58:28 +00:00
// First 8 bytes encode version
versionId = int64 ( binary . BigEndian . Uint64 ( root [ : 8 ] ) )
2018-06-25 15:27:05 +00:00
if od . stateStore . LastCommitID ( ) . Version != versionId {
if err := od . stateStore . LoadVersion ( versionId ) ; err != nil {
return nil , err
}
od . accountsCache = nil
2018-06-20 04:49:52 +00:00
}
2018-06-15 13:26:06 +00:00
}
2018-06-24 21:58:28 +00:00
if od . accountsCache == nil {
od . accountsCache = store . NewCacheKVStore ( od . stateStore . GetCommitKVStore ( AccountsKey ) )
2018-07-03 16:30:54 +00:00
od . storageCache = store . NewCacheKVStore ( od . stateStore . GetCommitKVStore ( StorageKey ) )
2018-06-24 21:58:28 +00:00
}
2018-07-03 16:30:54 +00:00
return & OurTrie { od : od , st : od . accountsCache , prefix : nil , hasData : hasData } , nil
2018-06-13 09:56:01 +00:00
}
func ( od * OurDatabase ) OpenStorageTrie ( addrHash , root eth_common . Hash ) ( eth_state . Trie , error ) {
2018-06-20 04:49:52 +00:00
hasData := root != ( eth_common . Hash { } )
2018-07-03 16:30:54 +00:00
return & OurTrie { od : od , st : od . storageCache , prefix : addrHash [ : ] , hasData : hasData } , nil
2018-06-13 09:56:01 +00:00
}
func ( od * OurDatabase ) CopyTrie ( eth_state . Trie ) eth_state . Trie {
return nil
}
func ( od * OurDatabase ) ContractCode ( addrHash , codeHash eth_common . Hash ) ( [ ] byte , error ) {
2018-06-18 15:33:07 +00:00
code := od . codeDb . Get ( codeHash [ : ] )
return code , nil
2018-06-13 09:56:01 +00:00
}
func ( od * OurDatabase ) ContractCodeSize ( addrHash , codeHash eth_common . Hash ) ( int , error ) {
2018-06-18 15:33:07 +00:00
code := od . codeDb . Get ( codeHash [ : ] )
return len ( code ) , nil
2018-06-13 09:56:01 +00:00
}
func ( od * OurDatabase ) TrieDB ( ) * eth_trie . Database {
2018-06-25 16:31:20 +00:00
return od . trieDbDummy
2018-06-13 09:56:01 +00:00
}
2018-06-26 11:41:29 +00:00
// Implementation of state.Trie from go-ethereum
2018-06-13 09:56:01 +00:00
type OurTrie struct {
2018-06-15 21:32:35 +00:00
od * OurDatabase
2018-06-15 10:05:12 +00:00
// This is essentially part of the KVStore for a specific prefix
2018-06-24 21:58:28 +00:00
st store . KVStore
2018-06-15 10:05:12 +00:00
prefix [ ] byte
2018-06-20 04:49:52 +00:00
hasData bool
2018-06-13 09:56:01 +00:00
}
2018-06-15 20:51:55 +00:00
func ( ot * OurTrie ) makePrefix ( key [ ] byte ) [ ] byte {
kk := make ( [ ] byte , len ( ot . prefix ) + len ( key ) )
copy ( kk , ot . prefix )
copy ( kk [ len ( ot . prefix ) : ] , key )
return kk
}
2018-06-13 09:56:01 +00:00
func ( ot * OurTrie ) TryGet ( key [ ] byte ) ( [ ] byte , error ) {
2018-06-15 20:51:55 +00:00
if ot . prefix == nil {
return ot . st . Get ( key ) , nil
}
return ot . st . Get ( ot . makePrefix ( key ) ) , nil
2018-06-13 09:56:01 +00:00
}
func ( ot * OurTrie ) TryUpdate ( key , value [ ] byte ) error {
2018-06-20 04:49:52 +00:00
ot . hasData = true
2018-06-15 20:51:55 +00:00
if ot . prefix == nil {
ot . st . Set ( key , value )
return nil
}
ot . st . Set ( ot . makePrefix ( key ) , value )
2018-06-13 09:56:01 +00:00
return nil
}
func ( ot * OurTrie ) TryDelete ( key [ ] byte ) error {
2018-06-15 20:51:55 +00:00
if ot . prefix == nil {
ot . st . Delete ( key )
return nil
}
ot . st . Delete ( ot . makePrefix ( key ) )
2018-06-13 09:56:01 +00:00
return nil
}
func ( ot * OurTrie ) Commit ( onleaf eth_trie . LeafCallback ) ( eth_common . Hash , error ) {
2018-06-20 04:49:52 +00:00
if ! ot . hasData {
return eth_common . Hash { } , nil
}
2018-06-24 21:58:28 +00:00
var commitHash eth_common . Hash
2018-07-02 22:28:39 +00:00
// We assume here that the next committed version will be od.stateStore.LastCommitID().Version+1
binary . BigEndian . PutUint64 ( commitHash [ : 8 ] , uint64 ( ot . od . stateStore . LastCommitID ( ) . Version + 1 ) )
2018-06-25 15:27:05 +00:00
if ot . prefix == nil {
if ot . od . accountsCache != nil {
ot . od . accountsCache . Write ( )
ot . od . accountsCache = nil
}
if ot . od . storageCache != nil {
ot . od . storageCache . Write ( )
ot . od . storageCache = nil
}
2018-06-26 11:41:29 +00:00
// Enumerate cached nodes from trie.Database
for _ , n := range ot . od . trieDbDummy . Nodes ( ) {
if err := ot . od . trieDbDummy . Commit ( n , false ) ; err != nil {
return eth_common . Hash { } , err
}
}
2018-06-15 21:32:35 +00:00
}
2018-06-24 21:58:28 +00:00
return commitHash , nil
2018-06-13 09:56:01 +00:00
}
func ( ot * OurTrie ) Hash ( ) eth_common . Hash {
return eth_common . Hash { }
}
func ( ot * OurTrie ) NodeIterator ( startKey [ ] byte ) eth_trie . NodeIterator {
return nil
}
func ( ot * OurTrie ) GetKey ( [ ] byte ) [ ] byte {
return nil
}
func ( ot * OurTrie ) Prove ( key [ ] byte , fromLevel uint , proofDb eth_ethdb . Putter ) error {
return nil
}
2018-06-26 11:41:29 +00:00
// Dummy implementation of core.ChainContext and consensus Engine from go-ethereum
2018-06-25 16:31:20 +00:00
type OurChainContext struct {
2018-06-26 11:41:29 +00:00
coinbase eth_common . Address // This is where the transaction fees will go
2018-06-25 16:31:20 +00:00
}
func ( occ * OurChainContext ) Engine ( ) eth_consensus . Engine {
2018-06-26 11:41:29 +00:00
return occ
2018-06-25 16:31:20 +00:00
}
func ( occ * OurChainContext ) GetHeader ( eth_common . Hash , uint64 ) * eth_types . Header {
return nil
}
2018-06-26 11:41:29 +00:00
func ( occ * OurChainContext ) Author ( header * eth_types . Header ) ( eth_common . Address , error ) {
return occ . coinbase , nil
2018-06-25 16:31:20 +00:00
}
2018-06-26 11:41:29 +00:00
func ( occ * OurChainContext ) APIs ( chain eth_consensus . ChainReader ) [ ] eth_rpc . API {
return nil
2018-06-25 16:31:20 +00:00
}
2018-06-26 11:41:29 +00:00
func ( occ * OurChainContext ) CalcDifficulty ( chain eth_consensus . ChainReader , time uint64 , parent * eth_types . Header ) * big . Int {
2018-06-25 16:31:20 +00:00
return nil
}
2018-06-26 11:41:29 +00:00
func ( occ * OurChainContext ) Finalize ( chain eth_consensus . ChainReader , header * eth_types . Header , state * eth_state . StateDB , txs [ ] * eth_types . Transaction ,
uncles [ ] * eth_types . Header , receipts [ ] * eth_types . Receipt ) ( * eth_types . Block , error ) {
return nil , nil
}
func ( occ * OurChainContext ) Prepare ( chain eth_consensus . ChainReader , header * eth_types . Header ) error {
2018-06-25 16:31:20 +00:00
return nil
}
2018-06-26 11:41:29 +00:00
func ( occ * OurChainContext ) Seal ( chain eth_consensus . ChainReader , block * eth_types . Block , stop <- chan struct { } ) ( * eth_types . Block , error ) {
2018-06-25 16:31:20 +00:00
return nil , nil
}
2018-06-26 11:41:29 +00:00
func ( occ * OurChainContext ) VerifyHeader ( chain eth_consensus . ChainReader , header * eth_types . Header , seal bool ) error {
2018-06-25 16:31:20 +00:00
return nil
}
2018-06-26 11:41:29 +00:00
func ( occ * OurChainContext ) VerifyHeaders ( chain eth_consensus . ChainReader , headers [ ] * eth_types . Header , seals [ ] bool ) ( chan <- struct { } , <- chan error ) {
2018-06-25 16:31:20 +00:00
return nil , nil
}
2018-06-26 11:41:29 +00:00
func ( occ * OurChainContext ) VerifySeal ( chain eth_consensus . ChainReader , header * eth_types . Header ) error {
return nil
}
func ( occ * OurChainContext ) VerifyUncles ( chain eth_consensus . ChainReader , block * eth_types . Block ) error {
return nil
}
// Implementation of ethdb.Database and ethdb.Batch from go-ethereum
type OurEthDb struct {
codeDb dbm . DB
}
func ( oedb * OurEthDb ) Put ( key [ ] byte , value [ ] byte ) error {
oedb . codeDb . Set ( key , value )
2018-06-25 16:31:20 +00:00
return nil
}
2018-06-26 11:41:29 +00:00
func ( oedb * OurEthDb ) Get ( key [ ] byte ) ( [ ] byte , error ) {
2018-06-25 16:31:20 +00:00
return nil , nil
}
2018-06-26 11:41:29 +00:00
func ( oedb * OurEthDb ) Has ( key [ ] byte ) ( bool , error ) {
return false , nil
}
func ( oedb * OurEthDb ) Delete ( key [ ] byte ) error {
2018-06-25 16:31:20 +00:00
return nil
}
2018-06-26 11:41:29 +00:00
func ( oedb * OurEthDb ) Close ( ) {
}
func ( oedb * OurEthDb ) NewBatch ( ) eth_ethdb . Batch {
return oedb
}
func ( oedb * OurEthDb ) ValueSize ( ) int {
return 0
}
func ( oedb * OurEthDb ) Write ( ) error {
2018-06-25 16:31:20 +00:00
return nil
}
2018-06-26 11:41:29 +00:00
func ( oedb * OurEthDb ) Reset ( ) {
}
2018-06-13 09:56:01 +00:00
func main ( ) {
2018-06-15 10:05:12 +00:00
stateDb := dbm . NewDB ( "state" /* name */ , dbm . MemDBBackend , "" /* dir */ )
2018-06-18 15:33:07 +00:00
codeDb := dbm . NewDB ( "code" /* name */ , dbm . MemDBBackend , "" /* dir */ )
2018-06-24 21:58:28 +00:00
d , err := OurNewDatabase ( stateDb , codeDb )
2018-06-18 15:33:07 +00:00
if err != nil {
panic ( err )
}
fmt . Printf ( "Instantiating state.StateDB\n" )
// With empty root hash, i.e. empty state
statedb , err := eth_state . New ( eth_common . Hash { } , d )
if err != nil {
panic ( err )
}
2018-06-18 21:10:29 +00:00
g := eth_core . DefaultGenesisBlock ( )
for addr , account := range g . Alloc {
statedb . AddBalance ( addr , account . Balance )
statedb . SetCode ( addr , account . Code )
statedb . SetNonce ( addr , account . Nonce )
for key , value := range account . Storage {
statedb . SetState ( addr , key , value )
}
}
// One of the genesis account having 200 ETH
b := statedb . GetBalance ( eth_common . HexToAddress ( "0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0" ) )
2018-06-18 15:33:07 +00:00
fmt . Printf ( "Balance: %s\n" , b )
2018-06-19 11:32:26 +00:00
genesis_root , err := statedb . Commit ( false /* deleteEmptyObjects */ )
2018-06-18 21:10:29 +00:00
if err != nil {
panic ( err )
}
2018-06-24 21:58:28 +00:00
commitID := d . stateStore . Commit ( )
fmt . Printf ( "CommitID after genesis: %v\n" , commitID )
2018-06-19 11:32:26 +00:00
fmt . Printf ( "Genesis state root hash: %x\n" , genesis_root [ : ] )
// File with blockchain data exported from geth by using "geth expordb" command
input , err := os . Open ( "/Users/alexeyakhunov/mygit/blockchain" )
2018-06-18 21:10:29 +00:00
if err != nil {
panic ( err )
}
2018-06-19 11:32:26 +00:00
defer input . Close ( )
// Ethereum mainnet config
chainConfig := eth_params . MainnetChainConfig
stream := eth_rlp . NewStream ( input , 0 )
var block eth_types . Block
n := 0
var root500 eth_common . Hash // Root hash after block 500
var root501 eth_common . Hash // Root hash after block 501
2018-06-20 04:49:52 +00:00
prev_root := genesis_root
d . tracing = true
2018-06-26 11:41:29 +00:00
chainContext := & OurChainContext { }
2018-07-02 22:28:39 +00:00
vmConfig := eth_vm . Config { }
2018-06-19 11:32:26 +00:00
for {
if err = stream . Decode ( & block ) ; err == io . EOF {
err = nil // Clear it
break
} else if err != nil {
2018-06-26 11:41:29 +00:00
panic ( fmt . Errorf ( "at block %d: %v" , block . NumberU64 ( ) , err ) )
2018-06-19 11:32:26 +00:00
}
// don't import first block
if block . NumberU64 ( ) == 0 {
continue
}
header := block . Header ( )
2018-06-26 11:41:29 +00:00
chainContext . coinbase = header . Coinbase
2018-06-25 15:27:05 +00:00
statedb , err := eth_state . New ( prev_root , d )
2018-06-20 04:49:52 +00:00
if err != nil {
2018-06-26 11:41:29 +00:00
panic ( fmt . Errorf ( "at block %d: %v" , block . NumberU64 ( ) , err ) )
2018-06-20 04:49:52 +00:00
}
2018-06-25 16:31:20 +00:00
var (
receipts eth_types . Receipts
usedGas = new ( uint64 )
allLogs [ ] * eth_types . Log
gp = new ( eth_core . GasPool ) . AddGas ( block . GasLimit ( ) )
)
if chainConfig . DAOForkSupport && chainConfig . DAOForkBlock != nil && chainConfig . DAOForkBlock . Cmp ( block . Number ( ) ) == 0 {
eth_misc . ApplyDAOHardFork ( statedb )
}
for i , tx := range block . Transactions ( ) {
statedb . Prepare ( tx . Hash ( ) , block . Hash ( ) , i )
2018-07-02 22:28:39 +00:00
var h eth_common . Hash = tx . Hash ( )
if bytes . Equal ( h [ : ] , eth_common . FromHex ( "0xc438cfcc3b74a28741bda361032f1c6362c34aa0e1cedff693f31ec7d6a12717" ) ) {
vmConfig . Tracer = eth_vm . NewStructLogger ( & eth_vm . LogConfig { } )
vmConfig . Debug = true
}
receipt , _ , err := eth_core . ApplyTransaction ( chainConfig , chainContext , nil , gp , statedb , header , tx , usedGas , vmConfig )
if vmConfig . Tracer != nil {
w , err := os . Create ( "structlogs.txt" )
if err != nil {
panic ( err )
}
encoder := json . NewEncoder ( w )
logs := FormatLogs ( vmConfig . Tracer . ( * eth_vm . StructLogger ) . StructLogs ( ) )
if err := encoder . Encode ( logs ) ; err != nil {
panic ( err )
}
if err := w . Close ( ) ; err != nil {
panic ( err )
}
vmConfig . Debug = false
vmConfig . Tracer = nil
}
2018-06-25 16:31:20 +00:00
if err != nil {
2018-06-26 11:41:29 +00:00
panic ( fmt . Errorf ( "at block %d, tx %x: %v" , block . NumberU64 ( ) , tx . Hash ( ) , err ) )
2018-06-25 16:31:20 +00:00
}
receipts = append ( receipts , receipt )
allLogs = append ( allLogs , receipt . Logs ... )
}
2018-06-19 11:32:26 +00:00
// Apply mining rewards to the statedb
accumulateRewards ( chainConfig , statedb , header , block . Uncles ( ) )
// Commit block
2018-06-24 21:58:28 +00:00
prev_root , err = statedb . Commit ( chainConfig . IsEIP158 ( block . Number ( ) ) /* deleteEmptyObjects */ )
2018-06-19 11:32:26 +00:00
if err != nil {
2018-06-26 11:41:29 +00:00
panic ( fmt . Errorf ( "at block %d: %v" , block . NumberU64 ( ) , err ) )
2018-06-19 11:32:26 +00:00
}
2018-07-03 16:30:54 +00:00
//fmt.Printf("State root after block %d: %x\n", block.NumberU64(), prev_root)
2018-06-24 21:58:28 +00:00
d . stateStore . Commit ( )
//fmt.Printf("CommitID after block %d: %v\n", block.NumberU64(), commitID)
2018-06-20 04:49:52 +00:00
switch block . NumberU64 ( ) {
2018-06-24 21:58:28 +00:00
case 500 :
2018-06-20 04:49:52 +00:00
root500 = prev_root
2018-06-24 21:58:28 +00:00
case 501 :
2018-06-20 04:49:52 +00:00
root501 = prev_root
2018-06-19 11:32:26 +00:00
}
n ++
2018-06-25 16:31:20 +00:00
if n % 10000 == 0 {
fmt . Printf ( "Processed %d blocks\n" , n )
}
if n >= 100000 {
2018-06-19 11:32:26 +00:00
break
}
}
fmt . Printf ( "Processed %d blocks\n" , n )
2018-06-20 04:49:52 +00:00
d . tracing = true
2018-06-19 11:32:26 +00:00
genesis_state , err := eth_state . New ( genesis_root , d )
fmt . Printf ( "Balance of one of the genesis investors: %s\n" , genesis_state . GetBalance ( eth_common . HexToAddress ( "0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0" ) ) )
2018-06-25 15:27:05 +00:00
//miner501 := eth_common.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D") // Miner of the block 501
2018-06-19 11:32:26 +00:00
// Try to create a new statedb from root of the block 500
2018-06-24 21:58:28 +00:00
fmt . Printf ( "root500: %x\n" , root500 [ : ] )
2018-06-19 11:32:26 +00:00
state500 , err := eth_state . New ( root500 , d )
if err != nil {
panic ( err )
}
miner501_balance_at_500 := state500 . GetBalance ( miner501 )
state501 , err := eth_state . New ( root501 , d )
if err != nil {
panic ( err )
}
miner501_balance_at_501 := state501 . GetBalance ( miner501 )
2018-06-25 15:27:05 +00:00
fmt . Printf ( "Investor's balance after block 500: %d\n" , state500 . GetBalance ( eth_common . HexToAddress ( "0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0" ) ) )
2018-06-19 11:32:26 +00:00
fmt . Printf ( "Miner of block 501's balance after block 500: %d\n" , miner501_balance_at_500 )
fmt . Printf ( "Miner of block 501's balance after block 501: %d\n" , miner501_balance_at_501 )
2018-06-13 09:56:01 +00:00
}