diff --git a/common/heights.go b/common/heights.go index a5c1c418..2ce6639c 100644 --- a/common/heights.go +++ b/common/heights.go @@ -34,7 +34,10 @@ const HeightSupportThetaTokenInSmartContract uint64 = 13123789 // approximate ti const HeightValidatorStakeChangedTo200K uint64 = 14526120 // approximate time: 12pm Mar 14, 2022 PT // HeightSupportWrappedTheta specifies the block height to support wrapped Theta -const HeightSupportWrappedTheta uint64 = 1000000000 +const HeightSupportWrappedTheta uint64 = 17285755 // approximate time: 7pm Sep 28, 2022 PT + +// HeightEnableMetachainSupport specifies the block height to enable Theta Metachain support (i.e. Mainnet 4.0) +const HeightEnableMetachainSupport uint64 = 1000000000 // CheckpointInterval defines the interval between checkpoints. const CheckpointInterval = int64(100) diff --git a/common/result/error_code.go b/common/result/error_code.go index c579adb4..a0596e4d 100644 --- a/common/result/error_code.go +++ b/common/result/error_code.go @@ -5,6 +5,8 @@ type ErrorCode int const ( CodeOK ErrorCode = 0 + CodeUndecided ErrorCode = 2 + // Common Errors CodeGenericError ErrorCode = 100000 CodeInvalidSignature ErrorCode = 100001 @@ -29,11 +31,12 @@ const ( CodeUnauthorizedToUpdateSplitRule ErrorCode = 104001 // SmartContract Errors - CodeEVMError ErrorCode = 105001 - CodeInvalidValueToTransfer ErrorCode = 105002 - CodeInvalidGasPrice ErrorCode = 105003 - CodeFeeLimitTooHigh ErrorCode = 105004 - CodeInvalidGasLimit ErrorCode = 105005 + CodeEVMError ErrorCode = 105001 + CodeInvalidValueToTransfer ErrorCode = 105002 + CodeInvalidGasPrice ErrorCode = 105003 + CodeFeeLimitTooHigh ErrorCode = 105004 + CodeInvalidGasLimit ErrorCode = 105005 + CodeDoNotSupportNativeThetaInSubchain ErrorCode = 105006 // Stake Deposit/Withdrawal Errors CodeInvalidStakePurpose ErrorCode = 106001 diff --git a/common/result/result.go b/common/result/result.go index 20acd550..27baa030 100644 --- a/common/result/result.go +++ b/common/result/result.go @@ -16,6 +16,11 @@ func (res Result) IsOK() bool { return res.Code == CodeOK } +// IsUndecided indicates if the execution succeeded +func (res Result) IsUndecided() bool { + return res.Code == CodeUndecided +} + // IsError indicates if the execution results in an error func (res Result) IsError() bool { return res.Code != CodeOK @@ -58,6 +63,18 @@ func OKWith(info Info) Result { return res } +// UndecidedWith returns a success result with extra information +func UndecidedWith(info Info) Result { + res := Result{ + Code: CodeUndecided, + Info: make(Info), + } + for k, v := range info { + res.Info[k] = v + } + return res +} + // Error returns an error result func Error(msgFormat string, a ...interface{}) Result { msg := fmt.Sprintf(msgFormat, a...) diff --git a/common/size.go b/common/size.go new file mode 100644 index 00000000..097b6304 --- /dev/null +++ b/common/size.go @@ -0,0 +1,56 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package common + +import ( + "fmt" +) + +// StorageSize is a wrapper around a float value that supports user friendly +// formatting. +type StorageSize float64 + +// String implements the stringer interface. +func (s StorageSize) String() string { + if s > 1099511627776 { + return fmt.Sprintf("%.2f TiB", s/1099511627776) + } else if s > 1073741824 { + return fmt.Sprintf("%.2f GiB", s/1073741824) + } else if s > 1048576 { + return fmt.Sprintf("%.2f MiB", s/1048576) + } else if s > 1024 { + return fmt.Sprintf("%.2f KiB", s/1024) + } else { + return fmt.Sprintf("%.2f B", s) + } +} + +// TerminalString implements log.TerminalStringer, formatting a string for console +// output during logging. +func (s StorageSize) TerminalString() string { + if s > 1099511627776 { + return fmt.Sprintf("%.2fTiB", s/1099511627776) + } else if s > 1073741824 { + return fmt.Sprintf("%.2fGiB", s/1073741824) + } else if s > 1048576 { + return fmt.Sprintf("%.2fMiB", s/1048576) + } else if s > 1024 { + return fmt.Sprintf("%.2fKiB", s/1024) + } else { + return fmt.Sprintf("%.2fB", s) + } +} diff --git a/core/genesis.go b/core/genesis.go index fb02b44f..1ec9196c 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -6,4 +6,8 @@ const ( MainnetGenesisBlockHash = "0xd8836c6cf3c3ccea0b015b4ed0f9efb0ffe6254db793a515843c9d0f68cbab65" GenesisBlockHeight = uint64(0) + + SubchainChainIDPrefix = "tsub" + + MinSubchainID = 1000 ) diff --git a/ledger/execution/tx_smart_contract.go b/ledger/execution/tx_smart_contract.go index 6a394712..09cb7624 100644 --- a/ledger/execution/tx_smart_contract.go +++ b/ledger/execution/tx_smart_contract.go @@ -161,7 +161,9 @@ func (exec *SmartContractTxExecutor) process(chainID string, view *st.StoreView, // Note: for contract deployment, vm.Execute() might transfer coins from the fromAccount to the // deployed smart contract. Thus, we should call vm.Execute() before calling getInput(). // Otherwise, the fromAccount returned by getInput() will have incorrect balance. - evmRet, contractAddr, gasUsed, evmErr := vm.Execute(exec.state.ParentBlock(), tx, view) + pb := exec.state.ParentBlock() + parentBlockInfo := vm.NewBlockInfo(pb.Height, pb.Timestamp, pb.ChainID) + evmRet, contractAddr, gasUsed, evmErr := vm.Execute(parentBlockInfo, tx, view) fromAddress := tx.From.Address fromAccount, success := getInput(view, tx.From) diff --git a/ledger/types/tx.go b/ledger/types/tx.go index 790ff0e9..4bc8e5dd 100644 --- a/ledger/types/tx.go +++ b/ledger/types/tx.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "math/big" + "strconv" + "strings" "sync" log "github.com/sirupsen/logrus" @@ -1101,7 +1103,20 @@ func MapChainID(chainIDStr string, blockHeight uint64) *big.Int { // For replay attack protection, should NOT use the same chainID as Ethereum chainID := big.NewInt(1).Add(big.NewInt(CHAIN_ID_OFFSET), chainIDWithoutOffset) - return chainID + + if blockHeight < common.HeightEnableMetachainSupport { + return chainID + } else { + // attempt to extract the IDs for subchains. ChainID in the form of tsub[1-9][0-9]* is considered as a Theta Subchain + subchainID, err := extractSubchainID(chainIDStr) // no need to add offset to the subchainIDs + if err == nil { + // this is a subchain chainID + return subchainID + } else { + // fallback + return chainID + } + } } func mapChainIDWithoutOffset(chainIDStr string) *big.Int { @@ -1120,3 +1135,32 @@ func mapChainIDWithoutOffset(chainIDStr string) *big.Int { chainIDBigInt := new(big.Int).Abs(crypto.Keccak256Hash(common.Bytes(chainIDStr)).Big()) // all other chainIDs return chainIDBigInt } + +// Subchain chainID should have the form tsub[1-9][0-9]* +func extractSubchainID(chainIDStr string) (*big.Int, error) { + if !strings.HasPrefix(chainIDStr, core.SubchainChainIDPrefix) { + return nil, fmt.Errorf("invalid subchain ID prefix: %v", chainIDStr) + } + + if len(chainIDStr) < 5 { + return nil, fmt.Errorf("subchain ID too short: %v", chainIDStr) + } + + leadingDigit := chainIDStr[4] + if leadingDigit == byte('0') { + return nil, fmt.Errorf("the leading digit of the subchain ID should not be zero: %v", chainIDStr) + } + + chainIDIntStr := chainIDStr[4:] + chainID, err := strconv.Atoi(chainIDIntStr) + if err != nil { + return nil, err + } + + // we require subchain ID to be at least core.MinSubchainID, so it doesn't overlap with the ID of the mainchain + if chainID < core.MinSubchainID { + return nil, fmt.Errorf("subchain ID too small: %v", chainIDStr) + } + + return big.NewInt(int64(chainID)), nil +} diff --git a/ledger/types/tx_test.go b/ledger/types/tx_test.go index 4a6508d2..412f6257 100644 --- a/ledger/types/tx_test.go +++ b/ledger/types/tx_test.go @@ -18,6 +18,103 @@ import ( var chainID string = "test_chain" +func TestChainID(t *testing.T) { + + // + // The IDs for the main chains + // + + chainIDStrMainnet := "mainnet" + chainIDStr := chainIDStrMainnet + chainID := MapChainID(chainIDStr, common.HeightRPCCompatibility+1) + assert.True(t, chainID.Cmp(big.NewInt(361)) == 0, "mapped chainID for %v is %v", chainIDStr, chainID) + fmt.Printf("extracted chainID for %v: %v\n", chainIDStr, chainID) + + chainIDStrTestnet := "testnet" + chainIDStr = chainIDStrTestnet + chainID = MapChainID(chainIDStr, common.HeightRPCCompatibility+1) + assert.True(t, chainID.Cmp(big.NewInt(365)) == 0, "mapped chainID for %v is %v", chainIDStr, chainID) + fmt.Printf("extracted chainID for %v: %v\n", chainIDStr, chainID) + + chainIDStrPrivatenet := "privatenet" + chainIDStr = chainIDStrPrivatenet + chainID = MapChainID(chainIDStr, common.HeightRPCCompatibility+1) + assert.True(t, chainID.Cmp(big.NewInt(366)) == 0, "mapped chainID for %v is %v", chainIDStr, chainID) + fmt.Printf("extracted chainID for %v: %v\n", chainIDStr, chainID) + + // + // Invalid subchain IDs + // + + var err error + invalidSubchainID0 := "tsub_881" // with an extra underscore + chainIDStr = invalidSubchainID0 + chainID, err = extractSubchainID(chainIDStr) + assert.True(t, err != nil, "should be an invalid subchain ID: %v", chainIDStr) + chainID = MapChainID(chainIDStr, common.HeightEnableMetachainSupport+1) + assert.True(t, chainID.Cmp(big.NewInt(881)) != 0, "mapped chainID for %v is %v", chainIDStr, chainID) + fmt.Printf("extracted chainID for %v: %v\n", chainIDStr, chainID) + + invalidSubchainID1 := "ts881" + chainIDStr = invalidSubchainID1 + chainID, err = extractSubchainID(chainIDStr) + assert.True(t, err != nil, "should be an invalid subchain ID: %v", chainIDStr) + chainID = MapChainID(chainIDStr, common.HeightEnableMetachainSupport+1) + assert.True(t, chainID.Cmp(big.NewInt(881)) != 0, "mapped chainID for %v is %v", chainIDStr, chainID) + fmt.Printf("extracted chainID for %v: %v\n", chainIDStr, chainID) + + invalidSubchainID2 := "tsub09998" // leading digit should not be 0 + chainIDStr = invalidSubchainID2 + chainID, err = extractSubchainID(chainIDStr) + assert.True(t, err != nil, "should be an invalid subchain ID: %v", chainIDStr) + chainID = MapChainID(chainIDStr, common.HeightEnableMetachainSupport+1) + assert.True(t, chainID.Cmp(big.NewInt(9998)) != 0, "mapped chainID for %v is %v", chainIDStr, chainID) + fmt.Printf("extracted chainID for %v: %v\n", chainIDStr, chainID) + + invalidSubchainID3 := "tsubabc9" // hex not allowed in chainID + chainIDStr = invalidSubchainID3 + chainID, err = extractSubchainID(chainIDStr) + assert.True(t, err != nil, "should be an invalid subchain ID: %v", chainIDStr) + chainID = MapChainID(chainIDStr, common.HeightEnableMetachainSupport+1) + assert.True(t, chainID.Cmp(big.NewInt(43977)) != 0, "mapped chainID for %v is %v", chainIDStr, chainID) + fmt.Printf("extracted chainID for %v: %v\n", chainIDStr, chainID) + + invalidSubchainID4 := "tsub999" // subchain ID needs to be at least 1000 + chainIDStr = invalidSubchainID4 + chainID, err = extractSubchainID(chainIDStr) + assert.True(t, err != nil, "should be an invalid subchain ID: %v", chainIDStr) + chainID = MapChainID(chainIDStr, common.HeightEnableMetachainSupport+1) + assert.True(t, chainID.Cmp(big.NewInt(999)) != 0, "mapped chainID for %v is %v", chainIDStr, chainID) + fmt.Printf("extracted chainID for %v: %v\n", chainIDStr, chainID) + + invalidSubchainID5 := "tsub34535873957238957239573985728957283957923528357238572893572983457238957238495893" // subchain ID needs to be smaller than uint64.max + chainIDStr = invalidSubchainID5 + chainID, err = extractSubchainID(chainIDStr) + assert.True(t, err != nil, "should be an invalid subchain ID: %v", chainIDStr) + chainID = MapChainID(chainIDStr, common.HeightEnableMetachainSupport+1) + cid, _ := big.NewInt(0).SetString("34535873957238957239573985728957283957923528357238572893572983457238957238495893", 10) + assert.True(t, chainID.Cmp(cid) != 0, "mapped chainID for %v is %v", chainIDStr, chainID) + fmt.Printf("extracted chainID for %v: %v\n", chainIDStr, chainID) + + // + // Valid subchain IDs + // + + validSubchainID1 := "tsub1991" + chainIDStr = validSubchainID1 + chainID = MapChainID(chainIDStr, common.HeightEnableMetachainSupport+1) + assert.True(t, chainID.Cmp(big.NewInt(1991)) == 0, "mapped chainID for %v is %v", chainIDStr, chainID) + fmt.Printf("extracted chainID for %v: %v\n", chainIDStr, chainID) + + validSubchainID2 := "tsub4546325235" + chainIDStr = validSubchainID2 + chainID = MapChainID(chainIDStr, common.HeightEnableMetachainSupport+1) + assert.True(t, chainID.Cmp(big.NewInt(4546325235)) == 0, "mapped chainID for %v is %v", chainIDStr, chainID) + fmt.Printf("extracted chainID for %v: %v\n", chainIDStr, chainID) + + // assert.True(t, false) +} + func TestCoinbaseTxSignable(t *testing.T) { chainID := "test_chain_id" va1PrivAcc := PrivAccountFromSecret("validator1") diff --git a/ledger/vm/exec.go b/ledger/vm/exec.go index 4e5163a7..31339d3f 100644 --- a/ledger/vm/exec.go +++ b/ledger/vm/exec.go @@ -5,14 +5,26 @@ import ( "math/big" "github.com/thetatoken/theta/common" - "github.com/thetatoken/theta/core" - "github.com/thetatoken/theta/ledger/state" "github.com/thetatoken/theta/ledger/types" "github.com/thetatoken/theta/ledger/vm/params" ) +type BlockInfo struct { + Height uint64 + Timestamp *big.Int + ChainID string +} + +func NewBlockInfo(height uint64, timestamp *big.Int, chainID string) *BlockInfo { + return &BlockInfo{ + Height: height, + Timestamp: timestamp, + ChainID: chainID, + } +} + // Execute executes the given smart contract -func Execute(parentBlock *core.Block, tx *types.SmartContractTx, storeView *state.StoreView) (evmRet common.Bytes, +func Execute(parentBlockInfo *BlockInfo, tx *types.SmartContractTx, statedb StateDB) (evmRet common.Bytes, contractAddr common.Address, gasUsed uint64, evmErr error) { context := Context{ CanTransfer: CanTransfer, @@ -20,16 +32,16 @@ func Execute(parentBlock *core.Block, tx *types.SmartContractTx, storeView *stat Origin: tx.From.Address, GasPrice: tx.GasPrice, GasLimit: tx.GasLimit, - BlockNumber: new(big.Int).SetUint64(parentBlock.Height + 1), - Time: parentBlock.Timestamp, + BlockNumber: new(big.Int).SetUint64(parentBlockInfo.Height + 1), + Time: parentBlockInfo.Timestamp, Difficulty: new(big.Int).SetInt64(0), } - chainIDBigInt := types.MapChainID(parentBlock.ChainID, context.BlockNumber.Uint64()) + chainIDBigInt := types.MapChainID(parentBlockInfo.ChainID, context.BlockNumber.Uint64()) chainConfig := ¶ms.ChainConfig{ ChainID: chainIDBigInt, } config := Config{} - evm := NewEVM(context, storeView, chainConfig, config) + evm := NewEVM(context, statedb, chainConfig, config) value := tx.From.Coins.TFuelWei if value == nil { @@ -49,7 +61,10 @@ func Execute(parentBlock *core.Block, tx *types.SmartContractTx, storeView *stat // if gasLimit > maxGasLimit { // return common.Bytes{}, common.Address{}, 0, ErrInvalidGasLimit // } - blockHeight := storeView.Height() + 1 + + // blockHeight := storeView.Height() + 1 + blockHeight := statedb.GetBlockHeight() // GetBlockHeight() returns storeView.Height() + 1 so it is equivalent to the above commented line + maxGasLimit := types.GetMaxGasLimit(blockHeight) if new(big.Int).SetUint64(gasLimit).Cmp(maxGasLimit) > 0 { return common.Bytes{}, common.Address{}, 0, ErrInvalidGasLimit diff --git a/ledger/vm/params/protocol_params.go b/ledger/vm/params/protocol_params.go index 9c860578..af0fa387 100644 --- a/ledger/vm/params/protocol_params.go +++ b/ledger/vm/params/protocol_params.go @@ -69,7 +69,8 @@ const ( MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. TxDataNonZeroGas uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. - MaxCodeSize = 24576 // Maximum bytecode to permit for a contract + MaxCodeSize = 24576 // Maximum bytecode to permit for a contract + MaxCodeSizeForMetachain = 49152 // Maximum bytecode to permit for a contract // Precompiled contract gas prices diff --git a/ledger/vm/vm.go b/ledger/vm/vm.go index 913565aa..47d28cf9 100644 --- a/ledger/vm/vm.go +++ b/ledger/vm/vm.go @@ -624,7 +624,12 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ret, err := run(evm, contract, nil, false) // check whether the max code size has been exceeded - maxCodeSizeExceeded := len(ret) > params.MaxCodeSize + maxCodeSize := params.MaxCodeSize + if blockHeight >= common.HeightEnableMetachainSupport { + maxCodeSize = params.MaxCodeSizeForMetachain + } + + maxCodeSizeExceeded := len(ret) > maxCodeSize // if the contract creation ran successfully and no errors were returned // calculate the gas required to store the code. If the code could not // be stored due to not enough gas set an error and let it be handled diff --git a/rpc/call.go b/rpc/call.go index 08d806b3..32739faa 100644 --- a/rpc/call.go +++ b/rpc/call.go @@ -52,8 +52,9 @@ func (t *ThetaRPCService) CallSmartContract(args *CallSmartContractArgs, result return fmt.Errorf("Failed to parse SmartContractTx: %v", args.SctxBytes) } - parentBlock := t.ledger.State().ParentBlock() - vmRet, contractAddr, gasUsed, vmErr := vm.Execute(parentBlock, sctx, ledgerState) + pb := t.ledger.State().ParentBlock() + parentBlockInfo := vm.NewBlockInfo(pb.Height, pb.Timestamp, pb.ChainID) + vmRet, contractAddr, gasUsed, vmErr := vm.Execute(parentBlockInfo, sctx, ledgerState) ledgerState.Save() result.VmReturn = hex.EncodeToString(vmRet) diff --git a/version/version_number.txt b/version/version_number.txt index 18091983..0c89fc92 100644 --- a/version/version_number.txt +++ b/version/version_number.txt @@ -1 +1 @@ -3.4.0 +4.0.0 \ No newline at end of file