Commit ad3eae27 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into inphi/mips-bin

parents 7618e0da 35ff4b47
...@@ -63,6 +63,8 @@ func TestEVM(t *testing.T) { ...@@ -63,6 +63,8 @@ func TestEVM(t *testing.T) {
if strings.HasPrefix(f.Name(), "oracle") { if strings.HasPrefix(f.Name(), "oracle") {
oracle = staticOracle(t, []byte("hello world")) oracle = staticOracle(t, []byte("hello world"))
} }
// Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin"
env, evmState := NewEVMEnv(contracts, addrs) env, evmState := NewEVMEnv(contracts, addrs)
env.Config.Tracer = tracer env.Config.Tracer = tracer
...@@ -83,6 +85,9 @@ func TestEVM(t *testing.T) { ...@@ -83,6 +85,9 @@ func TestEVM(t *testing.T) {
if us.state.PC == endAddr { if us.state.PC == endAddr {
break break
} }
if exitGroup && us.state.Exited {
break
}
insn := state.Memory.GetMemory(state.PC) insn := state.Memory.GetMemory(state.PC)
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn)
...@@ -122,11 +127,17 @@ func TestEVM(t *testing.T) { ...@@ -122,11 +127,17 @@ func TestEVM(t *testing.T) {
require.Equal(t, hexutil.Bytes(uniPost).String(), hexutil.Bytes(evmPost).String(), require.Equal(t, hexutil.Bytes(uniPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM") "mipsevm produced different state than EVM")
} }
require.Equal(t, uint32(endAddr), state.PC, "must reach end") if exitGroup {
// inspect test result require.NotEqual(t, uint32(endAddr), us.state.PC, "must not reach end")
done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8) require.True(t, us.state.Exited, "must set exited state")
require.Equal(t, done, uint32(1), "must be done") require.Equal(t, uint8(1), us.state.ExitCode, "must exit with 1")
require.Equal(t, result, uint32(1), "must have success result") } else {
require.Equal(t, uint32(endAddr), state.PC, "must reach end")
// inspect test result
done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8)
require.Equal(t, done, uint32(1), "must be done")
require.Equal(t, result, uint32(1), "must have success result")
}
}) })
} }
} }
......
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
li $v0, 4045
syscall
lui $t0, 0x4000
subu $v0, $v0, $t0
sltiu $v0, $v0, 1
# save results
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
li $v0, 4120
syscall
li $t0, 0x1
subu $v0, $v0, $t0
sltiu $v0, $v0, 1
# save results
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
li $a0, 1
li $v0, 4246
syscall
# Unreachable ....
# set test result to fail.
# Test runner should short-circuit before reaching this point.
li $v0, 0
# save results
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
# fnctl(0, 3)
li $v0, 4055
li $a0, 0x0
li $a1, 0x3
syscall
sltiu $v0, $v0, 1
# save results
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
.section .test, "x"
.balign 4
.set noreorder
.global test
.ent test
test:
li $v0, 4090
lui $a0, 0x3000
li $a1, 4096
syscall
lui $t0, 0x3000
subu $v0, $v0, $t0
sltiu $v0, $v0, 1
# save results
lui $s0, 0xbfff # Load the base address 0xbffffff0
ori $s0, 0xfff0
ori $s1, $0, 1 # Prepare the 'done' status
sw $v0, 8($s0) # Set the test result
sw $s1, 4($s0) # Set 'done'
$done:
jr $ra
nop
.end test
...@@ -69,6 +69,10 @@ $readloop: ...@@ -69,6 +69,10 @@ $readloop:
addiu $t0, $t0, -1 addiu $t0, $t0, -1
bnez $t0, $readloop bnez $t0, $readloop
nop nop
# reading the pre-image stream at EOF should have no effect
li $a1, 0x31000008
li $v0, 4003
syscall
# length at 0x31000000. We also check that the lower 32 bits are zero # length at 0x31000000. We also check that the lower 32 bits are zero
lui $s1, 0x3100 lui $s1, 0x3100
......
...@@ -35,6 +35,9 @@ func TestState(t *testing.T) { ...@@ -35,6 +35,9 @@ func TestState(t *testing.T) {
if strings.HasPrefix(f.Name(), "oracle") { if strings.HasPrefix(f.Name(), "oracle") {
oracle = staticOracle(t, []byte("hello world")) oracle = staticOracle(t, []byte("hello world"))
} }
// Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin"
// TODO: currently tests are compiled as flat binary objects // TODO: currently tests are compiled as flat binary objects
// We can use more standard tooling to compile them to ELF files and get remove maketests.py // We can use more standard tooling to compile them to ELF files and get remove maketests.py
fn := path.Join("open_mips_tests/test/bin", f.Name()) fn := path.Join("open_mips_tests/test/bin", f.Name())
...@@ -57,14 +60,24 @@ func TestState(t *testing.T) { ...@@ -57,14 +60,24 @@ func TestState(t *testing.T) {
if us.state.PC == endAddr { if us.state.PC == endAddr {
break break
} }
if exitGroup && us.state.Exited {
break
}
_, err := us.Step(false) _, err := us.Step(false)
require.NoError(t, err) require.NoError(t, err)
} }
require.Equal(t, uint32(endAddr), us.state.PC, "must reach end")
// inspect test result if exitGroup {
done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8) require.NotEqual(t, uint32(endAddr), us.state.PC, "must not reach end")
require.Equal(t, done, uint32(1), "must be done") require.True(t, us.state.Exited, "must set exited state")
require.Equal(t, result, uint32(1), "must have success result") require.Equal(t, uint8(1), us.state.ExitCode, "must exit with 1")
} else {
require.Equal(t, uint32(endAddr), us.state.PC, "must reach end")
done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8)
// inspect test result
require.Equal(t, done, uint32(1), "must be done")
require.Equal(t, result, uint32(1), "must have success result")
}
}) })
} }
} }
......
...@@ -30,7 +30,7 @@ You’ll need the following software installed to follow this tutorial: ...@@ -30,7 +30,7 @@ You’ll need the following software installed to follow this tutorial:
- [Git](https://git-scm.com/) - [Git](https://git-scm.com/)
- [Go](https://go.dev/) - [Go](https://go.dev/)
- [Node](https://nodejs.org/en/) - [Node](https://nodejs.org/en/)
- [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/) - [Pnpm](https://classic.yarnpkg.com/lang/en/docs/install/)
- [Foundry](https://github.com/foundry-rs/foundry#installation) - [Foundry](https://github.com/foundry-rs/foundry#installation)
- [Make](https://linux.die.net/man/1/make) - [Make](https://linux.die.net/man/1/make)
- [jq](https://github.com/jqlang/jq) - [jq](https://github.com/jqlang/jq)
...@@ -44,7 +44,7 @@ This tutorial was checked on: ...@@ -44,7 +44,7 @@ This tutorial was checked on:
| git, curl, jq, and make | OS default | `sudo apt install -y git curl make jq` | | git, curl, jq, and make | OS default | `sudo apt install -y git curl make jq` |
| Go | 1.20 | `sudo apt update` <br> `wget https://go.dev/dl/go1.20.linux-amd64.tar.gz` <br> `tar xvzf go1.20.linux-amd64.tar.gz` <br> `sudo cp go/bin/go /usr/bin/go` <br> `sudo mv go /usr/lib` <br> `echo export GOROOT=/usr/lib/go >> ~/.bashrc` | Go | 1.20 | `sudo apt update` <br> `wget https://go.dev/dl/go1.20.linux-amd64.tar.gz` <br> `tar xvzf go1.20.linux-amd64.tar.gz` <br> `sudo cp go/bin/go /usr/bin/go` <br> `sudo mv go /usr/lib` <br> `echo export GOROOT=/usr/lib/go >> ~/.bashrc`
| Node | 16.19.0 | `curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -` <br> `sudo apt-get install -y nodejs npm` | Node | 16.19.0 | `curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -` <br> `sudo apt-get install -y nodejs npm`
| yarn | 1.22.19 | `sudo npm install -g yarn` | pnpm | 8.5.6 | `sudo npm install -g pnpm`
| Foundry | 0.2.0 | `yarn install:foundry` | Foundry | 0.2.0 | `yarn install:foundry`
## Build the Source Code ## Build the Source Code
...@@ -69,14 +69,14 @@ We’re going to be spinning up an EVM Rollup from the OP Stack source code. Yo ...@@ -69,14 +69,14 @@ We’re going to be spinning up an EVM Rollup from the OP Stack source code. Yo
1. Install required modules. This is a slow process, while it is running you can already start building `op-geth`, as shown below. 1. Install required modules. This is a slow process, while it is running you can already start building `op-geth`, as shown below.
```bash ```bash
yarn install pnpm install
``` ```
1. Build the various packages inside of the Optimism Monorepo. 1. Build the various packages inside of the Optimism Monorepo.
```bash ```bash
make op-node op-batcher op-proposer make op-node op-batcher op-proposer
yarn build pnpm build
``` ```
### Build op-geth ### Build op-geth
...@@ -278,7 +278,7 @@ We’ve set up the L1 side of things, but now we need to set up the L2 side of t ...@@ -278,7 +278,7 @@ We’ve set up the L1 side of things, but now we need to set up the L2 side of t
You should then see the `genesis.json` and `rollup.json` files inside the `op-node` package. You should then see the `genesis.json` and `rollup.json` files inside the `op-node` package.
1. Next, generate the `jwt.txt` file with the following command: 1. Next, generate the `jwt.txt` file with the following command:
```bash ```bash
openssl rand -hex 32 > jwt.txt openssl rand -hex 32 > jwt.txt
...@@ -307,24 +307,6 @@ We’re almost ready to run our chain! Now we just need to run a few commands to ...@@ -307,24 +307,6 @@ We’re almost ready to run our chain! Now we just need to run a few commands to
mkdir datadir mkdir datadir
``` ```
1. Put a password file into the data directory folder:
```bash
echo "pwd" > datadir/password
```
1. Put the `Sequencer` private key into the data directory folder (don’t include a “0x” prefix):
```bash
echo "<SEQUENCER KEY HERE>" > datadir/block-signer-key
```
1. Import the key into `op-geth`:
```bash
./build/bin/geth account import --datadir=datadir --password=datadir/password datadir/block-signer-key
```
1. Next we need to initialize `op-geth` with the genesis file we generated and copied earlier: 1. Next we need to initialize `op-geth` with the genesis file we generated and copied earlier:
```bash ```bash
...@@ -344,7 +326,6 @@ Set these environment variables for the configuration ...@@ -344,7 +326,6 @@ Set these environment variables for the configuration
| Variable | Value | | Variable | Value |
| -------------- | - | -------------- | -
| `SEQ_ADDR` | Address of the `Sequencer` account
| `SEQ_KEY` | Private key of the `Sequencer` account | `SEQ_KEY` | Private key of the `Sequencer` account
| `BATCHER_KEY` | Private key of the `Batcher` accounts, which should have at least 1 ETH | `BATCHER_KEY` | Private key of the `Batcher` accounts, which should have at least 1 ETH
| `PROPOSER_KEY` | Private key of the `Proposer` account | `PROPOSER_KEY` | Private key of the `Proposer` account
...@@ -360,32 +341,27 @@ Run `op-geth` with the following commands. ...@@ -360,32 +341,27 @@ Run `op-geth` with the following commands.
cd ~/op-geth cd ~/op-geth
./build/bin/geth \ ./build/bin/geth \
--datadir ./datadir \ --datadir ./datadir \
--http \ --http \
--http.corsdomain="*" \ --http.corsdomain="*" \
--http.vhosts="*" \ --http.vhosts="*" \
--http.addr=0.0.0.0 \ --http.addr=0.0.0.0 \
--http.api=web3,debug,eth,txpool,net,engine \ --http.api=web3,debug,eth,txpool,net,engine \
--ws \ --ws \
--ws.addr=0.0.0.0 \ --ws.addr=0.0.0.0 \
--ws.port=8546 \ --ws.port=8546 \
--ws.origins="*" \ --ws.origins="*" \
--ws.api=debug,eth,txpool,net,engine \ --ws.api=debug,eth,txpool,net,engine \
--syncmode=full \ --syncmode=full \
--gcmode=archive \ --gcmode=archive \
--nodiscover \ --nodiscover \
--maxpeers=0 \ --maxpeers=0 \
--networkid=42069 \ --networkid=42069 \
--authrpc.vhosts="*" \ --authrpc.vhosts="*" \
--authrpc.addr=0.0.0.0 \ --authrpc.addr=0.0.0.0 \
--authrpc.port=8551 \ --authrpc.port=8551 \
--authrpc.jwtsecret=./jwt.txt \ --authrpc.jwtsecret=./jwt.txt \
--rollup.disabletxpoolgossip=true \ --rollup.disabletxpoolgossip=true
--password=./datadir/password \
--allow-insecure-unlock \
--mine \
--miner.etherbase=$SEQ_ADDR \
--unlock=$SEQ_ADDR
``` ```
And `op-geth` should be running! You should see some output, but you won’t see any blocks being created yet because `op-geth` is driven by the `op-node`. We’ll need to get that running next. And `op-geth` should be running! You should see some output, but you won’t see any blocks being created yet because `op-geth` is driven by the `op-node`. We’ll need to get that running next.
...@@ -443,7 +419,7 @@ cd ~/optimism/op-node ...@@ -443,7 +419,7 @@ cd ~/optimism/op-node
./bin/op-node \ ./bin/op-node \
--l2=http://localhost:8551 \ --l2=http://localhost:8551 \
--l2.jwt-secret=./jwt.txt \ --l2.jwt-secret=./jwt.txt \
--sequencer.enabled \ --sequencer.enabled \
--sequencer.l1-confs=3 \ --sequencer.l1-confs=3 \
--verifier.l1-confs=3 \ --verifier.l1-confs=3 \
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
"nx.json": "*", "nx.json": "*",
"tsconfig.json": "*", "tsconfig.json": "*",
".foundryrc": "*", ".foundryrc": "*",
".nvmrc": "*" "pnpm.lock.yaml": "*",
".npmrc": "*",
".nvmrc": "*"
}, },
"tasksRunnerOptions": { "tasksRunnerOptions": {
"default": { "default": {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package op_challenger package op_challenger
import ( import (
"fmt"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
// Main is the programmatic entry-point for running op-challenger // Main is the programmatic entry-point for running op-challenger
func Main(logger log.Logger, cfg *config.Config) error { func Main(logger log.Logger, cfg *config.Config) error {
client, err := ethclient.Dial(cfg.L1EthRpc)
if err != nil {
return fmt.Errorf("failed to dial L1: %w", err)
}
txMgr, err := txmgr.NewSimpleTxManager("challenger", logger, &metrics.NoopTxMetrics{}, cfg.TxMgrConfig)
if err != nil {
return fmt.Errorf("failed to create the transaction manager: %w", err)
}
contract, err := bindings.NewFaultDisputeGameCaller(cfg.GameAddress, client)
if err != nil {
return fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
}
loader := fault.NewLoader(logger, contract)
responder, err := fault.NewFaultResponder(logger, txMgr, cfg.GameAddress)
if err != nil {
return fmt.Errorf("failed to create the responder: %w", err)
}
gameDepth := 4
trace := fault.NewAlphabetProvider(cfg.AlphabetTrace, uint64(gameDepth))
agent := fault.NewAgent(loader, gameDepth, trace, responder, cfg.AgreeWithProposedOutput, logger)
logger.Info("Fault game started") logger.Info("Fault game started")
return nil
for {
logger.Info("Performing action")
_ = agent.Act()
time.Sleep(300 * time.Millisecond)
}
} }
#!/bin/bash
set -euo pipefail
DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd "$DIR"
DISPUTE_GAME_PROXY="0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"
CHARLIE_ADDRESS="0xF45B7537828CB2fffBC69996B054c2Aaf36DC778"
CHARLIE_KEY="74feb147d72bfae943e6b4e483410933d9e447d5dc47d52432dcc2c1454dabb7"
MALLORY_ADDRESS="0x4641c704a6c743f73ee1f36C7568Fbf4b80681e4"
MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d"
./bin/op-challenger --l1-eth-rpc http://localhost:8545 --alphabet "abcdefgh" --game-address $FAULT_GAME_ADDRESS --private-key $CHARLIE_KEY --num-confirmations 1 --agree-with-proposed-output=true
package main package main
import ( import (
"fmt"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
...@@ -11,9 +12,10 @@ import ( ...@@ -11,9 +12,10 @@ import (
) )
var ( var (
l1EthRpc = "http://example.com:8545" l1EthRpc = "http://example.com:8545"
gameAddressValue = "0xaa00000000000000000000000000000000000000" gameAddressValue = "0xaa00000000000000000000000000000000000000"
alphabetTrace = "abcdefghijz" alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true"
) )
func TestLogLevel(t *testing.T) { func TestLogLevel(t *testing.T) {
...@@ -33,12 +35,12 @@ func TestLogLevel(t *testing.T) { ...@@ -33,12 +35,12 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs()) cfg := configForArgs(t, addRequiredArgs())
defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace) defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace, true)
require.Equal(t, defaultCfg, cfg) require.Equal(t, defaultCfg, cfg)
} }
func TestDefaultConfigIsValid(t *testing.T) { func TestDefaultConfigIsValid(t *testing.T) {
cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace) cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace, true)
require.NoError(t, cfg.Check()) require.NoError(t, cfg.Check())
} }
...@@ -89,6 +91,24 @@ func TestTxManagerFlagsSupported(t *testing.T) { ...@@ -89,6 +91,24 @@ func TestTxManagerFlagsSupported(t *testing.T) {
require.Equal(t, uint64(7), cfg.TxMgrConfig.NumConfirmations) require.Equal(t, uint64(7), cfg.TxMgrConfig.NumConfirmations)
} }
func TestAgreeWithProposedOutput(t *testing.T) {
t.Run("MustBeProvided", func(t *testing.T) {
verifyArgsInvalid(t, "flag agree-with-proposed-output is required", addRequiredArgsExcept("--agree-with-proposed-output"))
})
t.Run("Enabled", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--agree-with-proposed-output"))
require.True(t, cfg.AgreeWithProposedOutput)
})
t.Run("EnabledWithArg", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--agree-with-proposed-output=true"))
require.True(t, cfg.AgreeWithProposedOutput)
})
t.Run("Disabled", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--agree-with-proposed-output=false"))
require.False(t, cfg.AgreeWithProposedOutput)
})
}
func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) { func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) {
_, _, err := runWithArgs(cliArgs) _, _, err := runWithArgs(cliArgs)
require.ErrorContains(t, err, messageContains) require.ErrorContains(t, err, messageContains)
...@@ -103,7 +123,7 @@ func configForArgs(t *testing.T, cliArgs []string) config.Config { ...@@ -103,7 +123,7 @@ func configForArgs(t *testing.T, cliArgs []string) config.Config {
func runWithArgs(cliArgs []string) (log.Logger, config.Config, error) { func runWithArgs(cliArgs []string) (log.Logger, config.Config, error) {
cfg := new(config.Config) cfg := new(config.Config)
var logger log.Logger var logger log.Logger
fullArgs := append([]string{"op-program"}, cliArgs...) fullArgs := append([]string{"op-challenger"}, cliArgs...)
err := run(fullArgs, func(log log.Logger, config *config.Config) error { err := run(fullArgs, func(log log.Logger, config *config.Config) error {
logger = log logger = log
cfg = config cfg = config
...@@ -126,17 +146,17 @@ func addRequiredArgsExcept(name string, optionalArgs ...string) []string { ...@@ -126,17 +146,17 @@ func addRequiredArgsExcept(name string, optionalArgs ...string) []string {
func requiredArgs() map[string]string { func requiredArgs() map[string]string {
return map[string]string{ return map[string]string{
"--l1-eth-rpc": l1EthRpc, "--agree-with-proposed-output": agreeWithProposedOutput,
"--game-address": gameAddressValue, "--l1-eth-rpc": l1EthRpc,
"--alphabet": alphabetTrace, "--game-address": gameAddressValue,
"--alphabet": alphabetTrace,
} }
} }
func toArgList(req map[string]string) []string { func toArgList(req map[string]string) []string {
var combined []string var combined []string
for name, value := range req { for name, value := range req {
combined = append(combined, name) combined = append(combined, fmt.Sprintf("%s=%s", name, value))
combined = append(combined, value)
} }
return combined return combined
} }
...@@ -21,22 +21,26 @@ var ( ...@@ -21,22 +21,26 @@ var (
// This also contains config options for auxiliary services. // This also contains config options for auxiliary services.
// It is used to initialize the challenger. // It is used to initialize the challenger.
type Config struct { type Config struct {
L1EthRpc string // L1 RPC Url L1EthRpc string // L1 RPC Url
GameAddress common.Address // Address of the fault game GameAddress common.Address // Address of the fault game
AlphabetTrace string // String for the AlphabetTraceProvider AlphabetTrace string // String for the AlphabetTraceProvider
AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output
TxMgrConfig txmgr.CLIConfig TxMgrConfig txmgr.CLIConfig
} }
func NewConfig(l1EthRpc string, func NewConfig(
l1EthRpc string,
GameAddress common.Address, GameAddress common.Address,
AlphabetTrace string, AlphabetTrace string,
AgreeWithProposedOutput bool,
) Config { ) Config {
return Config{ return Config{
L1EthRpc: l1EthRpc, L1EthRpc: l1EthRpc,
GameAddress: GameAddress, GameAddress: GameAddress,
AlphabetTrace: AlphabetTrace, AlphabetTrace: AlphabetTrace,
TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc), TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc),
AgreeWithProposedOutput: AgreeWithProposedOutput,
} }
} }
...@@ -70,9 +74,10 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) { ...@@ -70,9 +74,10 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
return &Config{ return &Config{
// Required Flags // Required Flags
L1EthRpc: ctx.String(flags.L1EthRpcFlag.Name), L1EthRpc: ctx.String(flags.L1EthRpcFlag.Name),
GameAddress: dgfAddress, GameAddress: dgfAddress,
AlphabetTrace: ctx.String(flags.AlphabetFlag.Name), AlphabetTrace: ctx.String(flags.AlphabetFlag.Name),
TxMgrConfig: txMgrConfig, AgreeWithProposedOutput: ctx.Bool(flags.AgreeWithProposedOutputFlag.Name),
TxMgrConfig: txMgrConfig,
}, nil }, nil
} }
...@@ -9,13 +9,14 @@ import ( ...@@ -9,13 +9,14 @@ import (
) )
var ( var (
validL1EthRpc = "http://localhost:8545" validL1EthRpc = "http://localhost:8545"
validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139") validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139")
validAlphabetTrace = "abcdefgh" validAlphabetTrace = "abcdefgh"
agreeWithProposedOutput = true
) )
func validConfig() Config { func validConfig() Config {
cfg := NewConfig(validL1EthRpc, validGameAddress, validAlphabetTrace) cfg := NewConfig(validL1EthRpc, validGameAddress, validAlphabetTrace, agreeWithProposedOutput)
return cfg return cfg
} }
......
...@@ -3,27 +3,30 @@ package fault ...@@ -3,27 +3,30 @@ package fault
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type Agent struct { type Agent struct {
solver *Solver solver *Solver
trace TraceProvider trace TraceProvider
loader Loader loader Loader
responder Responder responder Responder
maxDepth int maxDepth int
log log.Logger agreeWithProposedOutput bool
log log.Logger
} }
func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, log log.Logger) Agent { func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, agreeWithProposedOutput bool, log log.Logger) Agent {
return Agent{ return Agent{
solver: NewSolver(maxDepth, trace), solver: NewSolver(maxDepth, trace),
trace: trace, trace: trace,
loader: loader, loader: loader,
responder: responder, responder: responder,
maxDepth: maxDepth, maxDepth: maxDepth,
log: log, agreeWithProposedOutput: agreeWithProposedOutput,
log: log,
} }
} }
...@@ -36,11 +39,15 @@ func (a *Agent) Act() error { ...@@ -36,11 +39,15 @@ func (a *Agent) Act() error {
} }
// Create counter claims // Create counter claims
for _, claim := range game.Claims() { for _, claim := range game.Claims() {
_ = a.move(claim, game) if err := a.move(claim, game); err != nil {
log.Error("Failed to move", "err", err)
}
} }
// Step on all leaf claims // Step on all leaf claims
for _, claim := range game.Claims() { for _, claim := range game.Claims() {
_ = a.step(claim, game) if err := a.step(claim, game); err != nil {
log.Error("Failed to step", "err", err)
}
} }
return nil return nil
} }
...@@ -49,34 +56,33 @@ func (a *Agent) Act() error { ...@@ -49,34 +56,33 @@ func (a *Agent) Act() error {
func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) { func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) {
claims, err := a.loader.FetchClaims(ctx) claims, err := a.loader.FetchClaims(ctx)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to fetch claims: %w", err)
} }
if len(claims) == 0 { if len(claims) == 0 {
return nil, errors.New("no claims") return nil, errors.New("no claims")
} }
game := NewGameState(claims[0], uint64(a.maxDepth)) game := NewGameState(a.agreeWithProposedOutput, claims[0], uint64(a.maxDepth))
if err := game.PutAll(claims[1:]); err != nil { if err := game.PutAll(claims[1:]); err != nil {
return nil, err return nil, fmt.Errorf("failed to load claims into the local state: %w", err)
} }
return game, nil return game, nil
} }
// move determines & executes the next move given a claim pair // move determines & executes the next move given a claim
func (a *Agent) move(claim Claim, game Game) error { func (a *Agent) move(claim Claim, game Game) error {
a.log.Info("Fetching claims") nextMove, err := a.solver.NextMove(claim, game.AgreeWithClaimLevel(claim))
nextMove, err := a.solver.NextMove(claim)
if err != nil { if err != nil {
a.log.Warn("Failed to execute the next move", "err", err) a.log.Warn("Failed to execute the next move", "err", err)
return err return err
} }
if nextMove == nil { if nextMove == nil {
a.log.Info("No next move") a.log.Debug("No next move")
return nil return nil
} }
move := *nextMove move := *nextMove
log := a.log.New("is_defend", move.DefendsParent(), "depth", move.Depth(), "index_at_depth", move.IndexAtDepth(), "value", move.Value, log := a.log.New("is_defend", move.DefendsParent(), "depth", move.Depth(), "index_at_depth", move.IndexAtDepth(),
"letter", string(move.Value[31:]), "trace_index", move.Value[30], "value", move.Value, "trace_index", move.TraceIndex(a.maxDepth),
"parent_letter", string(claim.Value[31:]), "parent_trace_index", claim.Value[30]) "parent_value", claim.Value, "parent_trace_index", claim.TraceIndex(a.maxDepth))
if game.IsDuplicate(move) { if game.IsDuplicate(move) {
log.Debug("Duplicate move") log.Debug("Duplicate move")
return nil return nil
...@@ -85,25 +91,30 @@ func (a *Agent) move(claim Claim, game Game) error { ...@@ -85,25 +91,30 @@ func (a *Agent) move(claim Claim, game Game) error {
return a.responder.Respond(context.TODO(), move) return a.responder.Respond(context.TODO(), move)
} }
// step attempts to execute the step through the responder // step determines & executes the next step against a leaf claim through the responder
func (a *Agent) step(claim Claim, game Game) error { func (a *Agent) step(claim Claim, game Game) error {
if claim.Depth() != a.maxDepth { if claim.Depth() != a.maxDepth {
return nil return nil
} }
a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth) a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
step, err := a.solver.AttemptStep(claim) step, err := a.solver.AttemptStep(claim)
if err != nil { if err != nil {
a.log.Info("Failed to get a step", "err", err) a.log.Warn("Failed to get a step", "err", err)
return err
}
stateData, err := a.trace.GetPreimage(step.PreStateTraceIndex)
if err != nil {
a.log.Warn("Failed to get a state data", "err", err)
return err return err
} }
a.log.Info("Performing step", a.log.Info("Performing step",
"depth", step.LeafClaim.Depth(), "index_at_depth", step.LeafClaim.IndexAtDepth(), "value", step.LeafClaim.Value, "depth", step.LeafClaim.Depth(), "index_at_depth", step.LeafClaim.IndexAtDepth(), "value", step.LeafClaim.Value,
"is_attack", step.IsAttack) "is_attack", step.IsAttack, "prestate_trace_index", step.PreStateTraceIndex)
callData := StepCallData{ callData := StepCallData{
ClaimIndex: uint64(step.LeafClaim.ContractIndex), ClaimIndex: uint64(step.LeafClaim.ContractIndex),
IsAttack: step.IsAttack, IsAttack: step.IsAttack,
StateData: stateData,
} }
return a.responder.Step(context.TODO(), callData) return a.responder.Step(context.TODO(), callData)
} }
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"strings" "strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
) )
// AlphabetProvider is a [TraceProvider] that provides claims for specific // AlphabetProvider is a [TraceProvider] that provides claims for specific
...@@ -32,7 +33,7 @@ func (ap *AlphabetProvider) GetPreimage(i uint64) ([]byte, error) { ...@@ -32,7 +33,7 @@ func (ap *AlphabetProvider) GetPreimage(i uint64) ([]byte, error) {
if i >= uint64(len(ap.state)) { if i >= uint64(len(ap.state)) {
return ap.GetPreimage(uint64(len(ap.state)) - 1) return ap.GetPreimage(uint64(len(ap.state)) - 1)
} }
return buildAlphabetClaimBytes(i, ap.state[i]), nil return BuildAlphabetPreimage(i, ap.state[i]), nil
} }
// Get returns the claim value at the given index in the trace. // Get returns the claim value at the given index in the trace.
...@@ -41,17 +42,25 @@ func (ap *AlphabetProvider) Get(i uint64) (common.Hash, error) { ...@@ -41,17 +42,25 @@ func (ap *AlphabetProvider) Get(i uint64) (common.Hash, error) {
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
return common.BytesToHash(claimBytes), nil return crypto.Keccak256Hash(claimBytes), nil
} }
// buildAlphabetClaimBytes constructs the claim bytes for the index and state item. // BuildAlphabetPreimage constructs the claim bytes for the index and state item.
func buildAlphabetClaimBytes(i uint64, letter string) []byte { func BuildAlphabetPreimage(i uint64, letter string) []byte {
return append(IndexToBytes(i), []byte(letter)...) return append(IndexToBytes(i), LetterToBytes(letter)...)
} }
// IndexToBytes converts an index to a byte slice big endian // IndexToBytes converts an index to a byte slice big endian
func IndexToBytes(i uint64) []byte { func IndexToBytes(i uint64) []byte {
big := new(big.Int) big := new(big.Int)
big.SetUint64(i) big.SetUint64(i)
return big.Bytes() out := make([]byte, 32)
return big.FillBytes(out)
}
// LetterToBytes converts a letter to a 32 byte array
func LetterToBytes(letter string) []byte {
out := make([]byte, 32)
out[31] = byte(letter[0])
return out
} }
...@@ -20,15 +20,15 @@ func TestAlphabetProvider_Get_ClaimsByTraceIndex(t *testing.T) { ...@@ -20,15 +20,15 @@ func TestAlphabetProvider_Get_ClaimsByTraceIndex(t *testing.T) {
}{ }{
{ {
7, 7,
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"), alphabetClaim(7, "h"),
}, },
{ {
3, 3,
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"), alphabetClaim(3, "d"),
}, },
{ {
5, 5,
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000566"), alphabetClaim(5, "f"),
}, },
} }
...@@ -54,7 +54,7 @@ func FuzzIndexToBytes(f *testing.F) { ...@@ -54,7 +54,7 @@ func FuzzIndexToBytes(f *testing.F) {
// returns the correct pre-image for a index. // returns the correct pre-image for a index.
func TestGetPreimage_Succeeds(t *testing.T) { func TestGetPreimage_Succeeds(t *testing.T) {
ap := NewAlphabetProvider("abc", 2) ap := NewAlphabetProvider("abc", 2)
expected := append(IndexToBytes(uint64(0)), []byte("a")...) expected := BuildAlphabetPreimage(0, "a'")
retrieved, err := ap.GetPreimage(uint64(0)) retrieved, err := ap.GetPreimage(uint64(0))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected, retrieved) require.Equal(t, expected, retrieved)
...@@ -73,8 +73,7 @@ func TestGet_Succeeds(t *testing.T) { ...@@ -73,8 +73,7 @@ func TestGet_Succeeds(t *testing.T) {
ap := NewAlphabetProvider("abc", 2) ap := NewAlphabetProvider("abc", 2)
claim, err := ap.Get(0) claim, err := ap.Get(0)
require.NoError(t, err) require.NoError(t, err)
concatenated := append(IndexToBytes(0), []byte("a")...) expected := alphabetClaim(0, "a")
expected := common.BytesToHash(concatenated)
require.Equal(t, expected, claim) require.Equal(t, expected, claim)
} }
...@@ -92,7 +91,6 @@ func TestGet_Extends(t *testing.T) { ...@@ -92,7 +91,6 @@ func TestGet_Extends(t *testing.T) {
ap := NewAlphabetProvider("abc", 2) ap := NewAlphabetProvider("abc", 2)
claim, err := ap.Get(3) claim, err := ap.Get(3)
require.NoError(t, err) require.NoError(t, err)
concatenated := append(IndexToBytes(2), []byte("c")...) expected := alphabetClaim(2, "c")
expected := common.BytesToHash(concatenated)
require.Equal(t, expected, claim) require.Equal(t, expected, claim)
} }
...@@ -26,6 +26,6 @@ func FullGame() { ...@@ -26,6 +26,6 @@ func FullGame() {
}, },
} }
o := fault.NewOrchestrator(maxDepth, []fault.TraceProvider{canonicalProvider, disputedProvider}, []string{"charlie", "mallory"}, root) o := fault.NewOrchestrator(maxDepth, []fault.TraceProvider{canonicalProvider, disputedProvider}, []string{"charlie", "mallory"}, []bool{false, true}, root)
o.Start() o.Start()
} }
...@@ -53,19 +53,19 @@ func SolverExampleOne() { ...@@ -53,19 +53,19 @@ func SolverExampleOne() {
fmt.Println() fmt.Println()
PrettyPrintAlphabetClaim("Root claim", root) PrettyPrintAlphabetClaim("Root claim", root)
claim1, err := cannonicalSolver.NextMove(root) claim1, err := cannonicalSolver.NextMove(root, false)
if err != nil { if err != nil {
fmt.Printf("error getting claim from provider: %v", err) fmt.Printf("error getting claim from provider: %v", err)
} }
PrettyPrintAlphabetClaim("Cannonical move", *claim1) PrettyPrintAlphabetClaim("Cannonical move", *claim1)
claim2, err := disputedSolver.NextMove(*claim1) claim2, err := disputedSolver.NextMove(*claim1, false)
if err != nil { if err != nil {
fmt.Printf("error getting claim from provider: %v", err) fmt.Printf("error getting claim from provider: %v", err)
} }
PrettyPrintAlphabetClaim("Disputed moved", *claim2) PrettyPrintAlphabetClaim("Disputed moved", *claim2)
claim3, err := cannonicalSolver.NextMove(*claim2) claim3, err := cannonicalSolver.NextMove(*claim2, false)
if err != nil { if err != nil {
fmt.Printf("error getting claim from provider: %v", err) fmt.Printf("error getting claim from provider: %v", err)
} }
......
...@@ -33,35 +33,50 @@ type Game interface { ...@@ -33,35 +33,50 @@ type Game interface {
// PostStateClaim gets the claim which commits to the post-state of this specific claim. // PostStateClaim gets the claim which commits to the post-state of this specific claim.
// This will return an error if it is called with a non-leaf claim. // This will return an error if it is called with a non-leaf claim.
PostStateClaim(claim Claim) (Claim, error) PostStateClaim(claim Claim) (Claim, error)
// AgreeWithLevel returns if the game state agrees with the provided claim level.
AgreeWithClaimLevel(claim Claim) bool
} }
type extendedClaim struct { type extendedClaim struct {
self Claim self Claim
contractIndex int children []ClaimData
children []ClaimData
} }
// gameState is a struct that represents the state of a dispute game. // gameState is a struct that represents the state of a dispute game.
// The game state implements the [Game] interface. // The game state implements the [Game] interface.
type gameState struct { type gameState struct {
root ClaimData agreeWithProposedOutput bool
claims map[ClaimData]*extendedClaim root ClaimData
depth uint64 claims map[ClaimData]*extendedClaim
depth uint64
} }
// NewGameState returns a new game state. // NewGameState returns a new game state.
// The provided [Claim] is used as the root node. // The provided [Claim] is used as the root node.
func NewGameState(root Claim, depth uint64) *gameState { func NewGameState(agreeWithProposedOutput bool, root Claim, depth uint64) *gameState {
claims := make(map[ClaimData]*extendedClaim) claims := make(map[ClaimData]*extendedClaim)
claims[root.ClaimData] = &extendedClaim{ claims[root.ClaimData] = &extendedClaim{
self: root, self: root,
contractIndex: 0, children: make([]ClaimData, 0),
children: make([]ClaimData, 0),
} }
return &gameState{ return &gameState{
root: root.ClaimData, agreeWithProposedOutput: agreeWithProposedOutput,
claims: claims, root: root.ClaimData,
depth: depth, claims: claims,
depth: depth,
}
}
// AgreeWithLevel returns if the game state agrees with the provided claim level.
func (g *gameState) AgreeWithClaimLevel(claim Claim) bool {
isOddLevel := claim.Depth()%2 == 1
// If we agree with the proposed output, we agree with odd levels
// If we disagree with the proposed output, we agree with the root claim level & even levels
if g.agreeWithProposedOutput {
return isOddLevel
} else {
return !isOddLevel
} }
} }
...@@ -81,15 +96,15 @@ func (g *gameState) Put(claim Claim) error { ...@@ -81,15 +96,15 @@ func (g *gameState) Put(claim Claim) error {
if claim.IsRoot() || g.IsDuplicate(claim) { if claim.IsRoot() || g.IsDuplicate(claim) {
return ErrClaimExists return ErrClaimExists
} }
if parent, ok := g.claims[claim.Parent]; !ok { parent, ok := g.claims[claim.Parent]
if !ok {
return errors.New("no parent claim") return errors.New("no parent claim")
} else { } else {
parent.children = append(parent.children, claim.ClaimData) parent.children = append(parent.children, claim.ClaimData)
} }
g.claims[claim.ClaimData] = &extendedClaim{ g.claims[claim.ClaimData] = &extendedClaim{
self: claim, self: claim,
contractIndex: claim.ContractIndex, children: make([]ClaimData, 0),
children: make([]ClaimData, 0),
} }
return nil return nil
} }
......
...@@ -48,7 +48,7 @@ func createTestClaims() (Claim, Claim, Claim, Claim) { ...@@ -48,7 +48,7 @@ func createTestClaims() (Claim, Claim, Claim, Claim) {
func TestIsDuplicate(t *testing.T) { func TestIsDuplicate(t *testing.T) {
// Setup the game state. // Setup the game state.
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.Put(top)) require.NoError(t, g.Put(top))
// Root + Top should be duplicates // Root + Top should be duplicates
...@@ -65,7 +65,7 @@ func TestIsDuplicate(t *testing.T) { ...@@ -65,7 +65,7 @@ func TestIsDuplicate(t *testing.T) {
func TestGame_Put_RootAlreadyExists(t *testing.T) { func TestGame_Put_RootAlreadyExists(t *testing.T) {
// Setup the game state. // Setup the game state.
top, _, _, _ := createTestClaims() top, _, _, _ := createTestClaims()
g := NewGameState(top, testMaxDepth) g := NewGameState(false, top, testMaxDepth)
// Try to put the root claim into the game state again. // Try to put the root claim into the game state again.
err := g.Put(top) err := g.Put(top)
...@@ -77,7 +77,7 @@ func TestGame_Put_RootAlreadyExists(t *testing.T) { ...@@ -77,7 +77,7 @@ func TestGame_Put_RootAlreadyExists(t *testing.T) {
func TestGame_PutAll_RootAlreadyExists(t *testing.T) { func TestGame_PutAll_RootAlreadyExists(t *testing.T) {
// Setup the game state. // Setup the game state.
root, _, _, _ := createTestClaims() root, _, _, _ := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
// Try to put the root claim into the game state again. // Try to put the root claim into the game state again.
err := g.PutAll([]Claim{root}) err := g.PutAll([]Claim{root})
...@@ -88,7 +88,7 @@ func TestGame_PutAll_RootAlreadyExists(t *testing.T) { ...@@ -88,7 +88,7 @@ func TestGame_PutAll_RootAlreadyExists(t *testing.T) {
// instance errors when the given claim already exists in state. // instance errors when the given claim already exists in state.
func TestGame_PutAll_AlreadyExists(t *testing.T) { func TestGame_PutAll_AlreadyExists(t *testing.T) {
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
err := g.PutAll([]Claim{top, middle}) err := g.PutAll([]Claim{top, middle})
require.NoError(t, err) require.NoError(t, err)
...@@ -101,7 +101,7 @@ func TestGame_PutAll_AlreadyExists(t *testing.T) { ...@@ -101,7 +101,7 @@ func TestGame_PutAll_AlreadyExists(t *testing.T) {
func TestGame_PutAll_ParentsAndChildren(t *testing.T) { func TestGame_PutAll_ParentsAndChildren(t *testing.T) {
// Setup the game state. // Setup the game state.
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
// We should not be able to get the parent of the root claim. // We should not be able to get the parent of the root claim.
parent, err := g.getParent(root) parent, err := g.getParent(root)
...@@ -127,7 +127,7 @@ func TestGame_PutAll_ParentsAndChildren(t *testing.T) { ...@@ -127,7 +127,7 @@ func TestGame_PutAll_ParentsAndChildren(t *testing.T) {
func TestGame_Put_AlreadyExists(t *testing.T) { func TestGame_Put_AlreadyExists(t *testing.T) {
// Setup the game state. // Setup the game state.
top, middle, _, _ := createTestClaims() top, middle, _, _ := createTestClaims()
g := NewGameState(top, testMaxDepth) g := NewGameState(false, top, testMaxDepth)
// Put the next claim into state. // Put the next claim into state.
err := g.Put(middle) err := g.Put(middle)
...@@ -142,7 +142,7 @@ func TestGame_Put_AlreadyExists(t *testing.T) { ...@@ -142,7 +142,7 @@ func TestGame_Put_AlreadyExists(t *testing.T) {
func TestGame_Put_ParentsAndChildren(t *testing.T) { func TestGame_Put_ParentsAndChildren(t *testing.T) {
// Setup the game state. // Setup the game state.
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
// We should not be able to get the parent of the root claim. // We should not be able to get the parent of the root claim.
parent, err := g.getParent(root) parent, err := g.getParent(root)
...@@ -175,7 +175,7 @@ func TestGame_Put_ParentsAndChildren(t *testing.T) { ...@@ -175,7 +175,7 @@ func TestGame_Put_ParentsAndChildren(t *testing.T) {
func TestGame_ClaimPairs(t *testing.T) { func TestGame_ClaimPairs(t *testing.T) {
// Setup the game state. // Setup the game state.
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
// Add top claim to the game state. // Add top claim to the game state.
err := g.Put(top) err := g.Put(top)
...@@ -199,7 +199,7 @@ func TestGame_ClaimPairs(t *testing.T) { ...@@ -199,7 +199,7 @@ func TestGame_ClaimPairs(t *testing.T) {
// those functions return an error. // those functions return an error.
func TestPrePostStateOnlyOnLeafClaim(t *testing.T) { func TestPrePostStateOnlyOnLeafClaim(t *testing.T) {
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.PutAll([]Claim{top, middle, bottom})) require.NoError(t, g.PutAll([]Claim{top, middle, bottom}))
_, err := g.PreStateClaim(middle) _, err := g.PreStateClaim(middle)
...@@ -210,7 +210,7 @@ func TestPrePostStateOnlyOnLeafClaim(t *testing.T) { ...@@ -210,7 +210,7 @@ func TestPrePostStateOnlyOnLeafClaim(t *testing.T) {
func TestPreStateClaim(t *testing.T) { func TestPreStateClaim(t *testing.T) {
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.Put(top)) require.NoError(t, g.Put(top))
require.NoError(t, g.Put(middle)) require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom)) require.NoError(t, g.Put(bottom))
...@@ -224,7 +224,7 @@ func TestPreStateClaim(t *testing.T) { ...@@ -224,7 +224,7 @@ func TestPreStateClaim(t *testing.T) {
func TestPostStateClaim(t *testing.T) { func TestPostStateClaim(t *testing.T) {
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.Put(top)) require.NoError(t, g.Put(top))
require.NoError(t, g.Put(middle)) require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom)) require.NoError(t, g.Put(bottom))
...@@ -234,3 +234,27 @@ func TestPostStateClaim(t *testing.T) { ...@@ -234,3 +234,27 @@ func TestPostStateClaim(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, middle, post) require.Equal(t, middle, post)
} }
func TestAgreeWithClaimLevelDisagreeWithOutput(t *testing.T) {
// Setup the game state.
root, top, middle, bottom := createTestClaims()
g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.PutAll([]Claim{top, middle, bottom}))
require.True(t, g.AgreeWithClaimLevel(root))
require.False(t, g.AgreeWithClaimLevel(top))
require.True(t, g.AgreeWithClaimLevel(middle))
require.False(t, g.AgreeWithClaimLevel(bottom))
}
func TestAgreeWithClaimLevelAgreeWithOutput(t *testing.T) {
// Setup the game state.
root, top, middle, bottom := createTestClaims()
g := NewGameState(true, root, testMaxDepth)
require.NoError(t, g.PutAll([]Claim{top, middle, bottom}))
require.False(t, g.AgreeWithClaimLevel(root))
require.True(t, g.AgreeWithClaimLevel(top))
require.False(t, g.AgreeWithClaimLevel(middle))
require.True(t, g.AgreeWithClaimLevel(bottom))
}
...@@ -28,18 +28,14 @@ type Loader interface { ...@@ -28,18 +28,14 @@ type Loader interface {
// loader pulls in fault dispute game claim data periodically and over subscriptions. // loader pulls in fault dispute game claim data periodically and over subscriptions.
type loader struct { type loader struct {
log log.Logger log log.Logger
state Game
claimFetcher ClaimFetcher claimFetcher ClaimFetcher
} }
// NewLoader creates a new [loader]. // NewLoader creates a new [loader].
func NewLoader(log log.Logger, state Game, claimFetcher ClaimFetcher) *loader { func NewLoader(log log.Logger, claimFetcher ClaimFetcher) *loader {
return &loader{ return &loader{
log: log, log: log,
state: state,
claimFetcher: claimFetcher, claimFetcher: claimFetcher,
} }
} }
...@@ -60,6 +56,8 @@ func (l *loader) fetchClaim(ctx context.Context, arrIndex uint64) (Claim, error) ...@@ -60,6 +56,8 @@ func (l *loader) fetchClaim(ctx context.Context, arrIndex uint64) (Claim, error)
Value: fetchedClaim.Claim, Value: fetchedClaim.Claim,
Position: NewPositionFromGIndex(fetchedClaim.Position.Uint64()), Position: NewPositionFromGIndex(fetchedClaim.Position.Uint64()),
}, },
ContractIndex: int(arrIndex),
ParentContractIndex: int(fetchedClaim.ParentIndex),
} }
if !claim.IsRootPosition() { if !claim.IsRootPosition() {
......
...@@ -15,46 +15,8 @@ import ( ...@@ -15,46 +15,8 @@ import (
var ( var (
mockClaimDataError = fmt.Errorf("claim data errored") mockClaimDataError = fmt.Errorf("claim data errored")
mockClaimLenError = fmt.Errorf("claim len errored") mockClaimLenError = fmt.Errorf("claim len errored")
mockPutError = fmt.Errorf("put errored")
) )
type mockGameState struct {
putCalled int
putErrors bool
}
func (m *mockGameState) Put(claim Claim) error {
m.putCalled++
if m.putErrors {
return mockPutError
}
return nil
}
func (m *mockGameState) PutAll(claims []Claim) error {
m.putCalled += len(claims)
if m.putErrors {
return mockPutError
}
return nil
}
func (m *mockGameState) Claims() []Claim {
return []Claim{}
}
func (m *mockGameState) IsDuplicate(claim Claim) bool {
return false
}
func (m *mockGameState) PreStateClaim(claim Claim) (Claim, error) {
panic("unimplemented")
}
func (m *mockGameState) PostStateClaim(claim Claim) (Claim, error) {
panic("unimplemented")
}
type mockClaimFetcher struct { type mockClaimFetcher struct {
claimDataError bool claimDataError bool
claimLenError bool claimLenError bool
...@@ -126,7 +88,7 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) { ...@@ -126,7 +88,7 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) {
log := testlog.Logger(t, log.LvlError) log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher() mockClaimFetcher := newMockClaimFetcher()
expectedClaims := mockClaimFetcher.returnClaims expectedClaims := mockClaimFetcher.returnClaims
loader := NewLoader(log, &mockGameState{}, mockClaimFetcher) loader := NewLoader(log, mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background()) claims, err := loader.FetchClaims(context.Background())
require.NoError(t, err) require.NoError(t, err)
require.ElementsMatch(t, []Claim{ require.ElementsMatch(t, []Claim{
...@@ -139,6 +101,7 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) { ...@@ -139,6 +101,7 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) {
Value: expectedClaims[0].Claim, Value: expectedClaims[0].Claim,
Position: NewPositionFromGIndex(expectedClaims[0].Position.Uint64()), Position: NewPositionFromGIndex(expectedClaims[0].Position.Uint64()),
}, },
ContractIndex: 0,
}, },
{ {
ClaimData: ClaimData{ ClaimData: ClaimData{
...@@ -149,6 +112,7 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) { ...@@ -149,6 +112,7 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) {
Value: expectedClaims[0].Claim, Value: expectedClaims[0].Claim,
Position: NewPositionFromGIndex(expectedClaims[1].Position.Uint64()), Position: NewPositionFromGIndex(expectedClaims[1].Position.Uint64()),
}, },
ContractIndex: 1,
}, },
{ {
ClaimData: ClaimData{ ClaimData: ClaimData{
...@@ -159,6 +123,7 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) { ...@@ -159,6 +123,7 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) {
Value: expectedClaims[0].Claim, Value: expectedClaims[0].Claim,
Position: NewPositionFromGIndex(expectedClaims[2].Position.Uint64()), Position: NewPositionFromGIndex(expectedClaims[2].Position.Uint64()),
}, },
ContractIndex: 2,
}, },
}, claims) }, claims)
} }
...@@ -169,7 +134,7 @@ func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) { ...@@ -169,7 +134,7 @@ func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) {
log := testlog.Logger(t, log.LvlError) log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher() mockClaimFetcher := newMockClaimFetcher()
mockClaimFetcher.claimDataError = true mockClaimFetcher.claimDataError = true
loader := NewLoader(log, &mockGameState{}, mockClaimFetcher) loader := NewLoader(log, mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background()) claims, err := loader.FetchClaims(context.Background())
require.ErrorIs(t, err, mockClaimDataError) require.ErrorIs(t, err, mockClaimDataError)
require.Empty(t, claims) require.Empty(t, claims)
...@@ -181,7 +146,7 @@ func TestLoader_FetchClaims_ClaimLenErrors(t *testing.T) { ...@@ -181,7 +146,7 @@ func TestLoader_FetchClaims_ClaimLenErrors(t *testing.T) {
log := testlog.Logger(t, log.LvlError) log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher() mockClaimFetcher := newMockClaimFetcher()
mockClaimFetcher.claimLenError = true mockClaimFetcher.claimLenError = true
loader := NewLoader(log, &mockGameState{}, mockClaimFetcher) loader := NewLoader(log, mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background()) claims, err := loader.FetchClaims(context.Background())
require.ErrorIs(t, err, mockClaimLenError) require.ErrorIs(t, err, mockClaimLenError)
require.Empty(t, claims) require.Empty(t, claims)
......
...@@ -15,7 +15,7 @@ type Orchestrator struct { ...@@ -15,7 +15,7 @@ type Orchestrator struct {
claimLen, stepLen, step int claimLen, stepLen, step int
} }
func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, root Claim) Orchestrator { func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, agreeWithProposedOutput []bool, root Claim) Orchestrator {
o := Orchestrator{ o := Orchestrator{
agents: make([]Agent, len(traces)), agents: make([]Agent, len(traces)),
claims: []Claim{root}, claims: []Claim{root},
...@@ -23,7 +23,7 @@ func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, ro ...@@ -23,7 +23,7 @@ func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, ro
} }
log.Info("Starting game", "root_letter", string(root.Value[31:])) log.Info("Starting game", "root_letter", string(root.Value[31:]))
for i, trace := range traces { for i, trace := range traces {
o.agents[i] = NewAgent(&o, int(maxDepth), trace, &o, log.New("role", names[i])) o.agents[i] = NewAgent(&o, int(maxDepth), trace, &o, agreeWithProposedOutput[i], log.New("role", names[i]))
} }
return o return o
} }
......
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
// Solver uses a [TraceProvider] to determine the moves to make in a dispute game. // Solver uses a [TraceProvider] to determine the moves to make in a dispute game.
type Solver struct { type Solver struct {
TraceProvider TraceProvider
gameDepth int gameDepth int
} }
...@@ -22,7 +21,11 @@ func NewSolver(gameDepth int, traceProvider TraceProvider) *Solver { ...@@ -22,7 +21,11 @@ func NewSolver(gameDepth int, traceProvider TraceProvider) *Solver {
} }
// NextMove returns the next move to make given the current state of the game. // NextMove returns the next move to make given the current state of the game.
func (s *Solver) NextMove(claim Claim) (*Claim, error) { func (s *Solver) NextMove(claim Claim, agreeWithClaimLevel bool) (*Claim, error) {
if agreeWithClaimLevel {
return nil, nil
}
// Special case of the root claim // Special case of the root claim
if claim.IsRoot() { if claim.IsRoot() {
return s.handleRoot(claim) return s.handleRoot(claim)
...@@ -30,33 +33,14 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) { ...@@ -30,33 +33,14 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) {
return s.handleMiddle(claim) return s.handleMiddle(claim)
} }
type StepData struct {
LeafClaim Claim
IsAttack bool
}
// AttemptStep determines what step should occur for a given leaf claim.
// An error will be returned if the claim is not at the max depth.
func (s *Solver) AttemptStep(claim Claim) (StepData, error) {
if claim.Depth() != s.gameDepth {
return StepData{}, errors.New("cannot step on non-leaf claims")
}
claimCorrect, err := s.agreeWithClaim(claim.ClaimData)
if err != nil {
return StepData{}, err
}
return StepData{
LeafClaim: claim,
IsAttack: claimCorrect,
}, nil
}
func (s *Solver) handleRoot(claim Claim) (*Claim, error) { func (s *Solver) handleRoot(claim Claim) (*Claim, error) {
agree, err := s.agreeWithClaim(claim.ClaimData) agree, err := s.agreeWithClaim(claim.ClaimData)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Attack the root claim if we do not agree with it // Attack the root claim if we do not agree with it
// Note: We always disagree with the claim level at this point,
// so if we agree with claim maybe we should also attack?
if !agree { if !agree {
return s.attack(claim) return s.attack(claim)
} else { } else {
...@@ -65,10 +49,6 @@ func (s *Solver) handleRoot(claim Claim) (*Claim, error) { ...@@ -65,10 +49,6 @@ func (s *Solver) handleRoot(claim Claim) (*Claim, error) {
} }
func (s *Solver) handleMiddle(claim Claim) (*Claim, error) { func (s *Solver) handleMiddle(claim Claim) (*Claim, error) {
parentCorrect, err := s.agreeWithClaim(claim.Parent)
if err != nil {
return nil, err
}
claimCorrect, err := s.agreeWithClaim(claim.ClaimData) claimCorrect, err := s.agreeWithClaim(claim.ClaimData)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -76,25 +56,39 @@ func (s *Solver) handleMiddle(claim Claim) (*Claim, error) { ...@@ -76,25 +56,39 @@ func (s *Solver) handleMiddle(claim Claim) (*Claim, error) {
if claim.Depth() == s.gameDepth { if claim.Depth() == s.gameDepth {
return nil, errors.New("game depth reached") return nil, errors.New("game depth reached")
} }
if parentCorrect && claimCorrect { if claimCorrect {
// We agree with the parent, but the claim is disagreeing with it.
// Since we agree with the claim, the difference must be to the right of the claim
return s.defend(claim) return s.defend(claim)
} else if parentCorrect && !claimCorrect { } else {
// We agree with the parent, but the claim disagrees with it.
// Since we disagree with the claim, the difference must be to the left of the claim
return s.attack(claim)
} else if !parentCorrect && claimCorrect {
// Do nothing, we disagree with the parent, but this claim has correctly countered it
return nil, nil
} else if !parentCorrect && !claimCorrect {
// We disagree with the parent so want to counter it (which the claim is doing)
// but we also disagree with the claim so there must be a difference to the left of claim
// Note that we will create the correct counter-claim for parent when it is evaluated, no need to do it here
return s.attack(claim) return s.attack(claim)
} }
// This should not be reached }
return nil, errors.New("no next move")
type StepData struct {
LeafClaim Claim
IsAttack bool
PreStateTraceIndex uint64
}
// AttemptStep determines what step should occur for a given leaf claim.
// An error will be returned if the claim is not at the max depth.
func (s *Solver) AttemptStep(claim Claim) (StepData, error) {
if claim.Depth() != s.gameDepth {
return StepData{}, errors.New("cannot step on non-leaf claims")
}
claimCorrect, err := s.agreeWithClaim(claim.ClaimData)
if err != nil {
return StepData{}, err
}
index := claim.TraceIndex(s.gameDepth)
// TODO(CLI-4198): Handle case where we dispute trace index 0
if !claimCorrect {
index -= 1
}
return StepData{
LeafClaim: claim,
IsAttack: !claimCorrect,
PreStateTraceIndex: index,
}, nil
} }
// attack returns a response that attacks the claim. // attack returns a response that attacks the claim.
...@@ -105,8 +99,9 @@ func (s *Solver) attack(claim Claim) (*Claim, error) { ...@@ -105,8 +99,9 @@ func (s *Solver) attack(claim Claim) (*Claim, error) {
return nil, err return nil, err
} }
return &Claim{ return &Claim{
ClaimData: ClaimData{Value: value, Position: position}, ClaimData: ClaimData{Value: value, Position: position},
Parent: claim.ClaimData, Parent: claim.ClaimData,
ParentContractIndex: claim.ContractIndex,
}, nil }, nil
} }
...@@ -118,8 +113,9 @@ func (s *Solver) defend(claim Claim) (*Claim, error) { ...@@ -118,8 +113,9 @@ func (s *Solver) defend(claim Claim) (*Claim, error) {
return nil, err return nil, err
} }
return &Claim{ return &Claim{
ClaimData: ClaimData{Value: value, Position: position}, ClaimData: ClaimData{Value: value, Position: position},
Parent: claim.ClaimData, Parent: claim.ClaimData,
ParentContractIndex: claim.ContractIndex,
}, nil }, nil
} }
......
...@@ -4,9 +4,14 @@ import ( ...@@ -4,9 +4,14 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func alphabetClaim(index uint64, letter string) common.Hash {
return crypto.Keccak256Hash(BuildAlphabetPreimage(index, letter))
}
// TestSolver_NextMove_Opponent tests the [Solver] NextMove function // TestSolver_NextMove_Opponent tests the [Solver] NextMove function
// with an [fault.AlphabetProvider] as the [TraceProvider]. // with an [fault.AlphabetProvider] as the [TraceProvider].
func TestSolver_NextMove_Opponent(t *testing.T) { func TestSolver_NextMove_Opponent(t *testing.T) {
...@@ -18,73 +23,87 @@ func TestSolver_NextMove_Opponent(t *testing.T) { ...@@ -18,73 +23,87 @@ func TestSolver_NextMove_Opponent(t *testing.T) {
// The following claims are created using the state: "abcdexyz". // The following claims are created using the state: "abcdexyz".
// The responses are the responses we expect from the solver. // The responses are the responses we expect from the solver.
indices := []struct { indices := []struct {
traceIndex int claim Claim
claim Claim response ClaimData
response ClaimData
}{ }{
{ {
7,
Claim{ Claim{
ClaimData: ClaimData{ ClaimData: ClaimData{
Value: common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000077a"), Value: alphabetClaim(7, "z"),
Position: NewPosition(0, 0), Position: NewPosition(0, 0),
}, },
// Root claim has no parent // Root claim has no parent
}, },
ClaimData{ ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"), Value: alphabetClaim(3, "d"),
Position: NewPosition(1, 0), Position: NewPosition(1, 0),
}, },
}, },
{ {
3,
Claim{ Claim{
ClaimData: ClaimData{ ClaimData: ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"), Value: alphabetClaim(3, "d"),
Position: NewPosition(1, 0), Position: NewPosition(1, 0),
}, },
Parent: ClaimData{ Parent: ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"), Value: alphabetClaim(7, "h"),
Position: NewPosition(0, 0), Position: NewPosition(0, 0),
}, },
}, },
ClaimData{ ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000566"), Value: alphabetClaim(5, "f"),
Position: NewPosition(2, 2), Position: NewPosition(2, 2),
}, },
}, },
{ {
5,
Claim{ Claim{
ClaimData: ClaimData{ ClaimData: ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000578"), Value: alphabetClaim(5, "x"),
Position: NewPosition(2, 2), Position: NewPosition(2, 2),
}, },
Parent: ClaimData{ Parent: ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"), Value: alphabetClaim(7, "h"),
Position: NewPosition(1, 1), Position: NewPosition(1, 1),
}, },
}, },
ClaimData{ ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000465"), Value: alphabetClaim(4, "e"),
Position: NewPosition(3, 4), Position: NewPosition(3, 4),
}, },
}, },
} }
for _, test := range indices { for _, test := range indices {
res, err := solver.NextMove(test.claim) res, err := solver.NextMove(test.claim, false)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, test.response, res.ClaimData) require.Equal(t, test.response, res.ClaimData)
} }
} }
func TestNoMoveAgainstOwnLevel(t *testing.T) {
maxDepth := 3
mallory := NewAlphabetProvider("abcdepqr", uint64(maxDepth))
solver := NewSolver(maxDepth, mallory)
claim := Claim{
ClaimData: ClaimData{
Value: alphabetClaim(7, "z"),
Position: NewPosition(0, 0),
},
// Root claim has no parent
}
move, err := solver.NextMove(claim, true)
require.Nil(t, move)
require.Nil(t, err)
}
func TestAttemptStep(t *testing.T) { func TestAttemptStep(t *testing.T) {
maxDepth := 3 maxDepth := 3
canonicalProvider := NewAlphabetProvider("abcdefgh", uint64(maxDepth)) canonicalProvider := NewAlphabetProvider("abcdefgh", uint64(maxDepth))
solver := NewSolver(maxDepth, canonicalProvider) solver := NewSolver(maxDepth, canonicalProvider)
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.Put(top)) require.NoError(t, g.Put(top))
require.NoError(t, g.Put(middle)) require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom)) require.NoError(t, g.Put(bottom))
......
...@@ -33,6 +33,11 @@ var ( ...@@ -33,6 +33,11 @@ var (
Usage: "Alphabet Trace (temporary)", Usage: "Alphabet Trace (temporary)",
EnvVars: prefixEnvVars("ALPHABET"), EnvVars: prefixEnvVars("ALPHABET"),
} }
AgreeWithProposedOutputFlag = &cli.BoolFlag{
Name: "agree-with-proposed-output",
Usage: "Temporary hardcoded flag if we agree or disagree with the proposed output.",
EnvVars: prefixEnvVars("AGREE_WITH_PROPOSED_OUTPUT"),
}
// Optional Flags // Optional Flags
) )
...@@ -41,6 +46,7 @@ var requiredFlags = []cli.Flag{ ...@@ -41,6 +46,7 @@ var requiredFlags = []cli.Flag{
L1EthRpcFlag, L1EthRpcFlag,
DGFAddressFlag, DGFAddressFlag,
AlphabetFlag, AlphabetFlag,
AgreeWithProposedOutputFlag,
} }
// optionalFlags is a list of unchecked cli flags // optionalFlags is a list of unchecked cli flags
......
#!/bin/bash
set -euo pipefail
DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd "$DIR"
# build the challenger to keep it up to date
make
cd ..
make devnet-clean
make devnet-up-deploy
DEVNET_SPONSOR="ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
DISPUTE_GAME_PROXY="0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"
CHARLIE_ADDRESS="0xF45B7537828CB2fffBC69996B054c2Aaf36DC778"
CHARLIE_KEY="74feb147d72bfae943e6b4e483410933d9e447d5dc47d52432dcc2c1454dabb7"
MALLORY_ADDRESS="0x4641c704a6c743f73ee1f36C7568Fbf4b80681e4"
MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
echo "----------------------------------------------------------------"
echo " - Fetching balance of the sponsor"
echo " - Balance: $(cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)"
echo "----------------------------------------------------------------"
echo "Funding Charlie"
cast send $CHARLIE_ADDRESS --value 5ether --private-key $DEVNET_SPONSOR
echo "Funding Mallory"
cast send $MALLORY_ADDRESS --value 5ether --private-key $DEVNET_SPONSOR
# Fault game type = 0
GAME_TYPE=0
# Root claim commits to the entire trace.
# Alphabet game claim construction: keccak256(abi.encode(trace_index, trace[trace_index]))
ROOT_CLAIM=$(cast keccak $(cast abi-encode "f(uint256,uint256)" 15 122))
# Extra data is a dynamic `bytes` type that contains the L2 Block Number of the output proposal that the root claim disagrees with
# Doesn't matter right now since we're not deleting outputs, so just set it to 1
EXTRA_DATA=$(cast to-bytes32 1)
echo "Initializing the game"
cast call --private-key $MALLORY_KEY $DISPUTE_GAME_PROXY "create(uint8,bytes32,bytes)" $GAME_TYPE $ROOT_CLAIM $EXTRA_DATA
cast send --private-key $MALLORY_KEY $DISPUTE_GAME_PROXY "create(uint8,bytes32,bytes)" $GAME_TYPE $ROOT_CLAIM $EXTRA_DATA
#!/bin/bash
set -euo pipefail
DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd "$DIR"
DISPUTE_GAME_PROXY="0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"
CHARLIE_ADDRESS="0xF45B7537828CB2fffBC69996B054c2Aaf36DC778"
CHARLIE_KEY="74feb147d72bfae943e6b4e483410933d9e447d5dc47d52432dcc2c1454dabb7"
MALLORY_ADDRESS="0x4641c704a6c743f73ee1f36C7568Fbf4b80681e4"
MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d"
./bin/op-challenger --l1-eth-rpc http://localhost:8545 --alphabet "abcdexyz" --game-address $FAULT_GAME_ADDRESS --private-key $MALLORY_KEY --num-confirmations 1 --agree-with-proposed-output=false
#!/bin/bash
set -euo pipefail
DISPUTE_GAME_PROXY="0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"
FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d"
dir=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd "$dir"
cd ../packages/contracts-bedrock
forge script scripts/FaultDisputeGameViz.s.sol --sig "remote(address)" $FAULT_GAME_ADDRESS --fork-url http://localhost:8545
mv dispute_game.svg "$dir"
...@@ -27,7 +27,7 @@ type Frame struct { ...@@ -27,7 +27,7 @@ type Frame struct {
ID ChannelID `json:"id"` ID ChannelID `json:"id"`
FrameNumber uint16 `json:"frame_number"` FrameNumber uint16 `json:"frame_number"`
Data []byte `json:"data"` Data []byte `json:"data"`
IsLast bool `'json:"is_last"` IsLast bool `json:"is_last"`
} }
// MarshalBinary writes the frame to `w`. // MarshalBinary writes the frame to `w`.
......
...@@ -3,6 +3,7 @@ import os ...@@ -3,6 +3,7 @@ import os
import re import re
import subprocess import subprocess
import sys import sys
import json
from github import Github from github import Github
...@@ -13,9 +14,11 @@ REBUILD_ALL_PATTERNS = [ ...@@ -13,9 +14,11 @@ REBUILD_ALL_PATTERNS = [
r'ops/check-changed/.*', r'ops/check-changed/.*',
r'^go\.mod', r'^go\.mod',
r'^go\.sum', r'^go\.sum',
r'^pnpm-lock\.yaml',
r'ops/check-changed/.*' r'ops/check-changed/.*'
] ]
with open("../../nx.json") as file:
nx_json_data = json.load(file)
REBUILD_ALL_PATTERNS += nx_json_data["implicitDependencies"].keys()
WHITELISTED_BRANCHES = { WHITELISTED_BRANCHES = {
'master', 'master',
......
...@@ -331,6 +331,9 @@ OptimismPortal_Test:test_receive_succeeds() (gas: 127513) ...@@ -331,6 +331,9 @@ OptimismPortal_Test:test_receive_succeeds() (gas: 127513)
OptimismPortal_Test:test_simple_isOutputFinalized_succeeds() (gas: 32971) OptimismPortal_Test:test_simple_isOutputFinalized_succeeds() (gas: 32971)
OptimismPortal_Test:test_unpause_onlyGuardian_reverts() (gas: 46098) OptimismPortal_Test:test_unpause_onlyGuardian_reverts() (gas: 46098)
OptimismPortal_Test:test_unpause_succeeds() (gas: 31756) OptimismPortal_Test:test_unpause_succeeds() (gas: 31756)
PreimageOracle_Test:test_computePreimageKey_succeeds() (gas: 6267)
PreimageOracle_Test:test_loadKeccak256PreimagePart_outOfBoundsOffset_reverts() (gas: 9025)
PreimageOracle_Test:test_loadKeccak256PreimagePart_succeeds() (gas: 77552)
ProxyAdmin_Test:test_chugsplashChangeProxyAdmin_succeeds() (gas: 35586) ProxyAdmin_Test:test_chugsplashChangeProxyAdmin_succeeds() (gas: 35586)
ProxyAdmin_Test:test_chugsplashGetProxyAdmin_succeeds() (gas: 15675) ProxyAdmin_Test:test_chugsplashGetProxyAdmin_succeeds() (gas: 15675)
ProxyAdmin_Test:test_chugsplashGetProxyImplementation_succeeds() (gas: 51084) ProxyAdmin_Test:test_chugsplashGetProxyImplementation_succeeds() (gas: 51084)
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity ^0.8.15;
/// @title PreimageOracle
/// @notice A contract for storing permissioned pre-images.
contract PreimageOracle { contract PreimageOracle {
/// @notice Mapping of pre-image keys to pre-image lengths.
mapping(bytes32 => uint256) public preimageLengths; mapping(bytes32 => uint256) public preimageLengths;
/// @notice Mapping of pre-image keys to pre-image parts.
mapping(bytes32 => mapping(uint256 => bytes32)) public preimageParts; mapping(bytes32 => mapping(uint256 => bytes32)) public preimageParts;
/// @notice Mapping of pre-image keys to pre-image part offsets.
mapping(bytes32 => mapping(uint256 => bool)) public preimagePartOk; mapping(bytes32 => mapping(uint256 => bool)) public preimagePartOk;
function readPreimage(bytes32 key, uint256 offset) /// @notice Reads a pre-image from the oracle.
/// @param _key The key of the pre-image to read.
/// @param _offset The offset of the pre-image to read.
/// @return dat_ The pre-image data.
/// @return datLen_ The length of the pre-image data.
function readPreimage(bytes32 _key, uint256 _offset)
external external
view view
returns (bytes32 dat, uint256 datLen) returns (bytes32 dat_, uint256 datLen_)
{ {
require(preimagePartOk[key][offset], "preimage must exist"); require(preimagePartOk[_key][_offset], "pre-image must exist");
datLen = 32;
uint256 length = preimageLengths[key]; // Calculate the length of the pre-image data
// add 8 for the length-prefix part // Add 8 for the length-prefix part
if (offset + 32 >= length + 8) { datLen_ = 32;
datLen = length + 8 - offset; uint256 length = preimageLengths[_key];
if (_offset + 32 >= length + 8) {
datLen_ = length + 8 - _offset;
} }
dat = preimageParts[key][offset];
// Retrieve the pre-image data
dat_ = preimageParts[_key][_offset];
} }
// TODO(CLI-4104): // TODO(CLI-4104):
...@@ -37,17 +53,47 @@ contract PreimageOracle { ...@@ -37,17 +53,47 @@ contract PreimageOracle {
preimageLengths[key] = size; preimageLengths[key] = size;
} }
// loadKeccak256PreimagePart prepares the pre-image to be read by keccak256 key, /// @notice Computes and returns the key for a pre-image.
// starting at the given offset, up to 32 bytes (clipped at preimage length, if out of data). /// @param _preimage The pre-image.
function loadKeccak256PreimagePart(uint256 partOffset, bytes calldata preimage) external { /// @return key_ The pre-image key.
function computePreimageKey(bytes calldata _preimage) external pure returns (bytes32 key_) {
uint256 size;
assembly {
size := calldataload(0x24)
// Leave slots 0x40 and 0x60 untouched,
// and everything after as scratch-memory.
let ptr := 0x80
// Store size as a big-endian uint64 at the start of pre-image
mstore(ptr, shl(192, size))
ptr := add(ptr, 8)
// Copy preimage payload into memory so we can hash and read it.
calldatacopy(ptr, _preimage.offset, size)
// Compute the pre-image keccak256 hash (aka the pre-image key)
let h := keccak256(ptr, size)
// Mask out prefix byte, replace with type 2 byte
key_ := or(and(h, not(shl(248, 0xFF))), shl(248, 2))
}
}
/// @notice Prepares a pre-image to be read by keccak256 key, starting at
/// the given offset and up to 32 bytes (clipped at pre-image length, if out of data).
/// @param _partOffset The offset of the pre-image to read.
/// @param _preimage The preimage data.
function loadKeccak256PreimagePart(uint256 _partOffset, bytes calldata _preimage) external {
uint256 size; uint256 size;
bytes32 key; bytes32 key;
bytes32 part; bytes32 part;
assembly { assembly {
// len(sig) + len(partOffset) + len(preimage offset) = 4 + 32 + 32 = 0x44 // len(sig) + len(partOffset) + len(preimage offset) = 4 + 32 + 32 = 0x44
size := calldataload(0x44) size := calldataload(0x44)
// revert if part offset >= size+8 (i.e. parts must be within bounds)
if iszero(lt(partOffset, add(size, 8))) { // revert if part offset > size+8 (i.e. parts must be within bounds)
if gt(_partOffset, add(size, 8)) {
revert(0, 0) revert(0, 0)
} }
// we leave solidity slots 0x40 and 0x60 untouched, // we leave solidity slots 0x40 and 0x60 untouched,
...@@ -57,16 +103,16 @@ contract PreimageOracle { ...@@ -57,16 +103,16 @@ contract PreimageOracle {
mstore(ptr, shl(192, size)) mstore(ptr, shl(192, size))
ptr := add(ptr, 8) ptr := add(ptr, 8)
// copy preimage payload into memory so we can hash and read it. // copy preimage payload into memory so we can hash and read it.
calldatacopy(ptr, preimage.offset, size) calldatacopy(ptr, _preimage.offset, size)
// Note that it includes the 8-byte big-endian uint64 length prefix. // Note that it includes the 8-byte big-endian uint64 length prefix.
// this will be zero-padded at the end, since memory at end is clean. // this will be zero-padded at the end, since memory at end is clean.
part := mload(add(sub(ptr, 8), partOffset)) part := mload(add(sub(ptr, 8), _partOffset))
let h := keccak256(ptr, size) // compute preimage keccak256 hash let h := keccak256(ptr, size) // compute preimage keccak256 hash
// mask out prefix byte, replace with type 2 byte // mask out prefix byte, replace with type 2 byte
key := or(and(h, not(shl(248, 0xFF))), shl(248, 2)) key := or(and(h, not(shl(248, 0xFF))), shl(248, 2))
} }
preimagePartOk[key][partOffset] = true; preimagePartOk[key][_partOffset] = true;
preimageParts[key][partOffset] = part; preimageParts[key][_partOffset] = part;
preimageLengths[key] = size; preimageLengths[key] = size;
} }
} }
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
/// @title IPreimageOracle
/// @notice Interface for a preimage oracle.
interface IPreimageOracle {
/// @notice Reads a preimage from the oracle.
/// @param _key The key of the preimage to read.
/// @param _offset The offset of the preimage to read.
/// @return dat_ The preimage data.
/// @return datLen_ The length of the preimage data.
function readPreimage(bytes32 _key, uint256 _offset)
external
view
returns (bytes32 dat_, uint256 datLen_);
/// @notice Computes and returns the key for a pre-image.
/// @param _preimage The pre-image.
/// @return key_ The pre-image key.
function computePreimageKey(bytes calldata _preimage) external pure returns (bytes32 key_);
/// @notice Prepares a preimage to be read by keccak256 key, starting at
/// the given offset and up to 32 bytes (clipped at preimage length, if out of data).
/// @param _partOffset The offset of the preimage to read.
/// @param _preimage The preimage data.
function loadKeccak256PreimagePart(uint256 _partOffset, bytes calldata _preimage) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { Test } from "forge-std/Test.sol";
import { PreimageOracle } from "../cannon/PreimageOracle.sol";
contract PreimageOracle_Test is Test {
PreimageOracle oracle;
/// @notice Sets up the testing suite.
function setUp() public {
oracle = new PreimageOracle();
vm.label(address(oracle), "PreimageOracle");
}
/// @notice Test the pre-image key computation with a known pre-image.
function test_computePreimageKey_succeeds() public {
bytes memory preimage = hex"deadbeef";
bytes32 key = oracle.computePreimageKey(preimage);
bytes32 known = 0x02fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1;
assertEq(key, known);
}
/// @notice Tests that a pre-image is correctly set.
function test_loadKeccak256PreimagePart_succeeds() public {
// Set the pre-image
bytes memory preimage = hex"deadbeef";
bytes32 key = oracle.computePreimageKey(preimage);
uint256 offset = 0;
oracle.loadKeccak256PreimagePart(offset, preimage);
// Validate the pre-image part
bytes32 part = oracle.preimageParts(key, offset);
bytes32 expectedPart = 0x0000000000000004deadbeef0000000000000000000000000000000000000000;
assertEq(part, expectedPart);
// Validate the pre-image length
uint256 length = oracle.preimageLengths(key);
assertEq(length, preimage.length);
// Validate that the pre-image part is set
bool ok = oracle.preimagePartOk(key, offset);
assertTrue(ok);
}
/// @notice Tests that a pre-image cannot be set with an out-of-bounds offset.
function test_loadKeccak256PreimagePart_outOfBoundsOffset_reverts() public {
bytes memory preimage = hex"deadbeef";
uint256 offset = preimage.length + 9;
vm.expectRevert();
oracle.loadKeccak256PreimagePart(offset, preimage);
}
/// @notice Reading a pre-image part that has not been set should revert.
function testFuzz_readPreimage_missingPreimage_reverts(bytes32 key, uint256 offset) public {
vm.expectRevert("pre-image must exist");
oracle.readPreimage(key, offset);
}
}
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment