Skip to content

Commit

Permalink
Turing replica state (#1286)
Browse files Browse the repository at this point in the history
  • Loading branch information
InoMurko authored Oct 30, 2023
1 parent 81c5ca1 commit 4dca96f
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 50 deletions.
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

0 comments on commit 4dca96f

Please sign in to comment.