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

[evm] EIP-1153 enable transient storage feature #4214

Merged
merged 19 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
6 changes: 6 additions & 0 deletions action/protocol/execution/evm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,12 @@ func getChainConfig(g genesis.Blockchain, height uint64, id uint32, getBlockTime
}
sumatraTimestamp := (uint64)(sumatraTime.Unix())
chainConfig.ShanghaiTime = &sumatraTimestamp
upernavikTime, err := getBlockTime(g.UpernavikBlockHeight)
if err != nil {
return nil, err
}
upernavikTimestamp := (uint64)(upernavikTime.Unix())
chainConfig.CancunTime = &upernavikTimestamp
return &chainConfig, nil
}

Expand Down
2 changes: 1 addition & 1 deletion action/protocol/execution/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ func TestConstantinople(t *testing.T) {
},
{
"io1pcg2ja9krrhujpazswgz77ss46xgt88afqlk6y",
1261440000, // = 200*365*24*3600/5, around 200 years later
39275560,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it must less genesis default UpernavikBlockHeight

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check past PR, you need to break it to 2 parts

after Tsunami - Upernavik {
}
after Upernavik {
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still not fixed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix now

},
}
now := time.Now()
Expand Down
114 changes: 76 additions & 38 deletions action/protocol/execution/evm/evmstatedbadapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,27 @@

// StateDBAdapter represents the state db adapter for evm to access iotx blockchain
StateDBAdapter struct {
sm protocol.StateManager
logs []*action.Log
transactionLogs []*action.TransactionLog
err error
blockHeight uint64
executionHash hash.Hash256
lastAddBalanceAddr string
lastAddBalanceAmount *big.Int
refund uint64
refundSnapshot map[int]uint64
cachedContract contractMap
contractSnapshot map[int]contractMap // snapshots of contracts
suicided deleteAccount // account/contract calling Suicide
suicideSnapshot map[int]deleteAccount // snapshots of suicide accounts
preimages preimageMap
preimageSnapshot map[int]preimageMap
accessList *accessList // per-transaction access list
accessListSnapshot map[int]*accessList
sm protocol.StateManager
logs []*action.Log
transactionLogs []*action.TransactionLog
err error
blockHeight uint64
executionHash hash.Hash256
lastAddBalanceAddr string
lastAddBalanceAmount *big.Int
refund uint64
refundSnapshot map[int]uint64
cachedContract contractMap
contractSnapshot map[int]contractMap // snapshots of contracts
suicided deleteAccount // account/contract calling Suicide
suicideSnapshot map[int]deleteAccount // snapshots of suicide accounts
preimages preimageMap
preimageSnapshot map[int]preimageMap
accessList *accessList // per-transaction access list
accessListSnapshot map[int]*accessList
// Transient storage
transientStorage transientStorage
transientStorageSnapshot map[int]transientStorage
logsSnapshot map[int]int // logs is an array, save len(logs) at time of snapshot suffices
txLogsSnapshot map[int]int
notFixTopicCopyBug bool
Expand Down Expand Up @@ -170,23 +173,25 @@
opts ...StateDBAdapterOption,
) (*StateDBAdapter, error) {
s := &StateDBAdapter{
sm: sm,
logs: []*action.Log{},
err: nil,
blockHeight: blockHeight,
executionHash: executionHash,
lastAddBalanceAmount: new(big.Int),
refundSnapshot: make(map[int]uint64),
cachedContract: make(contractMap),
contractSnapshot: make(map[int]contractMap),
suicided: make(deleteAccount),
suicideSnapshot: make(map[int]deleteAccount),
preimages: make(preimageMap),
preimageSnapshot: make(map[int]preimageMap),
accessList: newAccessList(),
accessListSnapshot: make(map[int]*accessList),
logsSnapshot: make(map[int]int),
txLogsSnapshot: make(map[int]int),
sm: sm,
logs: []*action.Log{},
err: nil,
blockHeight: blockHeight,
executionHash: executionHash,
lastAddBalanceAmount: new(big.Int),
refundSnapshot: make(map[int]uint64),
cachedContract: make(contractMap),
contractSnapshot: make(map[int]contractMap),
suicided: make(deleteAccount),
suicideSnapshot: make(map[int]deleteAccount),
preimages: make(preimageMap),
preimageSnapshot: make(map[int]preimageMap),
accessList: newAccessList(),
accessListSnapshot: make(map[int]*accessList),
transientStorage: newTransientStorage(),
transientStorageSnapshot: make(map[int]transientStorage),
logsSnapshot: make(map[int]int),
txLogsSnapshot: make(map[int]int),
}
for _, opt := range opts {
if err := opt(s); err != nil {
Expand Down Expand Up @@ -478,13 +483,22 @@

// SetTransientState sets transient storage for a given account
func (stateDB *StateDBAdapter) SetTransientState(addr common.Address, key, value common.Hash) {
log.S().Panic("SetTransientState not implemented")
prev := stateDB.GetTransientState(addr, key)
millken marked this conversation as resolved.
Show resolved Hide resolved
if prev == value {
return

Check warning on line 488 in action/protocol/execution/evm/evmstatedbadapter.go

View check run for this annotation

Codecov / codecov/patch

action/protocol/execution/evm/evmstatedbadapter.go#L488

Added line #L488 was not covered by tests
}
stateDB.setTransientState(addr, key, value)
}

// setTransientState is a lower level setter for transient storage. It
// is called during a revert to prevent modifications to the journal.
func (stateDB *StateDBAdapter) setTransientState(addr common.Address, key, value common.Hash) {
millken marked this conversation as resolved.
Show resolved Hide resolved
stateDB.transientStorage.Set(addr, key, value)
}

// GetTransientState gets transient storage for a given account.
func (stateDB *StateDBAdapter) GetTransientState(addr common.Address, key common.Hash) common.Hash {
log.S().Panic("GetTransientState not implemented")
return common.Hash{}
return stateDB.transientStorage.Get(addr, key)
}

// Selfdestruct6780 implements EIP-6780
Expand Down Expand Up @@ -530,6 +544,8 @@
if !rules.IsBerlin {
return
}
// Clear out any leftover from previous executions
stateDB.accessList = newAccessList()

Check warning on line 548 in action/protocol/execution/evm/evmstatedbadapter.go

View check run for this annotation

Codecov / codecov/patch

action/protocol/execution/evm/evmstatedbadapter.go#L548

Added line #L548 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not necessary? we are creating a new StateDBAdpator for each tx

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for safety, it's best to add

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the relationship with eip-1153?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for safety, it's best to add

for what kind of safety? We need to be clear what we want to achieve.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stateDB.AddAddressToAccessList(sender)
if dst != nil {
stateDB.AddAddressToAccessList(*dst)
Expand All @@ -547,6 +563,8 @@
if rules.IsShanghai { // EIP-3651: warm coinbase
stateDB.AddAddressToAccessList(coinbase)
}
// Reset transient storage at the beginning of transaction execution
stateDB.transientStorage = newTransientStorage()

Check warning on line 567 in action/protocol/execution/evm/evmstatedbadapter.go

View check run for this annotation

Codecov / codecov/patch

action/protocol/execution/evm/evmstatedbadapter.go#L567

Added line #L567 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing, not necessary

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is best to add

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discused, let's remove this, it is done in NewStateDBAdapter

}

// AddressInAccessList returns true if the given address is in the access list
Expand Down Expand Up @@ -592,6 +610,23 @@
stateDB.logError(err)
return
}
// restore transientStorage
tss, ok := stateDB.transientStorageSnapshot[snapshot]
if !ok {
millken marked this conversation as resolved.
Show resolved Hide resolved
log.L().Error("unexpected error: missing transient storage snapshot")
return
}
stateDB.transientStorage = tss
{
delete(stateDB.transientStorageSnapshot, snapshot)
for i := snapshot + 1; ; i++ {
if _, ok := stateDB.transientStorageSnapshot[i]; ok {
delete(stateDB.transientStorageSnapshot, i)
} else {
break
}
}
}
millken marked this conversation as resolved.
Show resolved Hide resolved
ds, ok := stateDB.suicideSnapshot[snapshot]
if !ok {
// this should not happen, b/c we save the suicide accounts on a successful return of Snapshot(), but check anyway
Expand Down Expand Up @@ -750,6 +785,7 @@
stateDB.preimageSnapshot[sn] = p
// save a copy of access list
stateDB.accessListSnapshot[sn] = stateDB.accessList.Copy()
stateDB.transientStorageSnapshot[sn] = stateDB.transientStorage.Copy()
millken marked this conversation as resolved.
Show resolved Hide resolved
return sn
}

Expand Down Expand Up @@ -1089,6 +1125,8 @@
stateDB.preimageSnapshot = make(map[int]preimageMap)
stateDB.accessList = newAccessList()
stateDB.accessListSnapshot = make(map[int]*accessList)
stateDB.transientStorage = newTransientStorage()
stateDB.transientStorageSnapshot = make(map[int]transientStorage)
stateDB.logsSnapshot = make(map[int]int)
stateDB.txLogsSnapshot = make(map[int]int)
stateDB.logs = []*action.Log{}
Expand Down
42 changes: 42 additions & 0 deletions action/protocol/execution/evm/evmstatedbadapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -934,3 +934,45 @@ func TestSortMap(t *testing.T) {
require.True(testFunc(t, sm))
})
}

