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

Turing replica state #1286

Merged
merged 2 commits into from
Oct 30, 2023
Merged
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
29 changes: 28 additions & 1 deletion boba_examples/turing-hello-world/contracts/HelloTuring.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,36 @@ contract HelloTuring {
return product;
}

// Tests error handling when a contract tries to make more than one call
// per Tx, using the "multFloatNumbers" offchain handler.
// Multiple calls from the same stack depth are permitted for legacy reasons
// but should not be used in new code.
function callTwice(string memory _url, string memory a, string memory b, uint32 mode)
public returns (uint256) {

bytes memory encRequest;
bytes memory encResponse;

if (mode == 2) {
// Call from a different stack depth
HelloTuring(address(this)).callTwice(_url, a, b, 0);
} else if (mode == 1) {
// Call from same stack depth
encRequest = abi.encode(b);
encResponse = myHelper.TuringTxV2(_url, encRequest);
}

encRequest = abi.encode(a);
encResponse = myHelper.TuringTxV2(_url, encRequest);

uint256 product = abi.decode(encResponse, (uint256));
emit MultFloatNumbers(product);
return product;
}

// Tests a Turing method which returns a variable-length array.
// The parameters 'a' and 'b' are passed in the request, returing
// an array of 'b' elements each with value 'a'. This function
// an array of 'a' elements each with value 'b'. This function
// adds all of the returned values and returns a total of (a*b)
function multArray(string memory _url, uint256 a, uint256 b)
public {
Expand Down
76 changes: 66 additions & 10 deletions boba_examples/turing-hello-world/test/local-webserver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { BigNumber, Contract, ContractFactory, providers, Wallet, utils } from 'ethers'
import { ethers } from 'hardhat'
import { Contract, ContractFactory, providers, Wallet, utils } from 'ethers'
import chai, { expect } from 'chai'
import { solidity } from 'ethereum-waffle'
chai.use(solidity)
Expand Down Expand Up @@ -204,7 +203,7 @@ if (hre.network.name === "boba_local") {

it('Should fund your Turing helper contract in turingCredit', async () => {

const depositAmount = utils.parseEther('0.5')
const depositAmount = utils.parseEther('1.5')
const preBalance = await turingCredit.prepaidBalance(helper.address)

const bobaBalance = await L2BOBAToken.balanceOf(deployerWallet.address)
Expand Down Expand Up @@ -280,15 +279,26 @@ if (hre.network.name === "boba_local") {
}
})

it("should charge extra gas for L1 calldata storage", async() => {
const g1 = (await hello.estimateGas.multArray(urlStr2, 1, 10, gasOverride)).toNumber()
const g2 = (await hello.estimateGas.multArray(urlStr2, 101, 10, gasOverride)).toNumber()
it("should charge extra gas for L1 calldata storage", async() => {
const eg1 = (await hello.estimateGas.multArray(urlStr2, 1, 10, gasOverride)).toNumber()
let tx1 = await hello.multArray(urlStr2, 1, 10, gasOverride)
const res1 = await tx1.wait()
expect(res1).to.be.ok
const ag1 = res1.gasUsed.toNumber()

const eg2 = (await hello.estimateGas.multArray(urlStr2, 101, 10, gasOverride)).toNumber()
let tx2 = await hello.multArray(urlStr2, 101, 10, gasOverride)
const res2 = await tx2.wait()
expect(res2).to.be.ok
const ag2 = res2.gasUsed.toNumber()

// Larger calldata costs more gas inside the contract itself. We need to test for
// additional usage on top of this from the L1 calldata calculation. The exact value
// depends on the L1 gas price so this test doesn't look for a specific number
expect (g2 - g1).to.be.above(110000)
})
// depends on the L1 gas price so this test doesn't look for a specific number.
// Actual tx is a different code path than estimateGas so both are checked.
expect (eg2 - eg1).to.be.above(110000)
expect (ag2 - ag1).to.be.above(110000)
})

it("should support a large response", async() => {
const nElem = 2038
Expand All @@ -304,11 +314,57 @@ if (hre.network.name === "boba_local") {
expect(result).to.equal(nElem * 55)
})

it("should allow repeated calls (legacy support)", async () => {
await hello.estimateGas.callTwice(urlStr, '6', '6', 1, gasOverride)
let tr = await hello.callTwice(urlStr, '6', '6', 1, gasOverride)
const res = await tr.wait()
expect(res).to.be.ok

const ev = res.events.find(e => e.event === "MultFloatNumbers")
const result = parseInt(ev.data.slice(-64), 16) / 100
expect(result.toFixed(5)).to.equal('904.78000')
})

it("should disallow repeated calls with different input", async () => {
try {
await hello.estimateGas.callTwice(urlStr, '6', '7', 1, gasOverride)
expect(1).to.equal(0)
} catch (e) {
// generic error code indicating that a tx reverted
expect(e.error.toString()).to.contain("gas required exceeds allowance")
}

try {
let tr = await hello.callTwice(urlStr, '6', '7', 1, gasOverride)
const res = await tr.wait()
expect(1).to.equal(0)
} catch (e) {
expect(e.toString()).to.contain("transaction failed")
}
})

it("should disallow repeated calls at different depth", async () => {
try {
await hello.estimateGas.callTwice(urlStr, '8', '8', 2, gasOverride)
expect(1).to.equal(0)
} catch (e) {
expect(e.error.toString()).to.contain("gas required exceeds allowance")
}
try {
let tr = await hello.callTwice(urlStr, '8', '8', 2, gasOverride)
const res = await tr.wait()
expect(1).to.equal(0)
} catch (e) {
expect(e.toString()).to.contain("transaction failed")
}
})

it("final balance", async () => {
const postBalance = await turingCredit.prepaidBalance(
helper.address
)
//expect(postBalance).to.equal( utils.parseEther('0.5'))
// Change expected value if tests are added or skipped above
expect(postBalance).to.equal(utils.parseEther('0.7'))
})
})
} else {
Expand Down
89 changes: 51 additions & 38 deletions integration-tests/test/eth-l2/turing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,45 +73,48 @@ describe('Turing 256 Bit Random Number Test', async () => {
).attach(L1StandardBridgeAddress)
/* eslint-disable */
const http = require('http')
const ip = require("ip")
const ip = require('ip')
// start local server
const server = module.exports = http.createServer(async function (req, res) {

if (req.headers['content-type'] === 'application/json') {

let body = '';

req.on('data', function (chunk) {
body += chunk.toString()
})

req.on('end', async function () {
const jsonBody = JSON.parse(body)
const input = JSON.parse(body).params[0]
let result

const args = utils.defaultAbiCoder.decode(['uint256','uint256'], input)
if (req.url === "/echo") {
const randomPrice = Math.floor(Math.random() * 1000)
result = input
let response = {
"jsonrpc": "2.0",
"id": jsonBody.id,
"result": result
const server = (module.exports = http
.createServer(async function (req, res) {
if (req.headers['content-type'] === 'application/json') {
let body = ''

req.on('data', function (chunk) {
body += chunk.toString()
})

req.on('end', async function () {
const jsonBody = JSON.parse(body)
const input = JSON.parse(body).params[0]
let result

const args = utils.defaultAbiCoder.decode(
['uint256', 'uint256'],
input
)
if (req.url === '/echo') {
const randomPrice = Math.floor(Math.random() * 1000)
result = input
let response = {
jsonrpc: '2.0',
id: jsonBody.id,
result: result,
}
res.end(JSON.stringify(response))
server.emit('success', body)
} else {
res.writeHead(400, { 'Content-Type': 'text/plain' })
res.end('Bad request')
}
res.end(JSON.stringify(response))
server.emit('success', body)
} else {
res.writeHead(400, { 'Content-Type': 'text/plain' })
res.end('Bad request')
}
});
} else {
console.log("Other request:", req)
res.writeHead(400, { 'Content-Type': 'text/plain' })
res.end('Expected content-type: application/json')
}
}).listen(apiPort)
})
} else {
console.log('Other request:', req)
res.writeHead(400, { 'Content-Type': 'text/plain' })
res.end('Expected content-type: application/json')
}
})
.listen(apiPort))
URL = `http://${ip.address()}:${apiPort}/echo`
/* eslint-enable */
})
Expand Down Expand Up @@ -264,16 +267,26 @@ describe('Turing 256 Bit Random Number Test', async () => {
} catch (e) {
expect(e.error.toString()).to.contain('SERVER_ERROR')
}
try {
await random.MixedInput(URL, 123, 999, { gasLimit: 11_000_000 })
} catch (e) {
expect(e.error.toString()).to.contain('SERVER_ERROR')
}
})

