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

Merge branch 'develop' into feat/migration-fixes

parents 452bb4bd ad4d235d
---
'@eth-optimism/atst': minor
---
Move react api to @eth-optimism/atst/react so react isn't required to run the core sdk
---
'@eth-optimism/atst': patch
---
Fixed bug with atst not defaulting to currently connected chain
...@@ -1132,6 +1132,13 @@ workflows: ...@@ -1132,6 +1132,13 @@ workflows:
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
context: context:
- oplabs-gcr - oplabs-gcr
- docker-publish:
name: chain-mon-docker-publish
docker_file: ./ops/docker/Dockerfile.packages
docker_name: chain-mon
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
context:
- oplabs-gcr
- hive-test: - hive-test:
name: hive-test-rpc name: hive-test-rpc
version: <<pipeline.git.revision>> version: <<pipeline.git.revision>>
...@@ -1227,4 +1234,4 @@ workflows: ...@@ -1227,4 +1234,4 @@ workflows:
context: context:
- oplabs-gcr-release - oplabs-gcr-release
requires: requires:
- hold - hold
\ No newline at end of file
...@@ -184,42 +184,68 @@ func (s *L1Replica) L1Client(t Testing, cfg *rollup.Config) *sources.L1Client { ...@@ -184,42 +184,68 @@ func (s *L1Replica) L1Client(t Testing, cfg *rollup.Config) *sources.L1Client {
return l1F return l1F
} }
// ActL1FinalizeNext finalizes the next block, which must be marked as safe before doing so (see ActL1SafeNext). func (s *L1Replica) UnsafeNum() uint64 {
func (s *L1Replica) ActL1FinalizeNext(t Testing) { head := s.l1Chain.CurrentBlock()
headNum := uint64(0)
if head != nil {
headNum = head.NumberU64()
}
return headNum
}
func (s *L1Replica) SafeNum() uint64 {
safe := s.l1Chain.CurrentSafeBlock() safe := s.l1Chain.CurrentSafeBlock()
safeNum := uint64(0) safeNum := uint64(0)
if safe != nil { if safe != nil {
safeNum = safe.NumberU64() safeNum = safe.NumberU64()
} }
return safeNum
}
func (s *L1Replica) FinalizedNum() uint64 {
finalized := s.l1Chain.CurrentFinalizedBlock() finalized := s.l1Chain.CurrentFinalizedBlock()
finalizedNum := uint64(0) finalizedNum := uint64(0)
if finalized != nil { if finalized != nil {
finalizedNum = finalized.NumberU64() finalizedNum = finalized.NumberU64()
} }
if safeNum <= finalizedNum { return finalizedNum
}
// ActL1Finalize finalizes a later block, which must be marked as safe before doing so (see ActL1SafeNext).
func (s *L1Replica) ActL1Finalize(t Testing, num uint64) {
safeNum := s.SafeNum()
finalizedNum := s.FinalizedNum()
if safeNum < num {
t.InvalidAction("need to move forward safe block before moving finalized block") t.InvalidAction("need to move forward safe block before moving finalized block")
return return
} }
next := s.l1Chain.GetBlockByNumber(finalizedNum + 1) newFinalized := s.l1Chain.GetBlockByNumber(num)
if next == nil { if newFinalized == nil {
t.Fatalf("expected next block after finalized L1 block %d, safe head is ahead", finalizedNum) t.Fatalf("expected block at %d after finalized L1 block %d, safe head is ahead", num, finalizedNum)
} }
s.l1Chain.SetFinalized(next) s.l1Chain.SetFinalized(newFinalized)
} }
// ActL1SafeNext marks the next unsafe block as safe. // ActL1FinalizeNext finalizes the next block, which must be marked as safe before doing so (see ActL1SafeNext).
func (s *L1Replica) ActL1SafeNext(t Testing) { func (s *L1Replica) ActL1FinalizeNext(t Testing) {
safe := s.l1Chain.CurrentSafeBlock() n := s.FinalizedNum() + 1
safeNum := uint64(0) s.ActL1Finalize(t, n)
if safe != nil { }
safeNum = safe.NumberU64()
} // ActL1Safe marks the given unsafe block as safe.
next := s.l1Chain.GetBlockByNumber(safeNum + 1) func (s *L1Replica) ActL1Safe(t Testing, num uint64) {
if next == nil { newSafe := s.l1Chain.GetBlockByNumber(num)
t.InvalidAction("if head of chain is marked as safe then there's no next block") if newSafe == nil {
t.InvalidAction("could not find L1 block %d, cannot label it as safe", num)
return return
} }
s.l1Chain.SetSafe(next) s.l1Chain.SetSafe(newSafe)
}
// ActL1SafeNext marks the next unsafe block as safe.
func (s *L1Replica) ActL1SafeNext(t Testing) {
n := s.SafeNum() + 1
s.ActL1Safe(t, n)
} }
func (s *L1Replica) Close() error { func (s *L1Replica) Close() error {
......
...@@ -196,6 +196,62 @@ func TestL2Finalization(gt *testing.T) { ...@@ -196,6 +196,62 @@ func TestL2Finalization(gt *testing.T) {
require.Equal(t, heightToSubmit, sequencer.SyncStatus().FinalizedL2.Number, "unknown/bad finalized L1 blocks are ignored") require.Equal(t, heightToSubmit, sequencer.SyncStatus().FinalizedL2.Number, "unknown/bad finalized L1 blocks are ignored")
} }
// TestL2FinalizationWithSparseL1 tests that safe L2 blocks can be finalized even if we do not regularly get a L1 finalization signal
func TestL2FinalizationWithSparseL1(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug)
miner, engine, sequencer := setupSequencerTest(t, sd, log)
sequencer.ActL2PipelineFull(t)
miner.ActEmptyBlock(t)
sequencer.ActL1HeadSignal(t)
sequencer.ActBuildToL1Head(t)
startStatus := sequencer.SyncStatus()
require.Less(t, startStatus.SafeL2.Number, startStatus.UnsafeL2.Number, "sequencer has unsafe L2 block")
batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{
MinL1TxSize: 0,
MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher,
}, sequencer.RollupClient(), miner.EthClient(), engine.EthClient())
batcher.ActSubmitAll(t)
// include in L1
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t)
// Make 2 L1 blocks without batches
miner.ActEmptyBlock(t)
miner.ActEmptyBlock(t)
// See the L1 head, and traverse the pipeline to it
sequencer.ActL1HeadSignal(t)
sequencer.ActL2PipelineFull(t)
updatedStatus := sequencer.SyncStatus()
require.Equal(t, updatedStatus.SafeL2.Number, updatedStatus.UnsafeL2.Number, "unsafe L2 block is now safe")
require.Less(t, updatedStatus.FinalizedL2.Number, updatedStatus.UnsafeL2.Number, "submitted block is not yet finalized")
// Now skip straight to the head with L1 signals (sequencer has traversed the L1 blocks, but they did not have L2 contents)
headL1Num := miner.UnsafeNum()
miner.ActL1Safe(t, headL1Num)
miner.ActL1Finalize(t, headL1Num)
sequencer.ActL1SafeSignal(t)
sequencer.ActL1FinalizedSignal(t)
// Now see if the signals can be processed
sequencer.ActL2PipelineFull(t)
finalStatus := sequencer.SyncStatus()
// Verify the signal was processed, even though we signalled a later L1 block than the one with the batch.
require.Equal(t, finalStatus.FinalizedL2.Number, finalStatus.UnsafeL2.Number, "sequencer submitted its L2 block and it finalized")
}
// TestGarbageBatch tests the behavior of an invalid/malformed output channel frame containing // TestGarbageBatch tests the behavior of an invalid/malformed output channel frame containing
// valid batches being submitted to the batch inbox. These batches should always be rejected // valid batches being submitted to the batch inbox. These batches should always be rejected
// and the safe L2 head should remain unaltered. // and the safe L2 head should remain unaltered.
......
...@@ -4,8 +4,6 @@ import ( ...@@ -4,8 +4,6 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
...@@ -144,24 +142,19 @@ func TestL2Sequencer_SequencerOnlyReorg(gt *testing.T) { ...@@ -144,24 +142,19 @@ func TestL2Sequencer_SequencerOnlyReorg(gt *testing.T) {
// so it'll keep the L2 block with the old L1 origin, since no conflict is detected. // so it'll keep the L2 block with the old L1 origin, since no conflict is detected.
sequencer.ActL1HeadSignal(t) sequencer.ActL1HeadSignal(t)
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
// TODO: CLI-3405 we can detect the inconsistency of the L1 origin of the unsafe L2 head: // Verifier should detect the inconsistency of the L1 origin and reset the pipeline to follow the reorg
// as verifier, there is no need to wait for sequencer to recognize it.
newStatus := sequencer.SyncStatus() newStatus := sequencer.SyncStatus()
require.Equal(t, status.HeadL1.Hash, newStatus.UnsafeL2.L1Origin.Hash, "still have old bad L1 origin") require.Zero(t, newStatus.UnsafeL2.L1Origin.Number, "back to genesis block with good L1 origin, drop old unsafe L2 chain with bad L1 origins")
require.NotEqual(t, status.HeadL1.Hash, newStatus.HeadL1.Hash, "did see the new L1 head change") require.NotEqual(t, status.HeadL1.Hash, newStatus.HeadL1.Hash, "did see the new L1 head change")
require.Equal(t, newStatus.HeadL1.Hash, newStatus.CurrentL1.Hash, "did sync the new L1 head as verifier") require.Equal(t, newStatus.HeadL1.Hash, newStatus.CurrentL1.Hash, "did sync the new L1 head as verifier")
// the block N+1 cannot build on the old N which still refers to the now orphaned L1 origin // the block N+1 cannot build on the old N which still refers to the now orphaned L1 origin
require.Equal(t, status.UnsafeL2.L1Origin.Number, newStatus.HeadL1.Number-1, "seeing N+1 to attempt to build on N") require.Equal(t, status.UnsafeL2.L1Origin.Number, newStatus.HeadL1.Number-1, "seeing N+1 to attempt to build on N")
require.NotEqual(t, status.UnsafeL2.L1Origin.Hash, newStatus.HeadL1.ParentHash, "but N+1 cannot fit on N") require.NotEqual(t, status.UnsafeL2.L1Origin.Hash, newStatus.HeadL1.ParentHash, "but N+1 cannot fit on N")
sequencer.ActL1HeadSignal(t)
// sequence more L2 blocks, until we actually need the next L1 origin // After hitting a reset error, it resets derivation, and drops the old L1 chain
sequencer.ActBuildToL1HeadExclUnsafe(t)
// We expect block building to fail when the next L1 block is not consistent with the existing L1 origin
sequencer.ActL2StartBlockCheckErr(t, derive.ErrReset)
// After hitting a reset error, it reset derivation, and drops the old L1 chain
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
require.Zero(t, sequencer.SyncStatus().UnsafeL2.L1Origin.Number, "back to genesis block with good L1 origin, drop old unsafe L2 chain with bad L1 origins")
// Can build new L2 blocks with good L1 origin // Can build new L2 blocks with good L1 origin
sequencer.ActBuildToL1HeadUnsafe(t) sequencer.ActBuildToL1HeadUnsafe(t)
require.Equal(t, newStatus.HeadL1.Hash, sequencer.SyncStatus().UnsafeL2.L1Origin.Hash, "build L2 chain with new correct L1 origins") require.Equal(t, newStatus.HeadL1.Hash, sequencer.SyncStatus().UnsafeL2.L1Origin.Hash, "build L2 chain with new correct L1 origins")
......
...@@ -464,7 +464,7 @@ func (cfg SystemConfig) Start() (*System, error) { ...@@ -464,7 +464,7 @@ func (cfg SystemConfig) Start() (*System, error) {
c.P2P = p c.P2P = p
if c.Driver.SequencerEnabled { 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 ( ...@@ -19,6 +19,7 @@ import (
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
ophttp "github.com/ethereum-optimism/optimism/op-node/http"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -161,7 +162,8 @@ func runServer() { ...@@ -161,7 +162,8 @@ func runServer() {
mux.HandleFunc("/logs", makeGzipHandler(logsHandler)) mux.HandleFunc("/logs", makeGzipHandler(logsHandler))
log.Info("running webserver...") 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) 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 ( ...@@ -7,10 +7,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/http"
"strconv" "strconv"
"time" "time"
ophttp "github.com/ethereum-optimism/optimism/op-node/http"
"github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/metrics"
pb "github.com/libp2p/go-libp2p-pubsub/pb" pb "github.com/libp2p/go-libp2p-pubsub/pb"
...@@ -528,12 +528,10 @@ func (m *Metrics) RecordSequencerSealingTime(duration time.Duration) { ...@@ -528,12 +528,10 @@ func (m *Metrics) RecordSequencerSealingTime(duration time.Duration) {
// The server will be closed when the passed-in context is cancelled. // The server will be closed when the passed-in context is cancelled.
func (m *Metrics) Serve(ctx context.Context, hostname string, port int) error { func (m *Metrics) Serve(ctx context.Context, hostname string, port int) error {
addr := net.JoinHostPort(hostname, strconv.Itoa(port)) addr := net.JoinHostPort(hostname, strconv.Itoa(port))
server := &http.Server{ server := ophttp.NewHttpServer(promhttp.InstrumentMetricHandler(
Addr: addr, m.registry, promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{}),
Handler: promhttp.InstrumentMetricHandler( ))
m.registry, promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{}), server.Addr = addr
),
}
go func() { go func() {
<-ctx.Done() <-ctx.Done()
server.Close() server.Close()
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
ophttp "github.com/ethereum-optimism/optimism/op-node/http"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
...@@ -87,7 +88,7 @@ func (s *rpcServer) Start() error { ...@@ -87,7 +88,7 @@ func (s *rpcServer) Start() error {
} }
s.listenAddr = listener.Addr() s.listenAddr = listener.Addr()
s.httpServer = &http.Server{Handler: mux} s.httpServer = ophttp.NewHttpServer(mux)
go func() { go func() {
if err := s.httpServer.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { // todo improve error handling 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) s.log.Error("http server failed", "err", err)
......
...@@ -24,7 +24,7 @@ func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) { ...@@ -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 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 // TODO: create remote signer
......
...@@ -49,7 +49,7 @@ func TestVerifyBlockSignature(t *testing.T) { ...@@ -49,7 +49,7 @@ func TestVerifyBlockSignature(t *testing.T) {
}{ }{
{ {
name: "Legacy", name: "Legacy",
newSigner: NewLegacyLocalSigner, newSigner: newLegacyLocalSigner,
}, },
{ {
name: "Updated", name: "Updated",
...@@ -102,3 +102,7 @@ func TestVerifyBlockSignature(t *testing.T) { ...@@ -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) { ...@@ -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. // 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. // 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 var peersOfB []peer.ID
// B should be connected to the bootnode (A) it used (it's a valid optimism node to connect to here) // 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 // C should also be connected, although this one might take more time to discover
......
...@@ -64,10 +64,6 @@ type LocalSigner struct { ...@@ -64,10 +64,6 @@ type LocalSigner struct {
hasher func(domain [32]byte, chainID *big.Int, payloadBytes []byte) (common.Hash, error) 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 { func NewLocalSigner(priv *ecdsa.PrivateKey) *LocalSigner {
return &LocalSigner{priv: priv, hasher: SigningHash} return &LocalSigner{priv: priv, hasher: SigningHash}
} }
......
...@@ -224,7 +224,13 @@ func (eq *EngineQueue) Step(ctx context.Context) error { ...@@ -224,7 +224,13 @@ func (eq *EngineQueue) Step(ctx context.Context) error {
} }
outOfData := false outOfData := false
if len(eq.safeAttributes) == 0 { if len(eq.safeAttributes) == 0 {
eq.origin = eq.prev.Origin() newOrigin := eq.prev.Origin()
// Check if the L2 unsafe head origin is consistent with the new origin
if err := eq.verifyNewL1Origin(ctx, newOrigin); err != nil {
return err
}
eq.origin = newOrigin
eq.postProcessSafeL2() // make sure we track the last L2 safe head for every new L1 block
if next, err := eq.prev.NextAttributes(ctx, eq.safeHead); err == io.EOF { if next, err := eq.prev.NextAttributes(ctx, eq.safeHead); err == io.EOF {
outOfData = true outOfData = true
} else if err != nil { } else if err != nil {
...@@ -245,6 +251,38 @@ func (eq *EngineQueue) Step(ctx context.Context) error { ...@@ -245,6 +251,38 @@ func (eq *EngineQueue) Step(ctx context.Context) error {
} }
} }
// verifyNewL1Origin checks that the L2 unsafe head still has a L1 origin that is on the canonical chain.
// If the unsafe head origin is after the new L1 origin it is assumed to still be canonical.
// The check is only required when moving to a new L1 origin.
func (eq *EngineQueue) verifyNewL1Origin(ctx context.Context, newOrigin eth.L1BlockRef) error {
if newOrigin == eq.origin {
return nil
}
unsafeOrigin := eq.unsafeHead.L1Origin
if newOrigin.Number == unsafeOrigin.Number && newOrigin.ID() != unsafeOrigin {
return NewResetError(fmt.Errorf("l1 origin was inconsistent with l2 unsafe head origin, need reset to resolve: l1 origin: %v; unsafe origin: %v",
newOrigin.ID(), unsafeOrigin))
}
// Avoid requesting an older block by checking against the parent hash
if newOrigin.Number == unsafeOrigin.Number+1 && newOrigin.ParentHash != unsafeOrigin.Hash {
return NewResetError(fmt.Errorf("l2 unsafe head origin is no longer canonical, need reset to resolve: canonical hash: %v; unsafe origin hash: %v",
newOrigin.ParentHash, unsafeOrigin.Hash))
}
if newOrigin.Number > unsafeOrigin.Number+1 {
// If unsafe origin is further behind new origin, check it's still on the canonical chain.
canonical, err := eq.l1Fetcher.L1BlockRefByNumber(ctx, unsafeOrigin.Number)
if err != nil {
return NewTemporaryError(fmt.Errorf("failed to fetch canonical L1 block at slot: %v; err: %w", unsafeOrigin.Number, err))
}
if canonical.ID() != unsafeOrigin {
eq.log.Error("Resetting due to origin mismatch")
return NewResetError(fmt.Errorf("l2 unsafe head origin is no longer canonical, need reset to resolve: canonical: %v; unsafe origin: %v",
canonical, unsafeOrigin))
}
}
return nil
}
// tryFinalizeL2 traverses the past L1 blocks, checks if any has been finalized, // tryFinalizeL2 traverses the past L1 blocks, checks if any has been finalized,
// and then marks the latest fully derived L2 block from this as finalized, // and then marks the latest fully derived L2 block from this as finalized,
// or defaults to the current finalized L2 block. // or defaults to the current finalized L2 block.
...@@ -279,9 +317,15 @@ func (eq *EngineQueue) postProcessSafeL2() { ...@@ -279,9 +317,15 @@ func (eq *EngineQueue) postProcessSafeL2() {
L2Block: eq.safeHead, L2Block: eq.safeHead,
L1Block: eq.origin.ID(), L1Block: eq.origin.ID(),
}) })
last := &eq.finalityData[len(eq.finalityData)-1]
eq.log.Debug("extended finality-data", "last_l1", last.L1Block, "last_l2", last.L2Block)
} else { } else {
// if it's a now L2 block that was derived from the same latest L1 block, then just update the entry // if it's a new L2 block that was derived from the same latest L1 block, then just update the entry
eq.finalityData[len(eq.finalityData)-1].L2Block = eq.safeHead last := &eq.finalityData[len(eq.finalityData)-1]
if last.L2Block != eq.safeHead { // avoid logging if there are no changes
last.L2Block = eq.safeHead
eq.log.Debug("updated finality-data", "last_l1", last.L1Block, "last_l2", last.L2Block)
}
} }
} }
......
This diff is collapsed.
...@@ -6,6 +6,20 @@ ...@@ -6,6 +6,20 @@
"types": "src/index.ts", "types": "src/index.ts",
"module": "dist/index.cjs", "module": "dist/index.cjs",
"license": "MIT", "license": "MIT",
"exports": {
".": {
"types": "./src/index.ts",
"default": "./dist/index.js",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./react": {
"types": "./src/react.ts",
"default": "./dist/react.js",
"import": "./dist/react.js",
"require": "./dist/react.cjs"
}
},
"bin": { "bin": {
"atst": "./dist/cli.js" "atst": "./dist/cli.js"
}, },
......
...@@ -28,5 +28,3 @@ export type { AttestationCreatedEvent } from './types/AttestationCreatedEvent' ...@@ -28,5 +28,3 @@ export type { AttestationCreatedEvent } from './types/AttestationCreatedEvent'
export type { AttestationReadParams } from './types/AttestationReadParams' export type { AttestationReadParams } from './types/AttestationReadParams'
export type { DataTypeOption } from './types/DataTypeOption' export type { DataTypeOption } from './types/DataTypeOption'
export type { WagmiBytes } from './types/WagmiBytes' export type { WagmiBytes } from './types/WagmiBytes'
// react
export * from './react'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { Address } from 'wagmi' import type { Address } from '@wagmi/core'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress' import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import { abi } from '../lib/abi' import { abi } from '../lib/abi'
......
...@@ -10,7 +10,7 @@ export const prepareWriteAttestation = async ( ...@@ -10,7 +10,7 @@ export const prepareWriteAttestation = async (
about: Address, about: Address,
key: string, key: string,
value: string | WagmiBytes | number | boolean, value: string | WagmiBytes | number | boolean,
chainId = 10, chainId: number | undefined = undefined,
contractAddress: Address = ATTESTATION_STATION_ADDRESS contractAddress: Address = ATTESTATION_STATION_ADDRESS
) => { ) => {
let formattedKey: WagmiBytes let formattedKey: WagmiBytes
......
...@@ -14,7 +14,7 @@ type Attestation = { ...@@ -14,7 +14,7 @@ type Attestation = {
export const prepareWriteAttestations = async ( export const prepareWriteAttestations = async (
attestations: Attestation[], attestations: Attestation[],
chainId = 10, chainId: number | undefined = undefined,
contractAddress: Address = ATTESTATION_STATION_ADDRESS contractAddress: Address = ATTESTATION_STATION_ADDRESS
) => { ) => {
const formattedAttestations = attestations.map((attestation) => { const formattedAttestations = attestations.map((attestation) => {
...@@ -27,9 +27,7 @@ export const prepareWriteAttestations = async ( ...@@ -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` `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( const formattedValue = createValue(attestation.value) as WagmiBytes
attestation.value
) as WagmiBytes
return { return {
about: attestation.about, about: attestation.about,
key: formattedKey, key: formattedKey,
......
...@@ -11,4 +11,5 @@ export interface AttestationReadParams { ...@@ -11,4 +11,5 @@ export interface AttestationReadParams {
key: string key: string
dataType?: DataTypeOption dataType?: DataTypeOption
contractAddress?: Address contractAddress?: Address
chainId?: number
} }
import { BigNumber } from 'ethers' import { BigNumber } from 'ethers'
import { Address } from 'wagmi' import type { Address } from '@wagmi/core'
import { DataTypeOption } from './DataTypeOption' import { DataTypeOption } from './DataTypeOption'
import { WagmiBytes } from './WagmiBytes' import { WagmiBytes } from './WagmiBytes'
......
...@@ -10,7 +10,7 @@ export default defineConfig({ ...@@ -10,7 +10,7 @@ export default defineConfig({
* *
* @see https://tsup.egoist.dev/#building-cli-app * @see https://tsup.egoist.dev/#building-cli-app
*/ */
entry: ['src/index.ts', 'src/cli.ts'], entry: ['src/index.ts', 'src/cli.ts', 'src/react.ts'],
outDir: 'dist', outDir: 'dist',
target: 'es2021', target: 'es2021',
// will create a .js file for commonjs and a .cjs file for esm // will create a .js file for commonjs and a .cjs file for esm
......
Bytes_slice_Test:test_slice_acrossMultipleWords_works() (gas: 9423) Bytes_slice_Test:test_slice_acrossMultipleWords_works() (gas: 9413)
Bytes_slice_Test:test_slice_acrossWords_works() (gas: 1418) Bytes_slice_Test:test_slice_acrossWords_works() (gas: 1430)
Bytes_slice_Test:test_slice_fromNonZeroIdx_works() (gas: 17154) Bytes_slice_Test:test_slice_fromNonZeroIdx_works() (gas: 17240)
Bytes_slice_Test:test_slice_fromZeroIdx_works() (gas: 20694) Bytes_slice_Test:test_slice_fromZeroIdx_works() (gas: 20826)
Bytes_toNibbles_Test:test_toNibbles_expectedResult128Bytes_works() (gas: 129874) Bytes_toNibbles_Test:test_toNibbles_expectedResult128Bytes_works() (gas: 129874)
Bytes_toNibbles_Test:test_toNibbles_expectedResult5Bytes_works() (gas: 6132) Bytes_toNibbles_Test:test_toNibbles_expectedResult5Bytes_works() (gas: 6132)
Bytes_toNibbles_Test:test_toNibbles_zeroLengthInput_works() (gas: 944) Bytes_toNibbles_Test:test_toNibbles_zeroLengthInput_works() (gas: 944)
......
...@@ -122,6 +122,58 @@ contract Bytes_slice_Test is Test { ...@@ -122,6 +122,58 @@ contract Bytes_slice_Test is Test {
vm.expectRevert("slice_overflow"); vm.expectRevert("slice_overflow");
Bytes.slice(_input, _start, _length); Bytes.slice(_input, _start, _length);
} }
/**
* @notice Tests that the `slice` function correctly updates the free memory pointer depending
* on the length of the slice.
*/
function testFuzz_slice_memorySafety_succeeds(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// The start should never be more than the length of the input bytes array - 1
vm.assume(_start < _input.length);
// The length should never be more than the length of the input bytes array - the starting
// slice index.
vm.assume(_length <= _input.length - _start);
// Grab the free memory pointer before the slice operation
uint256 initPtr;
assembly {
initPtr := mload(0x40)
}
// Slice the input bytes array from `_start` to `_start + _length`
bytes memory slice = Bytes.slice(_input, _start, _length);
// Grab the free memory pointer after the slice operation
uint256 finalPtr;
assembly {
finalPtr := mload(0x40)
}
// The free memory pointer should have been updated properly
if (_length == 0) {
// If the slice length is zero, only 32 bytes of memory should have been allocated.
assertEq(finalPtr, initPtr + 0x20);
} else {
// If the slice length is greater than zero, the memory allocated should be the
// length of the slice rounded up to the next 32 byte word + 32 bytes for the
// length of the byte array.
//
// Note that we use a slightly less efficient, but equivalent method of rounding
// up `_length` to the next multiple of 32 than is used in the `slice` function.
// This is to diff test the method used in `slice`.
assertEq(finalPtr, initPtr + 0x20 + (((_length + 0x1F) >> 5) << 5));
// Sanity check for equivalence of the rounding methods.
assertEq(((_length + 0x1F) >> 5) << 5, (_length + 0x1F) & ~uint256(0x1F));
}
// The slice length should be equal to `_length`
assertEq(slice.length, _length);
}
} }
contract Bytes_toNibbles_Test is Test { contract Bytes_toNibbles_Test is Test {
......
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