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

Merge branch 'develop' into willc/atst-seperate-react

parents 3f4a4354 c10214b0
---
'@eth-optimism/atst': minor
---
Remove broken allowFailures as option
---
'@eth-optimism/atst': patch
---
Fixed bug with atst not defaulting to currently connected chain
......@@ -87,7 +87,7 @@ func (s *channelManager) Clear() {
func (s *channelManager) TxFailed(id txID) {
if data, ok := s.pendingTransactions[id]; ok {
s.log.Trace("marked transaction as failed", "id", id)
s.pendingChannel.PushFrame(id, data)
s.pendingChannel.PushFrame(id, data[1:]) // strip the version byte
delete(s.pendingTransactions, id)
} else {
s.log.Warn("unknown transaction marked as failed", "id", id)
......
......@@ -77,7 +77,7 @@ func TestBatchInLastPossibleBlocks(gt *testing.T) {
}
// 8 L1 blocks with 17 L2 blocks is the unsafe state.
// Because wew consistently batch submitted we are one epoch behind the unsafe head with the safe head
// Because we consistently batch submitted we are one epoch behind the unsafe head with the safe head
verifyChainStateOnSequencer(8, 17, 8, 15, 7)
// Create the batch for L2 blocks 16 & 17
......
......@@ -464,7 +464,7 @@ func (cfg SystemConfig) Start() (*System, error) {
c.P2P = p
if c.Driver.SequencerEnabled {
c.P2PSigner = &p2p.PreparedSigner{Signer: p2p.NewLegacyLocalSigner(cfg.Secrets.SequencerP2P)}
c.P2PSigner = &p2p.PreparedSigner{Signer: p2p.NewLocalSigner(cfg.Secrets.SequencerP2P)}
}
}
......
......@@ -19,6 +19,7 @@ import (
"time"
"github.com/ethereum-optimism/optimism/op-node/eth"
ophttp "github.com/ethereum-optimism/optimism/op-node/http"
"github.com/ethereum/go-ethereum/log"
)
......@@ -161,7 +162,8 @@ func runServer() {
mux.HandleFunc("/logs", makeGzipHandler(logsHandler))
log.Info("running webserver...")
if err := http.Serve(l, mux); err != nil && !errors.Is(err, http.ErrServerClosed) {
httpServer := ophttp.NewHttpServer(mux)
if err := httpServer.Serve(l); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Crit("http server failed", "message", err)
}
}
......
package http
import (
"net/http"
"github.com/ethereum/go-ethereum/rpc"
)
// Use default timeouts from Geth as battle tested default values
var timeouts = rpc.DefaultHTTPTimeouts
func NewHttpServer(handler http.Handler) *http.Server {
return &http.Server{
Handler: handler,
ReadTimeout: timeouts.ReadTimeout,
ReadHeaderTimeout: timeouts.ReadHeaderTimeout,
WriteTimeout: timeouts.WriteTimeout,
IdleTimeout: timeouts.IdleTimeout,
}
}
......@@ -7,10 +7,10 @@ import (
"errors"
"fmt"
"net"
"net/http"
"strconv"
"time"
ophttp "github.com/ethereum-optimism/optimism/op-node/http"
"github.com/ethereum-optimism/optimism/op-service/metrics"
pb "github.com/libp2p/go-libp2p-pubsub/pb"
......@@ -528,12 +528,10 @@ func (m *Metrics) RecordSequencerSealingTime(duration time.Duration) {
// The server will be closed when the passed-in context is cancelled.
func (m *Metrics) Serve(ctx context.Context, hostname string, port int) error {
addr := net.JoinHostPort(hostname, strconv.Itoa(port))
server := &http.Server{
Addr: addr,
Handler: promhttp.InstrumentMetricHandler(
m.registry, promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{}),
),
}
server := ophttp.NewHttpServer(promhttp.InstrumentMetricHandler(
m.registry, promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{}),
))
server.Addr = addr
go func() {
<-ctx.Done()
server.Close()
......
......@@ -7,6 +7,7 @@ import (
"net/http"
"strconv"
ophttp "github.com/ethereum-optimism/optimism/op-node/http"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
......@@ -87,7 +88,7 @@ func (s *rpcServer) Start() error {
}
s.listenAddr = listener.Addr()
s.httpServer = &http.Server{Handler: mux}
s.httpServer = ophttp.NewHttpServer(mux)
go func() {
if err := s.httpServer.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { // todo improve error handling
s.log.Error("http server failed", "err", err)
......
......@@ -24,7 +24,7 @@ func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) {
return nil, fmt.Errorf("failed to read batch submitter key: %w", err)
}
return &p2p.PreparedSigner{Signer: p2p.NewLegacyLocalSigner(priv)}, nil
return &p2p.PreparedSigner{Signer: p2p.NewLocalSigner(priv)}, nil
}
// TODO: create remote signer
......
......@@ -49,7 +49,7 @@ func TestVerifyBlockSignature(t *testing.T) {
}{
{
name: "Legacy",
newSigner: NewLegacyLocalSigner,
newSigner: newLegacyLocalSigner,
},
{
name: "Updated",
......@@ -102,3 +102,7 @@ func TestVerifyBlockSignature(t *testing.T) {
})
}
}
func newLegacyLocalSigner(priv *ecdsa.PrivateKey) *LocalSigner {
return &LocalSigner{priv: priv, hasher: LegacySigningHash}
}
......@@ -315,7 +315,7 @@ func TestDiscovery(t *testing.T) {
// B and C don't know each other yet, but both have A as a bootnode.
// It should only be a matter of time for them to connect, if they discover each other via A.
timeout := time.After(time.Second * 10)
timeout := time.After(time.Second * 60)
var peersOfB []peer.ID
// B should be connected to the bootnode (A) it used (it's a valid optimism node to connect to here)
// C should also be connected, although this one might take more time to discover
......
......@@ -64,10 +64,6 @@ type LocalSigner struct {
hasher func(domain [32]byte, chainID *big.Int, payloadBytes []byte) (common.Hash, error)
}
func NewLegacyLocalSigner(priv *ecdsa.PrivateKey) *LocalSigner {
return &LocalSigner{priv: priv, hasher: LegacySigningHash}
}
func NewLocalSigner(priv *ecdsa.PrivateKey) *LocalSigner {
return &LocalSigner{priv: priv, hasher: SigningHash}
}
......
package crypto
import (
"bytes"
"context"
"crypto/ecdsa"
"errors"
......@@ -56,10 +57,10 @@ func SignerFactoryFromConfig(l log.Logger, privateKey, mnemonic, hdPath string,
fromAddress = common.HexToAddress(signerConfig.Address)
signer = func(chainID *big.Int) SignerFn {
return func(ctx context.Context, address common.Address, tx *types.Transaction) (*types.Transaction, error) {
if address.String() != signerConfig.Address {
if !bytes.Equal(address[:], fromAddress[:]) {
return nil, fmt.Errorf("attempting to sign for %s, expected %s: ", address, signerConfig.Address)
}
return signerClient.SignTransaction(ctx, chainID, tx)
return signerClient.SignTransaction(ctx, chainID, address, tx)
}
}
} else {
......
......@@ -122,6 +122,11 @@ func (m *SimpleTxManager) IncreaseGasPrice(ctx context.Context, tx *types.Transa
gasTipCap = tip
}
// Return the same transaction if we don't update any fields.
// We do this because ethereum signatures are not deterministic and therefore the transaction hash will change
// when we re-sign the tx. We don't want to do that because we want to see ErrAlreadyKnown instead of ErrReplacementUnderpriced
var reusedTip, reusedFeeCap bool
// new = old * (100 + priceBump) / 100
// Enforce a min priceBump on the tip. Do this before the feeCap is calculated
thresholdTip := new(big.Int).Mul(priceBumpPercent, tx.GasTipCap())
......@@ -129,6 +134,7 @@ func (m *SimpleTxManager) IncreaseGasPrice(ctx context.Context, tx *types.Transa
if tx.GasTipCapIntCmp(gasTipCap) >= 0 {
m.l.Debug("Reusing the previous tip", "previous", tx.GasTipCap(), "suggested", gasTipCap)
gasTipCap = tx.GasTipCap()
reusedTip = true
} else if thresholdTip.Cmp(gasTipCap) > 0 {
m.l.Debug("Overriding the tip to enforce a price bump", "previous", tx.GasTipCap(), "suggested", gasTipCap, "new", thresholdTip)
gasTipCap = thresholdTip
......@@ -150,11 +156,16 @@ func (m *SimpleTxManager) IncreaseGasPrice(ctx context.Context, tx *types.Transa
if tx.GasFeeCapIntCmp(gasFeeCap) >= 0 {
m.l.Debug("Reusing the previous fee cap", "previous", tx.GasFeeCap(), "suggested", gasFeeCap)
gasFeeCap = tx.GasFeeCap()
reusedFeeCap = true
} else if thresholdFeeCap.Cmp(gasFeeCap) > 0 {
m.l.Debug("Overriding the fee cap to enforce a price bump", "previous", tx.GasFeeCap(), "suggested", gasFeeCap, "new", thresholdFeeCap)
gasFeeCap = thresholdFeeCap
}
if reusedTip && reusedFeeCap {
return tx, nil
}
rawTx := &types.DynamicFeeTx{
ChainID: tx.ChainId(),
Nonce: tx.Nonce(),
......
......@@ -4,6 +4,7 @@ import (
"context"
"errors"
"math/big"
"math/rand"
"sync"
"testing"
"time"
......@@ -11,9 +12,12 @@ import (
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/testutils"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
)
......@@ -727,3 +731,42 @@ func TestIncreaseGasPriceUseLargeIncrease(t *testing.T) {
require.True(t, newTx.GasFeeCap().Cmp(feeCap) == 0, "new tx fee cap must be equal L1")
require.True(t, newTx.GasTipCap().Cmp(borkedBackend.gasTip) == 0, "new tx tip must be equal L1")
}
// TestIncreaseGasPriceReusesTransaction asserts that if the L1 basefee & tip remain the
// same, the transaction is returned with the same signature values. The means that the error
// when submitting the transaction to the network is ErrAlreadyKnown instead of ErrReplacementUnderpriced
func TestIncreaseGasPriceReusesTransaction(t *testing.T) {
t.Parallel()
borkedBackend := failingBackend{
gasTip: big.NewInt(10),
baseFee: big.NewInt(45),
}
pk := testutils.InsecureRandomKey(rand.New(rand.NewSource(123)))
signer := opcrypto.PrivateKeySignerFn(pk, big.NewInt(10))
mgr := &SimpleTxManager{
Config: Config{
ResubmissionTimeout: time.Second,
ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: 1,
SafeAbortNonceTooLowCount: 3,
Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) {
return signer(from, tx)
},
From: crypto.PubkeyToAddress(pk.PublicKey),
},
name: "TEST",
backend: &borkedBackend,
l: testlog.Logger(t, log.LvlCrit),
}
tx := types.NewTx(&types.DynamicFeeTx{
GasTipCap: big.NewInt(10),
GasFeeCap: big.NewInt(100),
})
ctx := context.Background()
newTx, err := mgr.IncreaseGasPrice(ctx, tx)
require.NoError(t, err)
require.Equal(t, tx.Hash(), newTx.Hash())
}
......@@ -12,6 +12,7 @@ import (
optls "github.com/ethereum-optimism/optimism/op-service/tls"
"github.com/ethereum-optimism/optimism/op-service/tls/certman"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
......@@ -91,8 +92,8 @@ func (s *SignerClient) pingVersion() (string, error) {
return v, nil
}
func (s *SignerClient) SignTransaction(ctx context.Context, chainId *big.Int, tx *types.Transaction) (*types.Transaction, error) {
args := NewTransactionArgsFromTransaction(chainId, tx)
func (s *SignerClient) SignTransaction(ctx context.Context, chainId *big.Int, from common.Address, tx *types.Transaction) (*types.Transaction, error) {
args := NewTransactionArgsFromTransaction(chainId, from, tx)
var result hexutil.Bytes
if err := s.client.CallContext(ctx, &result, "eth_signTransaction", args); err != nil {
......
......@@ -31,12 +31,13 @@ type TransactionArgs struct {
}
// NewTransactionArgsFromTransaction creates a TransactionArgs struct from an EIP-1559 transaction
func NewTransactionArgsFromTransaction(chainId *big.Int, tx *types.Transaction) *TransactionArgs {
func NewTransactionArgsFromTransaction(chainId *big.Int, from common.Address, tx *types.Transaction) *TransactionArgs {
data := hexutil.Bytes(tx.Data())
nonce := hexutil.Uint64(tx.Nonce())
gas := hexutil.Uint64(tx.Gas())
accesses := tx.AccessList()
args := &TransactionArgs{
From: &from,
Input: &data,
Nonce: &nonce,
Value: (*hexutil.Big)(tx.Value()),
......
......@@ -38,7 +38,7 @@ The typescript sdk provides a clean [wagmi](https://wagmi.sh/) based interface f
The cli provides a convenient cli for interacting with the attestation station contract
TODO put a gif here of using it
![preview](./assets/preview.gif)
## React API
......
# Assets
## preview.gif
A gif preview of using the cli
## preview.tape
The script to record the preview.gif with [vhs](https://github.com/charmbracelet/vhs)
To execute:
1. [Download vhs](https://github.com/charmbracelet/vhs)
2. Install the local version of atst
```bash
npm uninstall @eth-optimism/atst -g && npm i . -g && atst --version
```
3. Start anvil
```bash
anvil --fork-url https://mainnet.optimism.io
```
4. Record tape vhs < assets/preview.tape
```bash
vhs < assets/preview.tape
```
5. The tape will be outputted to `assets/preview.gif`
# VHS File source
# https://github.com/charmbracelet/vhs
#
# Output:
# Output <path>.gif Create a GIF output at the given <path>
# Output <path>.mp4 Create an MP4 output at the given <path>
# Output <path>.webm Create a WebM output at the given <path>
#
# Settings:
# Set FontSize <number> Set the font size of the terminal
# Set FontFamily <string> Set the font family of the terminal
# Set Height <number> Set the height of the terminal
# Set Width <number> Set the width of the terminal
# Set LetterSpacing <float> Set the font letter spacing (tracking)
# Set LineHeight <float> Set the font line height
# Set Theme <string> Set the theme of the terminal (JSON)
# Set Padding <number> Set the padding of the terminal
# Set Framerate <number> Set the framerate of the recording
# Set PlaybackSpeed <float> Set the playback speed of the recording
#
# Sleep:
# Sleep <time> Sleep for a set amount of <time> in seconds
#
# Type:
# Type[@<time>] "<characters>" Type <characters> into the terminal with a
# <time> delay between each character
#
# Keys:
# Backspace[@<time>] [number] Press the Backspace key
# Down[@<time>] [number] Press the Down key
# Enter[@<time>] [number] Press the Enter key
# Space[@<time>] [number] Press the Space key
# Tab[@<time>] [number] Press the Tab key
# Left[@<time>] [number] Press the Left Arrow key
# Right[@<time>] [number] Press the Right Arrow key
# Up[@<time>] [number] Press the Up Arrow key
# Down[@<time>] [number] Press the Down Arrow key
# Ctrl+<key> Press the Control key + <key> (e.g. Ctrl+C)
#
# Display:
# Hide Hide the subsequent commands from the output
# Show Show the subsequent commands in the output
Output assets/preview.gif
Set FontSize 16
Set Width 1920
Set Height 1080
Type "atst write --key attitude --about 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --value 'feeling very optimistic' --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --rpc-url http://localhost:8545"
Enter
Sleep 2000ms
Type "atst read --key attitude --about 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --creator 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://localhost:8545"
Enter
Sleep 2000ms
Type "atst write --key impress-level --about 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --value 10 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --rpc-url http://localhost:8545"
Enter
Sleep 2000ms
Type "atst read --key impress-level --about 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --creator 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://localhost:8545"
Enter
Sleep 2000ms
Type "atst --help"
Enter
Sleep 2000ms
# atst cli docs
![preview](../assets/preview.gif)
## Installation
```bash
......
......@@ -171,11 +171,6 @@ const attestation = await readAttestations({
* @defaults defaults to the create2 address
*/
contractAddress,
/**
* Boolean: Whether to allow some of the calls to fail
* Defaults to false
*/
allowFailures,
})
```
......
......@@ -15,13 +15,13 @@ cli
.option('--creator <string>', readOptionsValidators.creator.description!)
.option('--about <string>', readOptionsValidators.about.description!)
.option('--key <string>', readOptionsValidators.key.description!)
.option('--data-type [string]', readOptionsValidators.dataType.description!, {
.option('--data-type <string>', readOptionsValidators.dataType.description!, {
default: readOptionsValidators.dataType.parse(undefined),
})
.option('--rpc-url [url]', readOptionsValidators.rpcUrl.description!, {
.option('--rpc-url <url>', readOptionsValidators.rpcUrl.description!, {
default: readOptionsValidators.rpcUrl.parse(undefined),
})
.option('--contract [address]', readOptionsValidators.contract.description!, {
.option('--contract <address>', readOptionsValidators.contract.description!, {
default: readOptionsValidators.contract.parse(undefined),
})
.example(
......@@ -52,17 +52,17 @@ cli
'--private-key <string>',
writeOptionsValidators.privateKey.description!
)
.option('--data-type [string]', readOptionsValidators.dataType.description!, {
.option('--data-type <string>', readOptionsValidators.dataType.description!, {
default: writeOptionsValidators.dataType.parse(undefined),
})
.option('--about <string>', writeOptionsValidators.about.description!)
.option('--key <string>', writeOptionsValidators.key.description!)
.option('--value <string>', writeOptionsValidators.value.description!)
.option('--rpc-url [url]', writeOptionsValidators.rpcUrl.description!, {
.option('--rpc-url <url>', writeOptionsValidators.rpcUrl.description!, {
default: writeOptionsValidators.rpcUrl.parse(undefined),
})
.option(
'--contract [address]',
'--contract <address>',
writeOptionsValidators.contract.description!,
{
default: writeOptionsValidators.contract.parse(undefined),
......
......@@ -10,7 +10,7 @@ export const prepareWriteAttestation = async (
about: Address,
key: string,
value: string | WagmiBytes | number | boolean,
chainId = 10,
chainId: number | undefined = undefined,
contractAddress: Address = ATTESTATION_STATION_ADDRESS
) => {
let formattedKey: WagmiBytes
......
......@@ -14,7 +14,7 @@ type Attestation = {
export const prepareWriteAttestations = async (
attestations: Attestation[],
chainId = 10,
chainId: number | undefined = undefined,
contractAddress: Address = ATTESTATION_STATION_ADDRESS
) => {
const formattedAttestations = attestations.map((attestation) => {
......@@ -27,9 +27,7 @@ export const prepareWriteAttestations = async (
`key is longer than 32 bytes: ${attestation.key}. Try using a shorter key or using 'encodeRawKey' to encode the key into 32 bytes first`
)
}
const formattedValue = createValue(
attestation.value
) as WagmiBytes
const formattedValue = createValue(attestation.value) as WagmiBytes
return {
about: attestation.about,
key: formattedKey,
......
......@@ -19,7 +19,6 @@ import { parseAttestationBytes } from './parseAttestationBytes'
* creator: creatorAddress,
* about: aboutAddress,
* key: 'my_key',
* allowFailure: false,
* },
* {
* creator: creatorAddress2,
......@@ -27,7 +26,6 @@ import { parseAttestationBytes } from './parseAttestationBytes'
* key: 'my_key',
* dataType: 'number',
* contractAddress: '0x1234',
* allowFailure: false,
* },
* )
*/
......@@ -40,7 +38,6 @@ export const readAttestations = async (
about,
key,
contractAddress = ATTESTATION_STATION_ADDRESS,
allowFailure = false,
} = attestation
if (key.length > 32) {
throw new Error(
......@@ -52,7 +49,6 @@ export const readAttestations = async (
abi,
functionName: 'attestations',
args: [creator, about, formatBytes32String(key) as WagmiBytes],
allowFailure,
} as const
})
......
......@@ -11,5 +11,5 @@ export interface AttestationReadParams {
key: string
dataType?: DataTypeOption
contractAddress?: Address
allowFailure?: boolean
chainId?: number
}
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