// Should reject a 2nd call from a different EVM depth.
it('should disallow nested Turing calls', async () => {
try {
const tr = await random.NestedRandom(1)
await random.estimateGas.NestedRandom(1)
expect(1).to.equal(0)
} catch (e) {
expect(e.error.toString()).to.contain('SERVER_ERROR')
}
try {
const tr = await random.NestedRandom(1, { gasLimit: 11_000_000 })
} catch (e) {
expect(e.error.toString()).to.contain('SERVER_ERROR')
}
})

it('should allow repeated Random calls (legacy support)', async () => {
Expand Down
5 changes: 5 additions & 0 deletions l2geth/core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
}
}

p2 := msg.GasPrice()
if vmenv.ChainConfig().IsTuringCharge2Fork(vmenv.BlockNumber) && p2.BitLen() > 0 {
vmenv.Context.TuringGasMul = float64(l1GasPrice.Uint64()) / float64(p2.Uint64())
}

// Determine the L2 Boba fee if users chose BOBA as the fee token
feeTokenSelection := statedb.GetFeeTokenSelection(msg.From())

Expand Down
25 changes: 25 additions & 0 deletions l2geth/core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,31 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// We are in Verifier/Replica mode
// Turing for this Transaction has already been run elsewhere - replay using
// information from the EVM context
if evm.Context.TuringInput == nil {
evm.Context.TuringInput = make([]byte, len(input))
copy(evm.Context.TuringInput, input)
evm.Context.TuringVMDepth = evm.depth
} else if !bytes.Equal(input, evm.Context.TuringInput) || evm.depth != evm.Context.TuringVMDepth {
log.Debug("TURING ERROR: evm.Context.Turing already set")
return nil, gas, ErrTuringDepth
}

// For compatibility, only apply a charge beyond the legacy size limit
if isTuring2 {
if len(evm.Context.Turing) > 160 {
feePerByte := evm.Context.TuringGasMul * 500.0 / 32.0
turingGas = uint64(float64(len(evm.Context.Turing)) * feePerByte)
}

if contract.Gas <= turingGas {
log.Debug("TURING ERROR: Insufficient gas for calldata", "have", contract.Gas, "need", turingGas)
return nil, 0, ErrTuringTooLong
} else {
log.Debug("TURING Deducting calldata gas", "had", contract.Gas, "len", len(evm.Context.Turing), "Mul", evm.Context.TuringGasMul, "deducting", turingGas)
contract.UseGas(turingGas)
}
}

ret, err = run(evm, contract, evm.Context.Turing, false)
log.Trace("TURING REPLAY", "evm.Context.Turing", evm.Context.Turing)
}
Expand Down
67 changes: 67 additions & 0 deletions l2geth/params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,36 @@ var (

// Enable the conditional logic to prevent Turing balances from reaching zero
BobaOperaTestnetTuringChargeForkNum = big.NewInt(3000)

// Enable the conditional logic to fix bug in charging for L1 Turing calldata
BobaMainnetTuringCharge2ForkNum = big.NewInt(1064000)

// Enable the conditional logic to fix bug in charging for L1 Turing calldata
BobaGoerliTuringCharge2ForkNum = big.NewInt(114000)

// Enable the conditional logic to fix bug in charging for L1 Turing calldata
BobaMoonbeamTuringCharge2ForkNum = big.NewInt(1580000)

// Enable the conditional logic to fix bug in charging for L1 Turing calldata
BobaMoonbaseTuringCharge2ForkNum = big.NewInt(350000)

// Enable the conditional logic to fix bug in charging for L1 Turing calldata
BobaAvaxTuringCharge2ForkNum = big.NewInt(101200)

// Enable the conditional logic to fix bug in charging for L1 Turing calldata
BobaFujiTuringCharge2ForkNum = big.NewInt(4000)

// Enable the conditional logic to fix bug in charging for L1 Turing calldata
BobaBnbTuringCharge2ForkNum = big.NewInt(25740000)

// Enable the conditional logic to fix bug in charging for L1 Turing calldata
BobaBnbTestnetTuringCharge2ForkNum = big.NewInt(428000)

// Enable the conditional logic to fix bug in charging for L1 Turing calldata
BobaOperaTuringCharge2ForkNum = big.NewInt(80000)

// Enable the conditional logic to fix bug in charging for L1 Turing calldata
BobaOperaTestnetTuringCharge2ForkNum = big.NewInt(3000)
)