func TestStateDBTransientStorage(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
sm, err := initMockStateManager(ctrl)
require.NoError(err)
var opts []StateDBAdapterOption
opts = append(opts,
NotFixTopicCopyBugOption(),
FixSnapshotOrderOption(),
)
state, err := NewStateDBAdapter(sm, 1, hash.ZeroHash256, opts...)
if err != nil {
t.Fatal(err)
}
key := common.Hash{0x01}
value := common.Hash{0x02}
addr := common.Address{}

sn := state.Snapshot()
state.SetTransientState(addr, key, value)
if exp, got := 0, sn; exp != got {
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
}
// the retrieved value should equal what was set
if got := state.GetTransientState(addr, key); got != value {
t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
}

// revert the transient state being set and then check that the
millken marked this conversation as resolved.
Show resolved Hide resolved
// value is now the empty hash
state.RevertToSnapshot(sn)
if got, exp := state.GetTransientState(addr, key), (common.Hash{}); exp != got {
t.Fatalf("transient storage mismatch: have %x, want %x", got, exp)
}

// reset transient state
state.SetTransientState(addr, key, value)
if got := state.GetTransientState(addr, key); got != value {
t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
}
}
10 changes: 10 additions & 0 deletions action/protocol/execution/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type (
IsIceland bool `json:"isIceland"`
IsLondon bool `json:"isLondon"`
IsShanghai bool `json:"isShanghai"`
IsCancun bool `json:"isCancun"`
}

Log struct {
Expand Down Expand Up @@ -437,6 +438,9 @@ func (sct *SmartContractTest) prepareBlockchain(
cfg.Genesis.Blockchain.SumatraBlockHeight = 0
cfg.Genesis.ActionGasLimit = 10000000
}
if sct.InitGenesis.IsCancun {
cfg.Genesis.Blockchain.UpernavikBlockHeight = 0
}
for _, expectedBalance := range sct.InitBalances {
cfg.Genesis.InitBalanceMap[expectedBalance.Account] = expectedBalance.Balance().String()
}
Expand Down Expand Up @@ -1321,6 +1325,12 @@ func TestShanghaiEVM(t *testing.T) {
})
}

func TestCancunEVM(t *testing.T) {
t.Run("eip1153-transientstorage", func(t *testing.T) {
NewSmartContractTest(t, "testdata-cancun/transientstorage.json")
})
}

func benchmarkHotContractWithFactory(b *testing.B, async bool) {
sct := SmartContractTest{
InitBalances: []ExpectedBalance{
Expand Down
49 changes: 49 additions & 0 deletions action/protocol/execution/testdata-cancun/transientstorage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"initGenesis": {
"isBering" : true,
"isIceland" : true,
"isLondon" : true,
"isShanghai" : true,
"isCancun": true
},
"initBalances": [{
"account": "io1mflp9m6hcgm2qcghchsdqj3z3eccrnekx9p0ms",
"rawBalance": "1000000000000000000000000000"
}],
"deployments": [{
"rawByteCode": "608060405234801561000f575f80fd5b506102898061001d5f395ff3fe608060405234801561000f575f80fd5b506004361061004a575f3560e01c80630198214f1461004e57806338cc48311461006a5780639c6f010c14610088578063f2c9ecd8146100b8575b5f80fd5b6100686004803603810190610063919061016a565b6100d6565b005b6100726100dd565b60405161007f91906101e7565b60405180910390f35b6100a2600480360381019061009d9190610200565b61010f565b6040516100af919061023a565b60405180910390f35b6100c0610119565b6040516100cd919061023a565b60405180910390f35b80825d5050565b5f6101006001600373ffffffffffffffffffffffffffffffffffffffff166100d6565b61010a600161010f565b905090565b5f815c9050919050565b5f6101255f60216100d6565b61012e5f61010f565b905090565b5f80fd5b5f819050919050565b61014981610137565b8114610153575f80fd5b50565b5f8135905061016481610140565b92915050565b5f80604083850312156101805761017f610133565b5b5f61018d85828601610156565b925050602061019e85828601610156565b9150509250929050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101d1826101a8565b9050919050565b6101e1816101c7565b82525050565b5f6020820190506101fa5f8301846101d8565b92915050565b5f6020828403121561021557610214610133565b5b5f61022284828501610156565b91505092915050565b61023481610137565b82525050565b5f60208201905061024d5f83018461022b565b9291505056fea2646970667358221220482553b58e8f517e223d2d9d4345c179916b57ace9585c2ff1965520d3a2221064736f6c63430008180033",
"rawPrivateKey": "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1",
"rawAmount": "0",
"rawGasLimit": 5000000,
"rawGasPrice": "0",
"rawExpectedGasConsumed": 207775,
"expectedStatus": 1,
"expectedBalances": [],
"comment": "deploy transientstore contract"
}],
"executions": [{
"rawPrivateKey": "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1",
"rawByteCode": "f2c9ecd8",
"rawAmount": "0",
"rawGasLimit": 1000000,
"rawGasPrice": "0",
"rawAccessList": [],
"rawExpectedGasConsumed": 11056,
"expectedStatus": 1,
"readOnly": true,
"rawReturnValue": "0000000000000000000000000000000000000000000000000000000000000021",
"comment": "call getNumber"
},{
"rawPrivateKey": "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1",
"rawByteCode": "38cc4831",
"rawAmount": "0",
"rawGasLimit": 1000000,
"rawGasPrice": "0",
"rawAccessList": [],
"rawExpectedGasConsumed": 11068,
"expectedStatus": 1,
"readOnly": true,
"rawReturnValue": "0000000000000000000000000000000000000000000000000000000000000003",
"comment": "call getAddress"
}]
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add newline

30 changes: 30 additions & 0 deletions action/protocol/execution/testdata-cancun/transientstorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This support was introduced with Solidity 0.8.24
pragma solidity 0.8.24;
// SPDX-License-Identifier: Unlicensed

contract TransientStorage {

// Sets a number in the transient storage
function getNumber() public returns (uint) {
tstore(0, uint(33));
return uint(tload(0));
}

// Sets an address in the transient storage
function getAddress() public returns (address) {
tstore(1, uint(uint160(address(3))));
return address(uint160(tload(1)));
}

function tstore(uint location, uint value) public {
assembly {
tstore(location, value)
}
}

function tload(uint location) public view returns (uint value) {
assembly {
value := tload(location)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transient storage is accessible to smart contracts via 2 new opcodes, TLOAD and TSTORE

refer: https://eips.ethereum.org/EIPS/eip-1153

}
}
millken marked this conversation as resolved.
Show resolved Hide resolved
Loading