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

Merge branch 'develop' into inphi/fpvm-oob

parents 1c846a83 5bbfc8ec
---
'@eth-optimism/sdk': patch
---
Fixed missing indexes for multicall support
...@@ -24,7 +24,7 @@ import ( ...@@ -24,7 +24,7 @@ import (
) )
var ( var (
StepBytes4 = crypto.Keccak256([]byte("Step(bytes,bytes)"))[:4] StepBytes4 = crypto.Keccak256([]byte("step(bytes,bytes)"))[:4]
CheatBytes4 = crypto.Keccak256([]byte("cheat(uint256,bytes32,bytes32,uint256)"))[:4] CheatBytes4 = crypto.Keccak256([]byte("cheat(uint256,bytes32,bytes32,uint256)"))[:4]
LoadKeccak256PreimagePartBytes4 = crypto.Keccak256([]byte("loadKeccak256PreimagePart(uint256,bytes)"))[:4] LoadKeccak256PreimagePartBytes4 = crypto.Keccak256([]byte("loadKeccak256PreimagePart(uint256,bytes)"))[:4]
) )
......
...@@ -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
...@@ -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 \
......
package api package api
import ( import (
"math/big"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
...@@ -25,7 +26,7 @@ func (mbv *MockBridgeView) DepositsByAddress(address common.Address) ([]*databas ...@@ -25,7 +26,7 @@ func (mbv *MockBridgeView) DepositsByAddress(address common.Address) ([]*databas
{ {
Deposit: database.Deposit{ Deposit: database.Deposit{
GUID: uuid.MustParse(guid1), GUID: uuid.MustParse(guid1),
InitiatedL1EventGUID: guid2, InitiatedL1EventGUID: uuid.MustParse(guid2),
Tx: database.Transaction{}, Tx: database.Transaction{},
TokenPair: database.TokenPair{}, TokenPair: database.TokenPair{},
}, },
...@@ -34,13 +35,28 @@ func (mbv *MockBridgeView) DepositsByAddress(address common.Address) ([]*databas ...@@ -34,13 +35,28 @@ func (mbv *MockBridgeView) DepositsByAddress(address common.Address) ([]*databas
}, nil }, nil
} }
// DepositsByAddress mocks returning deposits by an address
func (mbv *MockBridgeView) DepositByMessageNonce(nonce *big.Int) (*database.Deposit, error) {
return &database.Deposit{
GUID: uuid.MustParse(guid1),
InitiatedL1EventGUID: uuid.MustParse(guid2),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
}, nil
}
// LatestDepositMessageNonce mocks returning the latest cross domain message nonce for a deposit
func (mbv *MockBridgeView) LatestDepositMessageNonce() (*big.Int, error) {
return big.NewInt(0), nil
}
// WithdrawalsByAddress mocks returning withdrawals by an address // WithdrawalsByAddress mocks returning withdrawals by an address
func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*database.WithdrawalWithTransactionHashes, error) { func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*database.WithdrawalWithTransactionHashes, error) {
return []*database.WithdrawalWithTransactionHashes{ return []*database.WithdrawalWithTransactionHashes{
{ {
Withdrawal: database.Withdrawal{ Withdrawal: database.Withdrawal{
GUID: uuid.MustParse(guid2), GUID: uuid.MustParse(guid2),
InitiatedL2EventGUID: guid1, InitiatedL2EventGUID: uuid.MustParse(guid1),
WithdrawalHash: common.HexToHash("0x456"), WithdrawalHash: common.HexToHash("0x456"),
Tx: database.Transaction{}, Tx: database.Transaction{},
TokenPair: database.TokenPair{}, TokenPair: database.TokenPair{},
...@@ -50,6 +66,33 @@ func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*data ...@@ -50,6 +66,33 @@ func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*data
}, nil }, nil
} }
// WithdrawalsByMessageNonce mocks returning withdrawals by a withdrawal hash
func (mbv *MockBridgeView) WithdrawalByMessageNonce(nonce *big.Int) (*database.Withdrawal, error) {
return &database.Withdrawal{
GUID: uuid.MustParse(guid2),
InitiatedL2EventGUID: uuid.MustParse(guid1),
WithdrawalHash: common.HexToHash("0x456"),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
}, nil
}
// WithdrawalsByHash mocks returning withdrawals by a withdrawal hash
func (mbv *MockBridgeView) WithdrawalByHash(address common.Hash) (*database.Withdrawal, error) {
return &database.Withdrawal{
GUID: uuid.MustParse(guid2),
InitiatedL2EventGUID: uuid.MustParse(guid1),
WithdrawalHash: common.HexToHash("0x456"),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
}, nil
}
// LatestWithdrawalMessageNonce mocks returning the latest cross domain message nonce for a withdrawal
func (mbv *MockBridgeView) LatestWithdrawalMessageNonce() (*big.Int, error) {
return big.NewInt(0), nil
}
func TestHealthz(t *testing.T) { func TestHealthz(t *testing.T) {
api := NewApi(&MockBridgeView{}) api := NewApi(&MockBridgeView{})
request, err := http.NewRequest("GET", "/healthz", nil) request, err := http.NewRequest("GET", "/healthz", nil)
......
...@@ -5,6 +5,8 @@ import ( ...@@ -5,6 +5,8 @@ import (
"errors" "errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm" "gorm.io/gorm"
...@@ -21,6 +23,15 @@ type BlockHeader struct { ...@@ -21,6 +23,15 @@ type BlockHeader struct {
Timestamp uint64 Timestamp uint64
} }
func BlockHeaderFromGethHeader(header *types.Header) BlockHeader {
return BlockHeader{
Hash: header.Hash(),
ParentHash: header.ParentHash,
Number: U256{Int: header.Number},
Timestamp: header.Time,
}
}
type L1BlockHeader struct { type L1BlockHeader struct {
BlockHeader BlockHeader
} }
...@@ -41,8 +52,9 @@ type LegacyStateBatch struct { ...@@ -41,8 +52,9 @@ type LegacyStateBatch struct {
} }
type OutputProposal struct { type OutputProposal struct {
OutputRoot common.Hash `gorm:"primaryKey;serializer:json"` OutputRoot common.Hash `gorm:"primaryKey;serializer:json"`
L2BlockNumber U256 L2BlockNumber U256
L1ContractEventGUID uuid.UUID L1ContractEventGUID uuid.UUID
} }
......
...@@ -2,6 +2,8 @@ package database ...@@ -2,6 +2,8 @@ package database
import ( import (
"errors" "errors"
"fmt"
"math/big"
"gorm.io/gorm" "gorm.io/gorm"
...@@ -30,7 +32,17 @@ type TokenPair struct { ...@@ -30,7 +32,17 @@ type TokenPair struct {
type Deposit struct { type Deposit struct {
GUID uuid.UUID `gorm:"primaryKey"` GUID uuid.UUID `gorm:"primaryKey"`
InitiatedL1EventGUID string InitiatedL1EventGUID uuid.UUID
// Since we're only currently indexing a single StandardBridge,
// the message nonce serves as a unique identifier for this
// deposit. Once this generalizes to more than 1 deployed
// bridge, we need to include the `CrossDomainMessenger` address
// such that the (messenger_addr, nonce) is the unique identifier
// for a bridge msg
SentMessageNonce U256
FinalizedL2EventGUID *uuid.UUID
Tx Transaction `gorm:"embedded"` Tx Transaction `gorm:"embedded"`
TokenPair TokenPair `gorm:"embedded"` TokenPair TokenPair `gorm:"embedded"`
...@@ -43,11 +55,19 @@ type DepositWithTransactionHash struct { ...@@ -43,11 +55,19 @@ type DepositWithTransactionHash struct {
type Withdrawal struct { type Withdrawal struct {
GUID uuid.UUID `gorm:"primaryKey"` GUID uuid.UUID `gorm:"primaryKey"`
InitiatedL2EventGUID string InitiatedL2EventGUID uuid.UUID
// Since we're only currently indexing a single StandardBridge,
// the message nonce serves as a unique identifier for this
// withdrawal. Once this generalizes to more than 1 deployed
// bridge, we need to include the `CrossDomainMessenger` address
// such that the (messenger_addr, nonce) is the unique identifier
// for a bridge msg
SentMessageNonce U256
WithdrawalHash common.Hash `gorm:"serializer:json"` WithdrawalHash common.Hash `gorm:"serializer:json"`
ProvenL1EventGUID *string ProvenL1EventGUID *uuid.UUID
FinalizedL1EventGUID *string FinalizedL1EventGUID *uuid.UUID
Tx Transaction `gorm:"embedded"` Tx Transaction `gorm:"embedded"`
TokenPair TokenPair `gorm:"embedded"` TokenPair TokenPair `gorm:"embedded"`
...@@ -63,16 +83,24 @@ type WithdrawalWithTransactionHashes struct { ...@@ -63,16 +83,24 @@ type WithdrawalWithTransactionHashes struct {
type BridgeView interface { type BridgeView interface {
DepositsByAddress(address common.Address) ([]*DepositWithTransactionHash, error) DepositsByAddress(address common.Address) ([]*DepositWithTransactionHash, error)
DepositByMessageNonce(*big.Int) (*Deposit, error)
LatestDepositMessageNonce() (*big.Int, error)
WithdrawalsByAddress(address common.Address) ([]*WithdrawalWithTransactionHashes, error) WithdrawalsByAddress(address common.Address) ([]*WithdrawalWithTransactionHashes, error)
WithdrawalByMessageNonce(*big.Int) (*Withdrawal, error)
WithdrawalByHash(common.Hash) (*Withdrawal, error)
LatestWithdrawalMessageNonce() (*big.Int, error)
} }
type BridgeDB interface { type BridgeDB interface {
BridgeView BridgeView
StoreDeposits([]*Deposit) error StoreDeposits([]*Deposit) error
MarkFinalizedDepositEvent(uuid.UUID, uuid.UUID) error
StoreWithdrawals([]*Withdrawal) error StoreWithdrawals([]*Withdrawal) error
MarkProvenWithdrawalEvent(string, string) error MarkProvenWithdrawalEvent(uuid.UUID, uuid.UUID) error
MarkFinalizedWithdrawalEvent(string, string) error MarkFinalizedWithdrawalEvent(uuid.UUID, uuid.UUID) error
} }
/** /**
...@@ -114,6 +142,46 @@ func (db *bridgeDB) DepositsByAddress(address common.Address) ([]*DepositWithTra ...@@ -114,6 +142,46 @@ func (db *bridgeDB) DepositsByAddress(address common.Address) ([]*DepositWithTra
return deposits, nil return deposits, nil
} }
func (db *bridgeDB) DepositByMessageNonce(nonce *big.Int) (*Deposit, error) {
var deposit Deposit
result := db.gorm.First(&deposit, "sent_message_nonce = ?", U256{Int: nonce})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &deposit, nil
}
func (db *bridgeDB) LatestDepositMessageNonce() (*big.Int, error) {
var deposit Deposit
result := db.gorm.Order("sent_message_nonce DESC").Take(&deposit)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return deposit.SentMessageNonce.Int, nil
}
func (db *bridgeDB) MarkFinalizedDepositEvent(guid, finalizationEventGUID uuid.UUID) error {
var deposit Deposit
result := db.gorm.First(&deposit, "guid = ?", guid)
if result.Error != nil {
return result.Error
}
deposit.FinalizedL2EventGUID = &finalizationEventGUID
result = db.gorm.Save(&deposit)
return result.Error
}
// Withdrawals // Withdrawals
func (db *bridgeDB) StoreWithdrawals(withdrawals []*Withdrawal) error { func (db *bridgeDB) StoreWithdrawals(withdrawals []*Withdrawal) error {
...@@ -121,7 +189,7 @@ func (db *bridgeDB) StoreWithdrawals(withdrawals []*Withdrawal) error { ...@@ -121,7 +189,7 @@ func (db *bridgeDB) StoreWithdrawals(withdrawals []*Withdrawal) error {
return result.Error return result.Error
} }
func (db *bridgeDB) MarkProvenWithdrawalEvent(guid, provenL1EventGuid string) error { func (db *bridgeDB) MarkProvenWithdrawalEvent(guid, provenL1EventGuid uuid.UUID) error {
var withdrawal Withdrawal var withdrawal Withdrawal
result := db.gorm.First(&withdrawal, "guid = ?", guid) result := db.gorm.First(&withdrawal, "guid = ?", guid)
if result.Error != nil { if result.Error != nil {
...@@ -133,13 +201,17 @@ func (db *bridgeDB) MarkProvenWithdrawalEvent(guid, provenL1EventGuid string) er ...@@ -133,13 +201,17 @@ func (db *bridgeDB) MarkProvenWithdrawalEvent(guid, provenL1EventGuid string) er
return result.Error return result.Error
} }
func (db *bridgeDB) MarkFinalizedWithdrawalEvent(guid, finalizedL1EventGuid string) error { func (db *bridgeDB) MarkFinalizedWithdrawalEvent(guid, finalizedL1EventGuid uuid.UUID) error {
var withdrawal Withdrawal var withdrawal Withdrawal
result := db.gorm.First(&withdrawal, "guid = ?", guid) result := db.gorm.First(&withdrawal, "guid = ?", guid)
if result.Error != nil { if result.Error != nil {
return result.Error return result.Error
} }
if withdrawal.ProvenL1EventGUID == nil {
return fmt.Errorf("withdrawal %s marked finalized prior to being proven", guid)
}
withdrawal.FinalizedL1EventGUID = &finalizedL1EventGuid withdrawal.FinalizedL1EventGUID = &finalizedL1EventGuid
result = db.gorm.Save(&withdrawal) result = db.gorm.Save(&withdrawal)
return result.Error return result.Error
...@@ -167,3 +239,45 @@ func (db *bridgeDB) WithdrawalsByAddress(address common.Address) ([]*WithdrawalW ...@@ -167,3 +239,45 @@ func (db *bridgeDB) WithdrawalsByAddress(address common.Address) ([]*WithdrawalW
return withdrawals, nil return withdrawals, nil
} }
func (db *bridgeDB) WithdrawalByMessageNonce(nonce *big.Int) (*Withdrawal, error) {
var withdrawal Withdrawal
result := db.gorm.First(&withdrawal, "sent_message_nonce = ?", U256{Int: nonce})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &withdrawal, nil
}
func (db *bridgeDB) WithdrawalByHash(hash common.Hash) (*Withdrawal, error) {
var withdrawal Withdrawal
result := db.gorm.First(&withdrawal, "withdrawal_hash = ?", hash.String())
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &withdrawal, nil
}
func (db *bridgeDB) LatestWithdrawalMessageNonce() (*big.Int, error) {
var withdrawal Withdrawal
result := db.gorm.Order("sent_message_nonce DESC").Take(&withdrawal)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return withdrawal.SentMessageNonce.Int, nil
}
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/google/uuid" "github.com/google/uuid"
) )
...@@ -22,6 +23,20 @@ type ContractEvent struct { ...@@ -22,6 +23,20 @@ type ContractEvent struct {
Timestamp uint64 Timestamp uint64
} }
func ContractEventFromGethLog(log *types.Log, timestamp uint64) ContractEvent {
return ContractEvent{
GUID: uuid.New(),
BlockHash: log.BlockHash,
TransactionHash: log.TxHash,
EventSignature: log.Topics[0],
LogIndex: uint64(log.Index),
Timestamp: timestamp,
}
}
type L1ContractEvent struct { type L1ContractEvent struct {
ContractEvent `gorm:"embedded"` ContractEvent `gorm:"embedded"`
} }
......
...@@ -10,14 +10,15 @@ CREATE TABLE IF NOT EXISTS l1_block_headers ( ...@@ -10,14 +10,15 @@ CREATE TABLE IF NOT EXISTS l1_block_headers (
hash VARCHAR NOT NULL PRIMARY KEY, hash VARCHAR NOT NULL PRIMARY KEY,
parent_hash VARCHAR NOT NULL, parent_hash VARCHAR NOT NULL,
number UINT256, number UINT256,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
CREATE TABLE IF NOT EXISTS l2_block_headers ( CREATE TABLE IF NOT EXISTS l2_block_headers (
hash VARCHAR NOT NULL PRIMARY KEY, -- Block header
parent_hash VARCHAR NOT NULL, hash VARCHAR NOT NULL PRIMARY KEY,
number UINT256, parent_hash VARCHAR NOT NULL,
timestamp INTEGER NOT NULL number UINT256,
timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
/** /**
...@@ -30,7 +31,7 @@ CREATE TABLE IF NOT EXISTS l1_contract_events ( ...@@ -30,7 +31,7 @@ CREATE TABLE IF NOT EXISTS l1_contract_events (
transaction_hash VARCHAR NOT NULL, transaction_hash VARCHAR NOT NULL,
event_signature VARCHAR NOT NULL, event_signature VARCHAR NOT NULL,
log_index INTEGER NOT NULL, log_index INTEGER NOT NULL,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
CREATE TABLE IF NOT EXISTS l2_contract_events ( CREATE TABLE IF NOT EXISTS l2_contract_events (
...@@ -39,7 +40,7 @@ CREATE TABLE IF NOT EXISTS l2_contract_events ( ...@@ -39,7 +40,7 @@ CREATE TABLE IF NOT EXISTS l2_contract_events (
transaction_hash VARCHAR NOT NULL, transaction_hash VARCHAR NOT NULL,
event_signature VARCHAR NOT NULL, event_signature VARCHAR NOT NULL,
log_index INTEGER NOT NULL, log_index INTEGER NOT NULL,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
-- Tables that index finalization markers for L2 blocks. -- Tables that index finalization markers for L2 blocks.
...@@ -69,6 +70,10 @@ CREATE TABLE IF NOT EXISTS deposits ( ...@@ -69,6 +70,10 @@ CREATE TABLE IF NOT EXISTS deposits (
-- Event causing the deposit -- Event causing the deposit
initiated_l1_event_guid VARCHAR NOT NULL REFERENCES l1_contract_events(guid), initiated_l1_event_guid VARCHAR NOT NULL REFERENCES l1_contract_events(guid),
sent_message_nonce UINT256 UNIQUE,
-- Finalization marker for the deposit
finalized_l2_event_guid VARCHAR REFERENCES l2_contract_events(guid),
-- Deposit information (do we need indexes on from/to?) -- Deposit information (do we need indexes on from/to?)
from_address VARCHAR NOT NULL, from_address VARCHAR NOT NULL,
...@@ -78,7 +83,7 @@ CREATE TABLE IF NOT EXISTS deposits ( ...@@ -78,7 +83,7 @@ CREATE TABLE IF NOT EXISTS deposits (
l2_token_address VARCHAR NOT NULL, l2_token_address VARCHAR NOT NULL,
amount UINT256, amount UINT256,
data VARCHAR NOT NULL, data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
CREATE TABLE IF NOT EXISTS withdrawals ( CREATE TABLE IF NOT EXISTS withdrawals (
...@@ -86,6 +91,7 @@ CREATE TABLE IF NOT EXISTS withdrawals ( ...@@ -86,6 +91,7 @@ CREATE TABLE IF NOT EXISTS withdrawals (
-- Event causing this withdrawal -- Event causing this withdrawal
initiated_l2_event_guid VARCHAR NOT NULL REFERENCES l2_contract_events(guid), initiated_l2_event_guid VARCHAR NOT NULL REFERENCES l2_contract_events(guid),
sent_message_nonce UINT256 UNIQUE,
-- Multistep (bedrock) process of a withdrawal -- Multistep (bedrock) process of a withdrawal
withdrawal_hash VARCHAR NOT NULL, withdrawal_hash VARCHAR NOT NULL,
...@@ -101,5 +107,5 @@ CREATE TABLE IF NOT EXISTS withdrawals ( ...@@ -101,5 +107,5 @@ CREATE TABLE IF NOT EXISTS withdrawals (
l2_token_address VARCHAR NOT NULL, l2_token_address VARCHAR NOT NULL,
amount UINT256, amount UINT256,
data VARCHAR NOT NULL, data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
package processor
import (
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/google/uuid"
)
type ProcessedContractEventLogIndexKey struct {
header common.Hash
index uint
}
type ProcessedContractEvents struct {
events []*database.ContractEvent
eventsBySignature map[common.Hash][]*database.ContractEvent
eventByLogIndex map[ProcessedContractEventLogIndexKey]*database.ContractEvent
eventLog map[uuid.UUID]*types.Log
}
func NewProcessedContractEvents() *ProcessedContractEvents {
return &ProcessedContractEvents{
events: []*database.ContractEvent{},
eventsBySignature: make(map[common.Hash][]*database.ContractEvent),
eventByLogIndex: make(map[ProcessedContractEventLogIndexKey]*database.ContractEvent),
eventLog: make(map[uuid.UUID]*types.Log),
}
}
func (p *ProcessedContractEvents) AddLog(log *types.Log, time uint64) *database.ContractEvent {
contractEvent := database.ContractEventFromGethLog(log, time)
p.events = append(p.events, &contractEvent)
p.eventsBySignature[contractEvent.EventSignature] = append(p.eventsBySignature[contractEvent.EventSignature], &contractEvent)
p.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index}] = &contractEvent
p.eventLog[contractEvent.GUID] = log
return &contractEvent
}
func UnpackLog(out interface{}, log *types.Log, name string, contractAbi *abi.ABI) error {
eventAbi, ok := contractAbi.Events[name]
if !ok {
return fmt.Errorf("event %s not present in supplied ABI", name)
} else if len(log.Topics) == 0 {
return errors.New("anonymous events are not supported")
} else if log.Topics[0] != eventAbi.ID {
return errors.New("event signature mismatch")
}
err := contractAbi.UnpackIntoInterface(out, name, log.Data)
if err != nil {
return err
}
// handle topics if present
if len(log.Topics) > 1 {
var indexedArgs abi.Arguments
for _, arg := range eventAbi.Inputs {
if arg.Indexed {
indexedArgs = append(indexedArgs, arg)
}
}
// The first topic (event signature) is omitted
err := abi.ParseTopics(out, indexedArgs, log.Topics[1:])
if err != nil {
return err
}
}
return nil
}
This diff is collapsed.
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node" "github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
...@@ -124,8 +125,8 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -124,8 +125,8 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2
return err return err
} }
numLogs := len(logs) l2ContractEvents := make([]*database.L2ContractEvent, len(logs))
l2ContractEvents := make([]*database.L2ContractEvent, numLogs) processedContractEvents := NewProcessedContractEvents()
for i, log := range logs { for i, log := range logs {
header, ok := l2HeaderMap[log.BlockHash] header, ok := l2HeaderMap[log.BlockHash]
if !ok { if !ok {
...@@ -133,16 +134,8 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -133,16 +134,8 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2
return errors.New("parsed log with a block hash not in this batch") return errors.New("parsed log with a block hash not in this batch")
} }
l2ContractEvents[i] = &database.L2ContractEvent{ contractEvent := processedContractEvents.AddLog(&logs[i], header.Time)
ContractEvent: database.ContractEvent{ l2ContractEvents[i] = &database.L2ContractEvent{ContractEvent: *contractEvent}
GUID: uuid.New(),
BlockHash: log.BlockHash,
TransactionHash: log.TxHash,
EventSignature: log.Topics[0],
LogIndex: uint64(log.Index),
Timestamp: header.Time,
},
}
} }
/** Update Database **/ /** Update Database **/
...@@ -153,15 +146,118 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -153,15 +146,118 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2
return err return err
} }
numLogs := len(l2ContractEvents)
if numLogs > 0 { if numLogs > 0 {
processLog.Info("detected contract logs", "size", numLogs) processLog.Info("detected contract logs", "size", numLogs)
err = db.ContractEvents.StoreL2ContractEvents(l2ContractEvents) err = db.ContractEvents.StoreL2ContractEvents(l2ContractEvents)
if err != nil { if err != nil {
return err return err
} }
// forward along contract events to the bridge processor
err = l2BridgeProcessContractEvents(processLog, db, ethClient, processedContractEvents)
if err != nil {
return err
}
} }
// a-ok! // a-ok!
return nil return nil
} }
} }
func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethClient node.EthClient, events *ProcessedContractEvents) error {
rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())
l2ToL1MessagePasserABI, err := bindings.L2ToL1MessagePasserMetaData.GetAbi()
if err != nil {
return err
}
messagePassedEventAbi := l2ToL1MessagePasserABI.Events["MessagePassed"]
// Process New Withdrawals
initiatedWithdrawalEvents, err := StandardBridgeInitiatedEvents(events)
if err != nil {
return err
}
withdrawals := make([]*database.Withdrawal, len(initiatedWithdrawalEvents))
for i, initiatedBridgeEvent := range initiatedWithdrawalEvents {
log := events.eventLog[initiatedBridgeEvent.RawEvent.GUID]
// extract the withdrawal hash from the MessagePassed event
var msgPassedData bindings.L2ToL1MessagePasserMessagePassed
msgPassedLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 1}].GUID]
err := UnpackLog(&msgPassedData, msgPassedLog, messagePassedEventAbi.Name, l2ToL1MessagePasserABI)
if err != nil {
return err
}
withdrawals[i] = &database.Withdrawal{
GUID: uuid.New(),
InitiatedL2EventGUID: initiatedBridgeEvent.RawEvent.GUID,
SentMessageNonce: database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
WithdrawalHash: msgPassedData.WithdrawalHash,
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
Tx: database.Transaction{
FromAddress: initiatedBridgeEvent.From,
ToAddress: initiatedBridgeEvent.To,
Amount: database.U256{Int: initiatedBridgeEvent.Amount},
Data: initiatedBridgeEvent.ExtraData,
Timestamp: initiatedBridgeEvent.RawEvent.Timestamp,
},
}
}
if len(withdrawals) > 0 {
processLog.Info("detected L2StandardBridge withdrawals", "num", len(withdrawals))
err := db.Bridge.StoreWithdrawals(withdrawals)
if err != nil {
return err
}
}
// Finalize Deposits
finalizationBridgeEvents, err := StandardBridgeFinalizedEvents(rawEthClient, events)
if err != nil {
return err
}
for _, finalizedBridgeEvent := range finalizationBridgeEvents {
nonce := finalizedBridgeEvent.CrossDomainMessengerNonce
deposit, err := db.Bridge.DepositByMessageNonce(nonce)
if err != nil {
processLog.Error("error querying associated deposit messsage using nonce", "cross_domain_messenger_nonce", nonce)
return err
} else if deposit == nil {
latestNonce, err := db.Bridge.LatestDepositMessageNonce()
if err != nil {
return err
}
// Check if the L1Processor is behind or really has missed an event
if latestNonce == nil || nonce.Cmp(latestNonce) > 0 {
processLog.Warn("behind on indexed L1 deposits")
return errors.New("waiting for L1Processor to catch up")
} else {
processLog.Crit("missing indexed deposit for this finalization event")
return errors.New("missing deposit message")
}
}
err = db.Bridge.MarkFinalizedDepositEvent(deposit.GUID, finalizedBridgeEvent.RawEvent.GUID)
if err != nil {
processLog.Error("error finalizing deposit", "err", err)
return err
}
}
if len(finalizationBridgeEvents) > 0 {
processLog.Info("finalized L1StandardBridge deposits", "size", len(finalizationBridgeEvents))
}
// a-ok
return nil
}
package processor
import (
"context"
"math/big"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
type OptimismPortalWithdrawalProvenEvent struct {
*bindings.OptimismPortalWithdrawalProven
RawEvent *database.ContractEvent
}
type OptimismPortalProvenWithdrawal struct {
OutputRoot [32]byte
Timestamp *big.Int
L2OutputIndex *big.Int
}
func OptimismPortalWithdrawalProvenEvents(events *ProcessedContractEvents) ([]OptimismPortalWithdrawalProvenEvent, error) {
optimismPortalAbi, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil {
return nil, err
}
processedWithdrawalProvenEvents := events.eventsBySignature[optimismPortalAbi.Events["WithdrawalProven"].ID]
provenEvents := make([]OptimismPortalWithdrawalProvenEvent, len(processedWithdrawalProvenEvents))
for i, provenEvent := range processedWithdrawalProvenEvents {
provenEvents[i] = OptimismPortalWithdrawalProvenEvent{nil, provenEvent}
}
return provenEvents, nil
}
func OptimismPortalQueryProvenWithdrawal(ethClient *ethclient.Client, portalAddress common.Address, withdrawalHash common.Hash) (OptimismPortalProvenWithdrawal, error) {
var provenWithdrawal OptimismPortalProvenWithdrawal
optimismPortalAbi, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil {
return provenWithdrawal, err
}
name := "provenWithdrawals"
txData, err := optimismPortalAbi.Pack(name, withdrawalHash)
if err != nil {
return provenWithdrawal, err
}
callMsg := ethereum.CallMsg{To: &portalAddress, Data: txData}
data, err := ethClient.CallContract(context.Background(), callMsg, nil)
if err != nil {
return provenWithdrawal, err
}
err = optimismPortalAbi.UnpackIntoInterface(&provenWithdrawal, name, data)
if err != nil {
return provenWithdrawal, err
}
return provenWithdrawal, nil
}
...@@ -55,8 +55,8 @@ func (p processor) Start() { ...@@ -55,8 +55,8 @@ func (p processor) Start() {
firstHeader := unprocessedHeaders[0] firstHeader := unprocessedHeaders[0]
lastHeader := unprocessedHeaders[len(unprocessedHeaders)-1] lastHeader := unprocessedHeaders[len(unprocessedHeaders)-1]
batchLog := p.processLog.New("batch_start_block_number", firstHeader.Number, "batch_end_block_number", lastHeader.Number) batchLog := p.processLog.New("batch_start_block_number", firstHeader.Number, "batch_end_block_number", lastHeader.Number)
batchLog.Info("processing batch")
err := p.db.Transaction(func(db *database.DB) error { err := p.db.Transaction(func(db *database.DB) error {
batchLog.Info("processing batch")
return p.processFn(db, unprocessedHeaders) return p.processFn(db, unprocessedHeaders)
}) })
......
package processor
import (
"bytes"
"context"
"errors"
"math/big"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
var (
ethAddress = common.HexToAddress("0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000")
)
type StandardBridgeInitiatedEvent struct {
// We hardcode to ERC20 since ETH can be pseudo-represented as an ERC20 utilizing
// the hardcoded ETH address
*bindings.L1StandardBridgeERC20BridgeInitiated
CrossDomainMessengerNonce *big.Int
RawEvent *database.ContractEvent
}
type StandardBridgeFinalizedEvent struct {
// We hardcode to ERC20 since ETH can be pseudo-represented as an ERC20 utilizing
// the hardcoded ETH address
*bindings.L1StandardBridgeERC20BridgeFinalized
CrossDomainMessengerNonce *big.Int
RawEvent *database.ContractEvent
}
// StandardBridgeInitiatedEvents extracts all initiated bridge events from the contracts that follow the StandardBridge ABI. The
// correlated CrossDomainMessenger nonce is also parsed from the associated messenger events.
func StandardBridgeInitiatedEvents(events *ProcessedContractEvents) ([]StandardBridgeInitiatedEvent, error) {
ethBridgeInitiatedEvents, err := _standardBridgeInitiatedEvents[bindings.L1StandardBridgeETHBridgeInitiated](events)
if err != nil {
return nil, err
}
erc20BridgeInitiatedEvents, err := _standardBridgeInitiatedEvents[bindings.L1StandardBridgeERC20BridgeInitiated](events)
if err != nil {
return nil, err
}
return append(ethBridgeInitiatedEvents, erc20BridgeInitiatedEvents...), nil
}
// StandardBridgeFinalizedEvents extracts all finalization bridge events from the contracts that follow the StandardBridge ABI. The
// correlated CrossDomainMessenger nonce is also parsed by looking at the parameters of the corresponding relayMessage transaction data.
func StandardBridgeFinalizedEvents(rawEthClient *ethclient.Client, events *ProcessedContractEvents) ([]StandardBridgeFinalizedEvent, error) {
ethBridgeFinalizedEvents, err := _standardBridgeFinalizedEvents[bindings.L1StandardBridgeETHBridgeFinalized](rawEthClient, events)
if err != nil {
return nil, err
}
erc20BridgeFinalizedEvents, err := _standardBridgeFinalizedEvents[bindings.L1StandardBridgeERC20BridgeFinalized](rawEthClient, events)
if err != nil {
return nil, err
}
return append(ethBridgeFinalizedEvents, erc20BridgeFinalizedEvents...), nil
}
// parse out eth or erc20 bridge initiated events
func _standardBridgeInitiatedEvents[BridgeEvent bindings.L1StandardBridgeETHBridgeInitiated | bindings.L1StandardBridgeERC20BridgeInitiated](
events *ProcessedContractEvents,
) ([]StandardBridgeInitiatedEvent, error) {
l1StandardBridgeABI, err := bindings.L1StandardBridgeMetaData.GetAbi()
if err != nil {
return nil, err
}
l1CrossDomainMessengerABI, err := bindings.L1CrossDomainMessengerMetaData.GetAbi()
if err != nil {
return nil, err
}
sentMessageEventAbi := l1CrossDomainMessengerABI.Events["SentMessage"]
var tmp BridgeEvent
var eventName string
var finalizeMethodName string
switch any(tmp).(type) {
case bindings.L1StandardBridgeETHBridgeInitiated:
eventName = "ETHBridgeInitiated"
finalizeMethodName = "finalizeBridgeETH"
case bindings.L1StandardBridgeERC20BridgeInitiated:
eventName = "ERC20BridgeInitiated"
finalizeMethodName = "finalizeBridgeERC20"
default:
panic("should not be here")
}
processedInitiatedBridgeEvents := events.eventsBySignature[l1StandardBridgeABI.Events[eventName].ID]
initiatedBridgeEvents := make([]StandardBridgeInitiatedEvent, len(processedInitiatedBridgeEvents))
for i, bridgeInitiatedEvent := range processedInitiatedBridgeEvents {
log := events.eventLog[bridgeInitiatedEvent.GUID]
bridgeData := new(BridgeEvent)
err := UnpackLog(bridgeData, log, eventName, l1StandardBridgeABI)
if err != nil {
return nil, err
}
// Look for the sent message event to extract the associated messager nonce
// - L1: BridgeInitiated -> Portal#DepositTransaction -> SentMessage ...
// - L1: BridgeInitiated -> L2ToL1MessagePasser#MessagePassed -> SentMessage ...
var sentMsgData bindings.L1CrossDomainMessengerSentMessage
sentMsgLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 2}].GUID]
err = UnpackLog(&sentMsgData, sentMsgLog, sentMessageEventAbi.Name, l1CrossDomainMessengerABI)
if err != nil {
return nil, err
}
var erc20BridgeData *bindings.L1StandardBridgeERC20BridgeInitiated
var expectedCrossDomainMessage []byte
switch any(bridgeData).(type) {
case *bindings.L1StandardBridgeETHBridgeInitiated:
ethBridgeData := any(bridgeData).(*bindings.L1StandardBridgeETHBridgeInitiated)
expectedCrossDomainMessage, err = l1StandardBridgeABI.Pack(finalizeMethodName, ethBridgeData.From, ethBridgeData.To, ethBridgeData.Amount, ethBridgeData.ExtraData)
if err != nil {
return nil, err
}
// represent eth bridge as an erc20
erc20BridgeData = &bindings.L1StandardBridgeERC20BridgeInitiated{
// Represent ETH using the hardcoded address
LocalToken: ethAddress, RemoteToken: ethAddress,
// Bridge data
From: ethBridgeData.From, To: ethBridgeData.To, Amount: ethBridgeData.Amount, ExtraData: ethBridgeData.ExtraData,
}
case *bindings.L1StandardBridgeERC20BridgeInitiated:
_temp := any(bridgeData).(bindings.L1StandardBridgeERC20BridgeInitiated)
erc20BridgeData = &_temp
expectedCrossDomainMessage, err = l1StandardBridgeABI.Pack(finalizeMethodName, erc20BridgeData.RemoteToken, erc20BridgeData.LocalToken, erc20BridgeData.From, erc20BridgeData.To, erc20BridgeData.Amount, erc20BridgeData.ExtraData)
if err != nil {
return nil, err
}
}
if !bytes.Equal(sentMsgData.Message, expectedCrossDomainMessage) {
return nil, errors.New("bridge cross domain message mismatch")
}
initiatedBridgeEvents[i] = StandardBridgeInitiatedEvent{erc20BridgeData, sentMsgData.MessageNonce, bridgeInitiatedEvent}
}
return initiatedBridgeEvents, nil
}
// parse out eth or erc20 bridge finalization events
func _standardBridgeFinalizedEvents[BridgeEvent bindings.L1StandardBridgeETHBridgeFinalized | bindings.L1StandardBridgeERC20BridgeFinalized](
rawEthClient *ethclient.Client,
events *ProcessedContractEvents,
) ([]StandardBridgeFinalizedEvent, error) {
l1StandardBridgeABI, err := bindings.L1StandardBridgeMetaData.GetAbi()
if err != nil {
return nil, err
}
l1CrossDomainMessengerABI, err := bindings.L1CrossDomainMessengerMetaData.GetAbi()
if err != nil {
return nil, err
}
relayedMessageEventAbi := l1CrossDomainMessengerABI.Events["RelayedMessage"]
relayMessageMethodAbi := l1CrossDomainMessengerABI.Methods["relayMessage"]
var bridgeData BridgeEvent
var eventName string
switch any(bridgeData).(type) {
case bindings.L1StandardBridgeETHBridgeFinalized:
eventName = "ETHBridgeFinalized"
case bindings.L1StandardBridgeERC20BridgeFinalized:
eventName = "ERC20BridgeFinalized"
default:
panic("should not be here")
}
processedFinalizedBridgeEvents := events.eventsBySignature[l1StandardBridgeABI.Events[eventName].ID]
finalizedBridgeEvents := make([]StandardBridgeFinalizedEvent, len(processedFinalizedBridgeEvents))
for i, bridgeFinalizedEvent := range processedFinalizedBridgeEvents {
log := events.eventLog[bridgeFinalizedEvent.GUID]
var bridgeData BridgeEvent
err := UnpackLog(&bridgeData, log, eventName, l1StandardBridgeABI)
if err != nil {
return nil, err
}
// Look for the RelayedMessage event that follows right after the BridgeFinalized Event
relayedMsgLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 1}].GUID]
if relayedMsgLog.Topics[0] != relayedMessageEventAbi.ID {
return nil, errors.New("unexpected bridge event ordering")
}
// There's no way to extract the nonce on the relayed message event. we can extract
// the nonce by unpacking the transaction input for the `relayMessage` transaction
tx, isPending, err := rawEthClient.TransactionByHash(context.Background(), relayedMsgLog.TxHash)
if err != nil || isPending {
return nil, errors.New("unable to query relayMessage tx for bridge finalization event")
}
txData := tx.Data()
if !bytes.Equal(txData[:4], relayMessageMethodAbi.ID) {
return nil, errors.New("bridge finalization event does not match relayMessage tx invocation")
}
inputsMap := make(map[string]interface{})
err = relayMessageMethodAbi.Inputs.UnpackIntoMap(inputsMap, txData[4:])
if err != nil {
return nil, err
}
nonce, ok := inputsMap["_nonce"].(*big.Int)
if !ok {
return nil, errors.New("unable to extract `_nonce` parameter from relayMessage transaction")
}
var erc20BridgeData *bindings.L1StandardBridgeERC20BridgeFinalized
switch any(bridgeData).(type) {
case bindings.L1StandardBridgeETHBridgeInitiated:
ethBridgeData := any(bridgeData).(bindings.L1StandardBridgeETHBridgeFinalized)
erc20BridgeData = &bindings.L1StandardBridgeERC20BridgeFinalized{
// Represent ETH using the hardcoded address
LocalToken: ethAddress, RemoteToken: ethAddress,
// Bridge data
From: ethBridgeData.From, To: ethBridgeData.To, Amount: ethBridgeData.Amount, ExtraData: ethBridgeData.ExtraData,
}
case bindings.L1StandardBridgeERC20BridgeInitiated:
_temp := any(bridgeData).(bindings.L1StandardBridgeERC20BridgeFinalized)
erc20BridgeData = &_temp
}
finalizedBridgeEvents[i] = StandardBridgeFinalizedEvent{erc20BridgeData, nonce, bridgeFinalizedEvent}
}
return finalizedBridgeEvents, nil
}
...@@ -4,7 +4,12 @@ ...@@ -4,7 +4,12 @@
"defaultBase": "develop" "defaultBase": "develop"
}, },
"implicitDependencies": { "implicitDependencies": {
"nx.json": "*" "nx.json": "*",
"tsconfig.json": "*",
".foundryrc": "*",
"pnpm.lock.yaml": "*",
".npmrc": "*",
".nvmrc": "*"
}, },
"tasksRunnerOptions": { "tasksRunnerOptions": {
"default": { "default": {
......
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, 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
package fault
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
// setupFaultDisputeGame deploys the FaultDisputeGame contract to a simulated backend
func setupFaultDisputeGame() (common.Address, *bind.TransactOpts, *backends.SimulatedBackend, *bindings.FaultDisputeGame, error) {
privateKey, err := crypto.GenerateKey()
from := crypto.PubkeyToAddress(privateKey.PublicKey)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
opts, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337))
if err != nil {
return common.Address{}, nil, nil, nil, err
}
backend := backends.NewSimulatedBackend(core.GenesisAlloc{from: {Balance: big.NewInt(params.Ether)}}, 50_000_000)
_, _, contract, err := bindings.DeployFaultDisputeGame(
opts,
backend,
[32]byte{0x01},
big.NewInt(15),
common.Address{0xdd},
)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
return from, opts, backend, contract, nil
}
// TestBuildFaultDefendData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultDefendData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
responder, _ := newTestFaultResponder(t, false)
data, err := responder.buildFaultDefendData(1, [32]byte{0x02, 0x03})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Defend(opts, big.NewInt(1), [32]byte{0x02, 0x03})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
// TestBuildFaultAttackData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultAttackData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
responder, _ := newTestFaultResponder(t, false)
data, err := responder.buildFaultAttackData(1, [32]byte{0x02, 0x03})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Attack(opts, big.NewInt(1), [32]byte{0x02, 0x03})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
// TestBuildFaultStepData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultStepData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
responder, _ := newTestFaultResponder(t, false)
data, err := responder.buildStepTxData(StepCallData{
ClaimIndex: 2,
IsAttack: false,
StateData: []byte{0x01},
Proof: []byte{0x02},
})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Step(opts, big.NewInt(2), false, []byte{0x01}, []byte{0x02})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
...@@ -2,70 +2,117 @@ package fault ...@@ -2,70 +2,117 @@ package fault
import ( import (
"context" "context"
"sync" "errors"
"fmt"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type Agent struct { type Agent struct {
mu sync.Mutex
game Game
solver *Solver solver *Solver
trace TraceProvider trace TraceProvider
loader Loader
responder Responder responder Responder
maxDepth int maxDepth int
log log.Logger log log.Logger
} }
func NewAgent(game Game, maxDepth int, trace TraceProvider, responder Responder, log log.Logger) Agent { func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, log log.Logger) Agent {
return Agent{ return Agent{
game: game,
solver: NewSolver(maxDepth, trace), solver: NewSolver(maxDepth, trace),
trace: trace, trace: trace,
loader: loader,
responder: responder, responder: responder,
maxDepth: maxDepth, maxDepth: maxDepth,
log: log, log: log,
} }
} }
// AddClaim stores a claim in the local state. // Act iterates the game & performs all of the next actions.
// This function shares a lock with PerformActions. func (a *Agent) Act() error {
func (a *Agent) AddClaim(claim Claim) error { game, err := a.newGameFromContracts(context.Background())
a.mu.Lock() if err != nil {
defer a.mu.Unlock() a.log.Error("Failed to create new game", "err", err)
return a.game.Put(claim) return err
}
// Create counter claims
for _, claim := range game.Claims() {
if err := a.move(claim, game); err != nil {
log.Error("Failed to move", "err", err)
}
}
// Step on all leaf claims
for _, claim := range game.Claims() {
if err := a.step(claim, game); err != nil {
log.Error("Failed to step", "err", err)
}
}
return nil
} }
// PerformActions iterates the game & performs all of the next actions. // newGameFromContracts initializes a new game state from the state in the contract
// Note: PerformActions & AddClaim share a lock so the responder cannot func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) {
// call AddClaim on the same thread. claims, err := a.loader.FetchClaims(ctx)
func (a *Agent) PerformActions() { if err != nil {
a.mu.Lock() return nil, fmt.Errorf("failed to fetch claims: %w", err)
defer a.mu.Unlock() }
for _, claim := range a.game.Claims() { if len(claims) == 0 {
_ = a.move(claim) return nil, errors.New("no claims")
}
game := NewGameState(claims[0], uint64(a.maxDepth))
if err := game.PutAll(claims[1:]); err != nil {
return nil, fmt.Errorf("failed to load claims into the local state: %w", err)
} }
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) error { func (a *Agent) move(claim Claim, game Game) error {
nextMove, err := a.solver.NextMove(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 a.game.IsDuplicate(move) { if game.IsDuplicate(move) {
log.Debug("Duplicate move") log.Debug("Duplicate move")
return nil return nil
} }
log.Info("Performing move") log.Info("Performing move")
return a.responder.Respond(context.TODO(), move) return a.responder.Respond(context.TODO(), move)
} }
// step determines & executes the next step against a leaf claim through the responder
func (a *Agent) step(claim Claim, game Game) error {
if claim.Depth() != a.maxDepth {
return nil
}
a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
step, err := a.solver.AttemptStep(claim)
if err != nil {
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
}
a.log.Info("Performing step",
"depth", step.LeafClaim.Depth(), "index_at_depth", step.LeafClaim.IndexAtDepth(), "value", step.LeafClaim.Value,
"is_attack", step.IsAttack, "prestate_trace_index", step.PreStateTraceIndex)
callData := StepCallData{
ClaimIndex: uint64(step.LeafClaim.ContractIndex),
IsAttack: step.IsAttack,
StateData: stateData,
}
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)
} }
...@@ -36,9 +36,8 @@ type Game interface { ...@@ -36,9 +36,8 @@ type Game interface {
} }
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.
...@@ -54,9 +53,8 @@ type gameState struct { ...@@ -54,9 +53,8 @@ type gameState struct {
func NewGameState(root Claim, depth uint64) *gameState { func NewGameState(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, root: root.ClaimData,
...@@ -87,9 +85,8 @@ func (g *gameState) Put(claim Claim) error { ...@@ -87,9 +85,8 @@ func (g *gameState) Put(claim Claim) error {
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
} }
......
...@@ -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)
......
...@@ -2,74 +2,74 @@ package fault ...@@ -2,74 +2,74 @@ package fault
import ( import (
"context" "context"
"os"
"time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type Orchestrator struct { type Orchestrator struct {
agents []Agent agents []Agent
outputChs []chan Claim claims []Claim
responses chan Claim steps []StepCallData
// tracking when to exit
claimLen, stepLen, step int
} }
func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, root Claim) Orchestrator { func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, root Claim) Orchestrator {
o := Orchestrator{ o := Orchestrator{
responses: make(chan Claim, 100), agents: make([]Agent, len(traces)),
outputChs: make([]chan Claim, len(traces)), claims: []Claim{root},
agents: make([]Agent, len(traces)), steps: make([]StepCallData, 0),
} }
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 {
game := NewGameState(root, maxDepth) o.agents[i] = NewAgent(&o, int(maxDepth), trace, &o, log.New("role", names[i]))
o.agents[i] = NewAgent(game, int(maxDepth), trace, &o, log.New("role", names[i]))
o.outputChs[i] = make(chan Claim)
} }
return o return o
} }
func (o *Orchestrator) Respond(_ context.Context, response Claim) error { func (o *Orchestrator) Respond(_ context.Context, response Claim) error {
o.responses <- response response.ContractIndex = len(o.claims)
o.claims = append(o.claims, response)
return nil return nil
} }
func (o *Orchestrator) Step(ctx context.Context, stepData StepCallData) error { func (o *Orchestrator) Step(_ context.Context, stepData StepCallData) error {
log.Info("Step recorded", "step", stepData)
o.steps = append(o.steps, stepData)
return nil return nil
} }
func (o *Orchestrator) Start() { func (o *Orchestrator) FetchClaims(ctx context.Context) ([]Claim, error) {
for i := 0; i < len(o.agents); i++ { c := make([]Claim, len(o.claims))
go runAgent(&o.agents[i], o.outputChs[i]) copy(c, o.claims)
} return c, nil
o.responderThread()
} }
func runAgent(agent *Agent, claimCh <-chan Claim) { func (o *Orchestrator) Start() {
for { for {
agent.PerformActions() for _, a := range o.agents {
// Note: Should drain the channel here _ = a.Act()
claim := <-claimCh }
_ = agent.AddClaim(claim) if o.shouldExit() {
log.Info("exiting")
return
}
} }
} }
func (o *Orchestrator) responderThread() { func (o *Orchestrator) shouldExit() bool {
timer := time.NewTimer(200 * time.Millisecond) cl := o.claimLen
defer timer.Stop() sl := o.stepLen
for {
select { o.claimLen = len(o.claims)
case resp := <-o.responses: o.stepLen = len(o.steps)
timer.Reset(200 * time.Millisecond)
for _, ch := range o.outputChs {
// Copy it. Should be immutable, but be sure.
resp := resp
ch <- resp
}
case <-timer.C:
os.Exit(0)
}
noProgress := o.claimLen == cl && o.stepLen == sl
if noProgress {
o.step = o.step + 1
} else {
o.step = 0
} }
return noProgress && o.step == 1
} }
...@@ -104,7 +104,6 @@ func (r *faultResponder) sendTxAndWait(ctx context.Context, txData []byte) error ...@@ -104,7 +104,6 @@ func (r *faultResponder) sendTxAndWait(ctx context.Context, txData []byte) error
func (r *faultResponder) buildStepTxData(stepData StepCallData) ([]byte, error) { func (r *faultResponder) buildStepTxData(stepData StepCallData) ([]byte, error) {
return r.fdgAbi.Pack( return r.fdgAbi.Pack(
"step", "step",
big.NewInt(int64(stepData.StateIndex)),
big.NewInt(int64(stepData.ClaimIndex)), big.NewInt(int64(stepData.ClaimIndex)),
stepData.IsAttack, stepData.IsAttack,
stepData.StateData, stepData.StateData,
......
...@@ -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
} }
...@@ -31,14 +30,14 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) { ...@@ -31,14 +30,14 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) {
} }
type StepData struct { type StepData struct {
LeafClaim Claim LeafClaim Claim
StateClaim Claim IsAttack bool
IsAttack bool PreStateTraceIndex uint64
} }
// AttemptStep determines what step should occur for a given leaf claim. // 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. // An error will be returned if the claim is not at the max depth.
func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) { func (s *Solver) AttemptStep(claim Claim) (StepData, error) {
if claim.Depth() != s.gameDepth { if claim.Depth() != s.gameDepth {
return StepData{}, errors.New("cannot step on non-leaf claims") return StepData{}, errors.New("cannot step on non-leaf claims")
} }
...@@ -46,20 +45,15 @@ func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) { ...@@ -46,20 +45,15 @@ func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) {
if err != nil { if err != nil {
return StepData{}, err return StepData{}, err
} }
var selectorFn func(Claim) (Claim, error) index := claim.TraceIndex(s.gameDepth)
if claimCorrect { // TODO(CLI-4198): Handle case where we dispute trace index 0
selectorFn = state.PostStateClaim if !claimCorrect {
} else { index -= 1
selectorFn = state.PreStateClaim
}
stateClaim, err := selectorFn(claim)
if err != nil {
return StepData{}, err
} }
return StepData{ return StepData{
LeafClaim: claim, LeafClaim: claim,
StateClaim: stateClaim, IsAttack: !claimCorrect,
IsAttack: claimCorrect, PreStateTraceIndex: index,
}, nil }, nil
} }
...@@ -117,8 +111,9 @@ func (s *Solver) attack(claim Claim) (*Claim, error) { ...@@ -117,8 +111,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
} }
...@@ -130,8 +125,9 @@ func (s *Solver) defend(claim Claim) (*Claim, error) { ...@@ -130,8 +125,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,55 +23,51 @@ func TestSolver_NextMove_Opponent(t *testing.T) { ...@@ -18,55 +23,51 @@ 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),
}, },
}, },
...@@ -89,12 +90,11 @@ func TestAttemptStep(t *testing.T) { ...@@ -89,12 +90,11 @@ func TestAttemptStep(t *testing.T) {
require.NoError(t, g.Put(middle)) require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom)) require.NoError(t, g.Put(bottom))
step, err := solver.AttemptStep(bottom, g) step, err := solver.AttemptStep(bottom)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, bottom, step.LeafClaim) require.Equal(t, bottom, step.LeafClaim)
require.Equal(t, middle, step.StateClaim)
require.True(t, step.IsAttack) require.True(t, step.IsAttack)
_, err = solver.AttemptStep(middle, g) _, err = solver.AttemptStep(middle)
require.Error(t, err) require.Error(t, err)
} }
...@@ -14,7 +14,6 @@ var ( ...@@ -14,7 +14,6 @@ var (
// StepCallData encapsulates the data needed to perform a step. // StepCallData encapsulates the data needed to perform a step.
type StepCallData struct { type StepCallData struct {
StateIndex uint64
ClaimIndex uint64 ClaimIndex uint64
IsAttack bool IsAttack bool
StateData []byte StateData []byte
......
#!/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
#!/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"
...@@ -38,11 +38,11 @@ func LightApplicationScoreParams(cfg *rollup.Config) ApplicationScoreParams { ...@@ -38,11 +38,11 @@ func LightApplicationScoreParams(cfg *rollup.Config) ApplicationScoreParams {
ValidResponseWeight: 0.5, ValidResponseWeight: 0.5,
ValidResponseDecay: ScoreDecay(tenEpochs, slot), ValidResponseDecay: ScoreDecay(tenEpochs, slot),
// Takes 20 error responses to reach the default ban threshold of -100 // Takes 10 error responses to reach the default gossip threshold of -10
// But at most we track 10. These errors include not supporting p2p sync // But at most we track 9. These errors include not supporting p2p sync
// so we don't (yet) want to ban a peer based on this measure alone. // so we don't (yet) want to ignore gossip from a peer based on this measure alone.
ErrorResponseCap: 10, ErrorResponseCap: 9,
ErrorResponseWeight: -5, ErrorResponseWeight: -1,
ErrorResponseDecay: ScoreDecay(tenEpochs, slot), ErrorResponseDecay: ScoreDecay(tenEpochs, slot),
// Takes 5 rejected payloads to reach the default ban threshold of -100 // Takes 5 rejected payloads to reach the default ban threshold of -100
......
...@@ -15,7 +15,7 @@ type Metrics interface { ...@@ -15,7 +15,7 @@ type Metrics interface {
RecordL1Ref(name string, ref eth.L1BlockRef) RecordL1Ref(name string, ref eth.L1BlockRef)
RecordL2Ref(name string, ref eth.L2BlockRef) RecordL2Ref(name string, ref eth.L2BlockRef)
RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID) RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID)
RecordChannelInputBytes(inputCompresedBytes int) RecordChannelInputBytes(inputCompressedBytes int)
} }
type L1Fetcher interface { type L1Fetcher interface {
......
...@@ -21,7 +21,7 @@ type Metrics interface { ...@@ -21,7 +21,7 @@ type Metrics interface {
RecordL1Ref(name string, ref eth.L1BlockRef) RecordL1Ref(name string, ref eth.L1BlockRef)
RecordL2Ref(name string, ref eth.L2BlockRef) RecordL2Ref(name string, ref eth.L2BlockRef)
RecordChannelInputBytes(inputCompresedBytes int) RecordChannelInputBytes(inputCompressedBytes int)
RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID) RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID)
......
...@@ -11,7 +11,7 @@ type TestDerivationMetrics struct { ...@@ -11,7 +11,7 @@ type TestDerivationMetrics struct {
FnRecordL1Ref func(name string, ref eth.L1BlockRef) FnRecordL1Ref func(name string, ref eth.L1BlockRef)
FnRecordL2Ref func(name string, ref eth.L2BlockRef) FnRecordL2Ref func(name string, ref eth.L2BlockRef)
FnRecordUnsafePayloads func(length uint64, memSize uint64, next eth.BlockID) FnRecordUnsafePayloads func(length uint64, memSize uint64, next eth.BlockID)
FnRecordChannelInputBytes func(inputCompresedBytes int) FnRecordChannelInputBytes func(inputCompressedBytes int)
} }
func (t *TestDerivationMetrics) RecordL1ReorgDepth(d uint64) { func (t *TestDerivationMetrics) RecordL1ReorgDepth(d uint64) {
...@@ -38,9 +38,9 @@ func (t *TestDerivationMetrics) RecordUnsafePayloadsBuffer(length uint64, memSiz ...@@ -38,9 +38,9 @@ func (t *TestDerivationMetrics) RecordUnsafePayloadsBuffer(length uint64, memSiz
} }
} }
func (t *TestDerivationMetrics) RecordChannelInputBytes(inputCompresedBytes int) { func (t *TestDerivationMetrics) RecordChannelInputBytes(inputCompressedBytes int) {
if t.FnRecordChannelInputBytes != nil { if t.FnRecordChannelInputBytes != nil {
t.FnRecordChannelInputBytes(inputCompresedBytes) t.FnRecordChannelInputBytes(inputCompressedBytes)
} }
} }
......
...@@ -2,7 +2,7 @@ FROM ethereum/client-go:v1.11.2 ...@@ -2,7 +2,7 @@ FROM ethereum/client-go:v1.11.2
RUN apk add --no-cache jq RUN apk add --no-cache jq
COPY entrypoint.sh /entrypoint.sh COPY entrypoint-l1.sh /entrypoint.sh
VOLUME ["/db"] VOLUME ["/db"]
......
...@@ -2,7 +2,7 @@ FROM us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:optimism ...@@ -2,7 +2,7 @@ FROM us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:optimism
RUN apk add --no-cache jq RUN apk add --no-cache jq
COPY entrypoint.sh /entrypoint.sh COPY entrypoint-l2.sh /entrypoint.sh
VOLUME ["/db"] VOLUME ["/db"]
......
#!/bin/sh
set -exu
VERBOSITY=${GETH_VERBOSITY:-3}
GETH_DATA_DIR=/db
GETH_CHAINDATA_DIR="$GETH_DATA_DIR/geth/chaindata"
GENESIS_FILE_PATH="${GENESIS_FILE_PATH:-/genesis.json}"
CHAIN_ID=$(cat "$GENESIS_FILE_PATH" | jq -r .config.chainId)
RPC_PORT="${RPC_PORT:-8545}"
WS_PORT="${WS_PORT:-8546}"
if [ ! -d "$GETH_CHAINDATA_DIR" ]; then
echo "$GETH_CHAINDATA_DIR missing, running init"
echo "Initializing genesis."
geth --verbosity="$VERBOSITY" init \
--datadir="$GETH_DATA_DIR" \
"$GENESIS_FILE_PATH"
else
echo "$GETH_CHAINDATA_DIR exists."
fi
# Warning: Archive mode is required, otherwise old trie nodes will be
# pruned within minutes of starting the devnet.
exec geth \
--datadir="$GETH_DATA_DIR" \
--verbosity="$VERBOSITY" \
--http \
--http.corsdomain="*" \
--http.vhosts="*" \
--http.addr=0.0.0.0 \
--http.port="$RPC_PORT" \
--http.api=web3,debug,eth,txpool,net,engine \
--ws \
--ws.addr=0.0.0.0 \
--ws.port="$WS_PORT" \
--ws.origins="*" \
--ws.api=debug,eth,txpool,net,engine \
--syncmode=full \
--nodiscover \
--maxpeers=0 \
--networkid=$CHAIN_ID \
--authrpc.addr="0.0.0.0" \
--authrpc.port="8551" \
--authrpc.vhosts="*" \
--authrpc.jwtsecret=/config/jwt-secret.txt \
--gcmode=archive \
--metrics \
--metrics.addr=0.0.0.0 \
--metrics.port=6060 \
"$@"
...@@ -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',
......
...@@ -6,10 +6,10 @@ WORKDIR /opt ...@@ -6,10 +6,10 @@ WORKDIR /opt
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y curl build-essential git && \ apt-get install -y curl build-essential git && \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \
chmod +x ./rustup.sh && \ chmod +x ./rustup.sh && \
./rustup.sh -y ./rustup.sh -y
COPY ./.foundryrc ./.foundryrc COPY ./.foundryrc ./.foundryrc
...@@ -21,10 +21,10 @@ RUN git clone https://github.com/foundry-rs/foundry.git ./foundry \ ...@@ -21,10 +21,10 @@ RUN git clone https://github.com/foundry-rs/foundry.git ./foundry \
WORKDIR /opt/foundry WORKDIR /opt/foundry
RUN source $HOME/.profile && \ RUN source $HOME/.profile && \
cargo build --release && \ cargo build --release && \
strip /opt/foundry/target/release/forge && \ strip /opt/foundry/target/release/forge && \
strip /opt/foundry/target/release/cast && \ strip /opt/foundry/target/release/cast && \
strip /opt/foundry/target/release/anvil strip /opt/foundry/target/release/anvil
FROM ethereum/client-go:alltools-v1.10.25 as geth FROM ethereum/client-go:alltools-v1.10.25 as geth
...@@ -105,3 +105,8 @@ RUN echo "downloading mockery tool" && \ ...@@ -105,3 +105,8 @@ RUN echo "downloading mockery tool" && \
cp mockery-tmp-dir/mockery /usr/local/bin/mockery && \ cp mockery-tmp-dir/mockery /usr/local/bin/mockery && \
chmod +x /usr/local/bin/mockery && \ chmod +x /usr/local/bin/mockery && \
rm -rf mockery-tmp-dir rm -rf mockery-tmp-dir
RUN echo "installing mips binutils" && \
apt-get install -y binutils-mips-linux-gnu python3 python3-pip && \
pip3 install capstone pyelftools
...@@ -7,7 +7,7 @@ import { ...@@ -7,7 +7,7 @@ import {
waitForProvider, waitForProvider,
} from '@eth-optimism/common-ts' } from '@eth-optimism/common-ts'
import { getChainId, compareAddrs } from '@eth-optimism/core-utils' import { getChainId, compareAddrs } from '@eth-optimism/core-utils'
import { Provider } from '@ethersproject/abstract-provider' import { Provider, TransactionResponse } from '@ethersproject/abstract-provider'
import mainnetConfig from '@eth-optimism/contracts-bedrock/deploy-config/mainnet.json' import mainnetConfig from '@eth-optimism/contracts-bedrock/deploy-config/mainnet.json'
import goerliConfig from '@eth-optimism/contracts-bedrock/deploy-config/goerli.json' import goerliConfig from '@eth-optimism/contracts-bedrock/deploy-config/goerli.json'
import l2OutputOracleArtifactsMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/L2OutputOracleProxy.json' import l2OutputOracleArtifactsMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/L2OutputOracleProxy.json'
...@@ -101,7 +101,7 @@ export class WalletMonService extends BaseServiceV2< ...@@ -101,7 +101,7 @@ export class WalletMonService extends BaseServiceV2<
unexpectedCalls: { unexpectedCalls: {
type: Counter, type: Counter,
desc: 'Number of unexpected wallets', desc: 'Number of unexpected wallets',
labels: ['wallet', 'target', 'nickname'], labels: ['wallet', 'target', 'nickname', 'transactionHash'],
}, },
unexpectedRpcErrors: { unexpectedRpcErrors: {
type: Counter, type: Counter,
...@@ -150,7 +150,7 @@ export class WalletMonService extends BaseServiceV2< ...@@ -150,7 +150,7 @@ export class WalletMonService extends BaseServiceV2<
number: block.number, number: block.number,
}) })
const transactions = [] const transactions: TransactionResponse[] = []
for (const txHash of block.transactions) { for (const txHash of block.transactions) {
const t = await this.options.rpc.getTransaction(txHash) const t = await this.options.rpc.getTransaction(txHash)
transactions.push(t) transactions.push(t)
...@@ -175,11 +175,13 @@ export class WalletMonService extends BaseServiceV2< ...@@ -175,11 +175,13 @@ export class WalletMonService extends BaseServiceV2<
nickname: account.label, nickname: account.label,
wallet: account.address, wallet: account.address,
target: transaction.to, target: transaction.to,
transactionHash: transaction.hash,
}) })
this.logger.error('Unexpected call detected', { this.logger.error('Unexpected call detected', {
nickname: account.label, nickname: account.label,
address: account.address, address: account.address,
target: transaction.to, target: transaction.to,
transactionHash: transaction.hash,
}) })
} }
} }
......
...@@ -379,7 +379,7 @@ contract MIPS { ...@@ -379,7 +379,7 @@ contract MIPS {
} }
// will revert if any required input state is missing // will revert if any required input state is missing
function Step(bytes calldata stateData, bytes calldata proof) public returns (bytes32) { function step(bytes calldata stateData, bytes calldata proof) public returns (bytes32) {
State memory state; State memory state;
// packed data is ~6 times smaller // packed data is ~6 times smaller
assembly { assembly {
......
...@@ -1563,9 +1563,17 @@ export class CrossChainMessenger { ...@@ -1563,9 +1563,17 @@ export class CrossChainMessenger {
opts?: { opts?: {
signer?: Signer signer?: Signer
overrides?: Overrides overrides?: Overrides
} },
/**
* The index of the withdrawal if multiple are made with multicall
*/
messageIndex: number = 0
): Promise<TransactionResponse> { ): Promise<TransactionResponse> {
const tx = await this.populateTransaction.proveMessage(message, opts) const tx = await this.populateTransaction.proveMessage(
message,
opts,
messageIndex
)
return (opts?.signer || this.l1Signer).sendTransaction(tx) return (opts?.signer || this.l1Signer).sendTransaction(tx)
} }
...@@ -1584,10 +1592,18 @@ export class CrossChainMessenger { ...@@ -1584,10 +1592,18 @@ export class CrossChainMessenger {
opts?: { opts?: {
signer?: Signer signer?: Signer
overrides?: PayableOverrides overrides?: PayableOverrides
} },
/**
* The index of the withdrawal if multiple are made with multicall
*/
messageIndex = 0
): Promise<TransactionResponse> { ): Promise<TransactionResponse> {
return (opts?.signer || this.l1Signer).sendTransaction( return (opts?.signer || this.l1Signer).sendTransaction(
await this.populateTransaction.finalizeMessage(message, opts) await this.populateTransaction.finalizeMessage(
message,
opts,
messageIndex
)
) )
} }
...@@ -1810,7 +1826,6 @@ export class CrossChainMessenger { ...@@ -1810,7 +1826,6 @@ export class CrossChainMessenger {
opts?: { opts?: {
overrides?: Overrides overrides?: Overrides
}, },
/** /**
* The index of the withdrawal if multiple are made with multicall * The index of the withdrawal if multiple are made with multicall
*/ */
...@@ -1822,13 +1837,17 @@ export class CrossChainMessenger { ...@@ -1822,13 +1837,17 @@ export class CrossChainMessenger {
} }
if (this.bedrock) { if (this.bedrock) {
return this.populateTransaction.finalizeMessage(resolved, { return this.populateTransaction.finalizeMessage(
...(opts || {}), resolved,
overrides: { {
...opts?.overrides, ...(opts || {}),
gasLimit: messageGasLimit, overrides: {
...opts?.overrides,
gasLimit: messageGasLimit,
},
}, },
}) messageIndex
)
} else { } else {
const legacyL1XDM = new ethers.Contract( const legacyL1XDM = new ethers.Contract(
this.contracts.l1.L1CrossDomainMessenger.address, this.contracts.l1.L1CrossDomainMessenger.address,
...@@ -1861,7 +1880,6 @@ export class CrossChainMessenger { ...@@ -1861,7 +1880,6 @@ export class CrossChainMessenger {
opts?: { opts?: {
overrides?: PayableOverrides overrides?: PayableOverrides
}, },
/** /**
* The index of the withdrawal if multiple are made with multicall * The index of the withdrawal if multiple are made with multicall
*/ */
...@@ -1921,7 +1939,6 @@ export class CrossChainMessenger { ...@@ -1921,7 +1939,6 @@ export class CrossChainMessenger {
opts?: { opts?: {
overrides?: PayableOverrides overrides?: PayableOverrides
}, },
/** /**
* The index of the withdrawal if multiple are made with multicall * The index of the withdrawal if multiple are made with multicall
*/ */
...@@ -2229,10 +2246,18 @@ export class CrossChainMessenger { ...@@ -2229,10 +2246,18 @@ export class CrossChainMessenger {
message: MessageLike, message: MessageLike,
opts?: { opts?: {
overrides?: CallOverrides overrides?: CallOverrides
} },
/**
* The index of the withdrawal if multiple are made with multicall
*/
messageIndex = 0
): Promise<BigNumber> => { ): Promise<BigNumber> => {
return this.l1Provider.estimateGas( return this.l1Provider.estimateGas(
await this.populateTransaction.finalizeMessage(message, opts) await this.populateTransaction.finalizeMessage(
message,
opts,
messageIndex
)
) )
}, },
......
...@@ -123,6 +123,9 @@ importers: ...@@ -123,6 +123,9 @@ importers:
prettier-plugin-solidity: prettier-plugin-solidity:
specifier: ^1.0.0-beta.13 specifier: ^1.0.0-beta.13
version: 1.0.0-beta.18(prettier@2.8.1) version: 1.0.0-beta.18(prettier@2.8.1)
rimraf:
specifier: ^5.0.1
version: 5.0.1
ts-mocha: ts-mocha:
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.0.0(mocha@8.4.0) version: 10.0.0(mocha@8.4.0)
...@@ -9833,7 +9836,7 @@ packages: ...@@ -9833,7 +9836,7 @@ packages:
fs.realpath: 1.0.0 fs.realpath: 1.0.0
minimatch: 8.0.4 minimatch: 8.0.4
minipass: 4.2.8 minipass: 4.2.8
path-scurry: 1.9.2 path-scurry: 1.10.0
dev: true dev: true
/global-modules@2.0.0: /global-modules@2.0.0:
...@@ -12149,11 +12152,6 @@ packages: ...@@ -12149,11 +12152,6 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true dev: true
/lru-cache@9.1.2:
resolution: {integrity: sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==}
engines: {node: 14 || >=16.14}
dev: true
/lru_map@0.3.3: /lru_map@0.3.3:
resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==}
...@@ -14531,14 +14529,6 @@ packages: ...@@ -14531,14 +14529,6 @@ packages:
minipass: 6.0.2 minipass: 6.0.2
dev: true dev: true
/path-scurry@1.9.2:
resolution: {integrity: sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==}
engines: {node: '>=16 || 14 >=14.17'}
dependencies:
lru-cache: 9.1.2
minipass: 6.0.2
dev: true
/path-to-regexp@0.1.7: /path-to-regexp@0.1.7:
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
...@@ -15807,6 +15797,14 @@ packages: ...@@ -15807,6 +15797,14 @@ packages:
glob: 9.3.5 glob: 9.3.5
dev: true dev: true
/rimraf@5.0.1:
resolution: {integrity: sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==}
engines: {node: '>=14'}
hasBin: true
dependencies:
glob: 10.3.1
dev: true
/ripemd160@2.0.2: /ripemd160@2.0.2:
resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==}
dependencies: dependencies:
......
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