// TrustedCheckpoint represents a set of post-processed trie roots (CHT and
Expand Down Expand Up @@ -567,6 +597,43 @@ func (c *ChainConfig) IsTuringChargeFork(num *big.Int) bool {
return true
}

func (c *ChainConfig) IsTuringCharge2Fork(num *big.Int) bool {
if c.ChainID == nil {
return true
}
if c.ChainID.Cmp(OpMainnetChainID) == 0 {
return isForked(BobaMainnetTuringCharge2ForkNum, num)
}
if c.ChainID.Cmp(OpGoerliChainID) == 0 {
return isForked(BobaGoerliTuringCharge2ForkNum, num)
}
if c.ChainID.Cmp(OpMoonbeamChainID) == 0 {
return isForked(BobaMoonbeamTuringCharge2ForkNum, num)
}
if c.ChainID.Cmp(OpMoonbaseChainID) == 0 {
return isForked(BobaMoonbaseTuringCharge2ForkNum, num)
}
if c.ChainID.Cmp(OpBnbChainID) == 0 {
return isForked(BobaBnbTuringCharge2ForkNum, num)
}
if c.ChainID.Cmp(OpBnbTestnetChainID) == 0 {
return isForked(BobaBnbTestnetTuringCharge2ForkNum, num)
}
if c.ChainID.Cmp(OpAvaxChainID) == 0 {
return isForked(BobaAvaxTuringCharge2ForkNum, num)
}
if c.ChainID.Cmp(OpFujiChainID) == 0 {
return isForked(BobaFujiTuringCharge2ForkNum, num)
}
if c.ChainID.Cmp(OpOperaChainID) == 0 {
return isForked(BobaOperaTuringCharge2ForkNum, num)
}
if c.ChainID.Cmp(OpOperaTestnetChainID) == 0 {
return isForked(BobaOperaTestnetTuringCharge2ForkNum, num)
}
return true
}

func (c *ChainConfig) IsEthereumL2() bool {
if os.Getenv("IS_ETHEREUM_L2") == "true" {
return true
Expand Down
2 changes: 1 addition & 1 deletion ops/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ services:
environment:
L1_NODE_WEB3_URL: http://l1_chain:8545
L1_CONFIRMATIONS: 8
L2_NODE_WEB3_URL: http://l2geth:8545
L2_NODE_WEB3_URL: http://replica:8545
L2_CHECK_INTERVAL: 10
VERIFIER_WEB3_URL: http://verifier:8545
ADDRESS_MANAGER_ADDRESS: "0x5FbDB2315678afecb367f032d93F642f64180aa3"
Expand Down