Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
dee2081
[MEL] - Implement delayed message accumulation in native mode
ganeshvanahalli Jul 11, 2025
68c9112
Merge branch 'mel-database-impl' into mel-delayedmsg-accumulation
ganeshvanahalli Jul 15, 2025
4170ed1
address PR comments
ganeshvanahalli Jul 15, 2025
ffb0921
add documentation for checkAgainstAccumulator and a minor fix
ganeshvanahalli Jul 15, 2025
3aad133
undo changes to addressed review comments from other PRs
ganeshvanahalli Jul 16, 2025
3e23fa1
dont make L2msg rlp optional
ganeshvanahalli Jul 16, 2025
6a1a0c5
make meldb take a KeyValueStore
ganeshvanahalli Jul 17, 2025
a46c9ce
Merge branch 'master' into mel-delayedmsg-accumulation
ganeshvanahalli Jul 17, 2025
a584333
handle reorg in start step- reducing code diff
ganeshvanahalli Jul 17, 2025
40b5e8f
Merge branch 'master' into mel-delayedmsg-accumulation
ganeshvanahalli Jul 28, 2025
d5ed1e8
Merge branch 'master' into mel-delayedmsg-accumulation
ganeshvanahalli Jul 28, 2025
bcfd767
Merge branch 'master' into mel-delayedmsg-accumulation
rauljordan Jul 29, 2025
8f53088
Merge branch 'master' into mel-delayedmsg-accumulation
rauljordan Jul 29, 2025
6f1b33f
Merge branch 'master' into mel-delayedmsg-accumulation
ganeshvanahalli Jul 30, 2025
8d788ee
Merge branch 'master' into mel-delayedmsg-accumulation
rauljordan Jul 31, 2025
4b42aaa
Merge branch 'master' into mel-delayedmsg-accumulation
ganeshvanahalli Aug 11, 2025
c290a9c
Message extraction function works with logs instead of receipts
ganeshvanahalli Aug 11, 2025
cd2cb43
Merge branch 'master' into mel-delayedmsg-accumulation
rauljordan Aug 11, 2025
c100876
Merge branch 'mel-delayedmsg-accumulation' into mel-uses-logs
rauljordan Aug 13, 2025
4329432
Merge pull request #3471 from OffchainLabs/mel-uses-logs
rauljordan Aug 25, 2025
dd2a94f
merge master and resolve conflicts
ganeshvanahalli Nov 21, 2025
5237d1f
Merge branch 'master' into mel-delayedmsg-accumulation
ganeshvanahalli Nov 24, 2025
0c00fb7
only keep delayed message accumulation changes
ganeshvanahalli Nov 24, 2025
31345f9
cleanup non related code
ganeshvanahalli Nov 24, 2025
b84ef03
Merge branch 'master' into mel-delayedmsg-accumulation
ganeshvanahalli Dec 8, 2025
744808a
Implement preimage recorder for DelayedMessageDatabase interface
ganeshvanahalli Dec 8, 2025
5594ad9
Update cmd/mel-replay/delayed_message_db_test.go
rauljordan Dec 8, 2025
ea28402
fix test name
ganeshvanahalli Dec 9, 2025
de18198
Merge branch 'master' into mel-delayedmsg-accumulation
eljobe Dec 12, 2025
2c2426b
code refactor
ganeshvanahalli Dec 12, 2025
4e3d4c4
fix lint
ganeshvanahalli Dec 12, 2025
7308f79
Implement recording of preimages related to sequencer batches (DA pro…
ganeshvanahalli Dec 12, 2025
a9583f7
Merge branch 'master' into mel-delayedmsg-accumulation
ganeshvanahalli Dec 15, 2025
4c7f7f6
address PR comments
ganeshvanahalli Dec 15, 2025
a2d970d
Merge branch 'mel-delayedmsg-accumulation' into preimage-recording-fo…
ganeshvanahalli Dec 15, 2025
9ee3e9f
Merge branch 'preimage-recording-for-delayedmsgdb' into record-daprov…
ganeshvanahalli Dec 15, 2025
9cf89cd
add RecoverPayloadAndPreimages method on DACertificatePreimageReader
ganeshvanahalli Dec 15, 2025
afbc77f
merge master
ganeshvanahalli Dec 15, 2025
2c32013
Merge branch 'master' into preimage-recording-for-delayedmsgdb
ganeshvanahalli Dec 16, 2025
585785f
Merge branch 'preimage-recording-for-delayedmsgdb' into record-daprov…
ganeshvanahalli Dec 16, 2025
bbd379d
implement RecoverPayloadAndPreimages method on EvilDAProvider
ganeshvanahalli Dec 16, 2025
aad1a77
merge master and resolve conflicts
ganeshvanahalli Dec 16, 2025
e448284
fix schema.go
ganeshvanahalli Dec 16, 2025
7873f25
bring in merkle partials calculation step in ExtractMessages function
ganeshvanahalli Dec 16, 2025
7c24e2e
code refactor
ganeshvanahalli Dec 16, 2025
2884add
add documentation
ganeshvanahalli Dec 18, 2025
45dfef9
merge master and resolve conflict
ganeshvanahalli Dec 18, 2025
e02ef94
add changelog
ganeshvanahalli Dec 19, 2025
12aa301
move recording related code to new package, melrecording
ganeshvanahalli Dec 23, 2025
9afe872
merge master and resolve conflicts
ganeshvanahalli Dec 23, 2025
5d4b4ad
reduce conflicts
ganeshvanahalli Dec 23, 2025
78db72c
address PR comments
ganeshvanahalli Dec 30, 2025
10d8ba1
Merge branch 'master' into record-daprovider-preimages
ganeshvanahalli Dec 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions arbnode/mel/extraction/message_extraction_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func ExtractMessages(
ctx context.Context,
inputState *mel.State,
parentChainHeader *types.Header,
dataProviders *daprovider.DAProviderRegistry,
dapReaders arbstate.DapReaderSource,
delayedMsgDatabase DelayedMessageDatabase,
txFetcher TransactionFetcher,
logsFetcher LogsFetcher,
Expand All @@ -68,7 +68,7 @@ func ExtractMessages(
inputState,
parentChainHeader,
chainConfig,
dataProviders,
dapReaders,
delayedMsgDatabase,
txFetcher,
logsFetcher,
Expand All @@ -90,7 +90,7 @@ func extractMessagesImpl(
inputState *mel.State,
parentChainHeader *types.Header,
chainConfig *params.ChainConfig,
dataProviders *daprovider.DAProviderRegistry,
dapReaders arbstate.DapReaderSource,
delayedMsgDatabase DelayedMessageDatabase,
txFetcher TransactionFetcher,
logsFetcher LogsFetcher,
Expand Down Expand Up @@ -154,6 +154,12 @@ func extractMessagesImpl(
}
state.DelayedMessagesSeen += 1
}
if len(delayedMessages) > 0 {
// Only need to calculate partials once, after all the delayed messages are `seen`
if err := state.GenerateDelayedMessagesSeenMerklePartialsAndRoot(); err != nil {
return nil, nil, nil, nil, err
}
}

// Batch posting reports are included in the same transaction as a batch, so there should
// always be the same number of reports as there are batches.
Expand Down Expand Up @@ -207,7 +213,7 @@ func extractMessagesImpl(
batch.SequenceNumber,
batch.BlockHash,
serialized,
dataProviders,
dapReaders,
daprovider.KeysetValidate,
chainConfig,
)
Expand Down
67 changes: 67 additions & 0 deletions arbnode/mel/recording/dap_reader_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package melrecording

import (
"context"

"github.com/ethereum/go-ethereum/common"

"github.com/offchainlabs/nitro/arbstate"
"github.com/offchainlabs/nitro/daprovider"
"github.com/offchainlabs/nitro/util/containers"
"github.com/offchainlabs/nitro/validator"
)

// RecordingDAPReader implements recording of preimages when melextraction.ExtractMessages function is called by MEL validator for creation
// of validation entry. Since ExtractMessages function would use daprovider.Reader interface to fetch the sequencer batch via RecoverPayload
// we implement collecting of preimages as well in the same method and record it
type RecordingDAPReader struct {
validatorCtx context.Context
reader daprovider.Reader
preimages daprovider.PreimagesMap
}

func (r *RecordingDAPReader) RecoverPayload(batchNum uint64, batchBlockHash common.Hash, sequencerMsg []byte) containers.PromiseInterface[daprovider.PayloadResult] {
promise := r.reader.RecoverPayloadAndPreimages(batchNum, batchBlockHash, sequencerMsg)
result, err := promise.Await(r.validatorCtx)
if err != nil {
return containers.NewReadyPromise(daprovider.PayloadResult{}, err)
}
validator.CopyPreimagesInto(r.preimages, result.Preimages)
return containers.NewReadyPromise(daprovider.PayloadResult{Payload: result.Payload}, nil)
}

func (r *RecordingDAPReader) CollectPreimages(batchNum uint64, batchBlockHash common.Hash, sequencerMsg []byte) containers.PromiseInterface[daprovider.PreimagesResult] {
return r.reader.CollectPreimages(batchNum, batchBlockHash, sequencerMsg)
}

func (r *RecordingDAPReader) RecoverPayloadAndPreimages(batchNum uint64, batchBlockHash common.Hash, sequencerMsg []byte) containers.PromiseInterface[daprovider.PayloadAndPreimagesResult] {
return r.reader.RecoverPayloadAndPreimages(batchNum, batchBlockHash, sequencerMsg)
}

// RecordingDAPReaderSource is used for recording preimages related to sequencer batches stored by da providers, given a
// DapReaderSource it implements GetReader method to return a daprovider.Reader interface that records preimgaes. It takes
// in a context variable (corresponding to creation of validation entry) from the MEL validator
type RecordingDAPReaderSource struct {
validatorCtx context.Context
dapReaders arbstate.DapReaderSource
preimages daprovider.PreimagesMap
}

func NewRecordingDAPReaderSource(validatorCtx context.Context, dapReaders arbstate.DapReaderSource) *RecordingDAPReaderSource {
return &RecordingDAPReaderSource{
validatorCtx: validatorCtx,
dapReaders: dapReaders,
preimages: make(daprovider.PreimagesMap),
}
}

func (s *RecordingDAPReaderSource) GetReader(headerByte byte) daprovider.Reader {
reader := s.dapReaders.GetReader(headerByte)
return &RecordingDAPReader{
validatorCtx: s.validatorCtx,
reader: reader,
preimages: s.preimages,
}
}

func (s *RecordingDAPReaderSource) Preimages() daprovider.PreimagesMap { return s.preimages }
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
package melrunner
package melrecording

import (
"context"
"errors"
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"

"github.com/offchainlabs/nitro/arbnode/db/read"
"github.com/offchainlabs/nitro/arbnode/db/schema"
"github.com/offchainlabs/nitro/arbnode/mel"
"github.com/offchainlabs/nitro/arbos/merkleAccumulator"
)

// RecordingDatabase holds an ethdb.KeyValueStore that contains delayed messages stored by native MEL and implements DelayedMessageDatabase
// DelayedMsgDatabase holds an ethdb.KeyValueStore that contains delayed messages stored by native MEL and implements DelayedMessageDatabase
// interface defined in 'mel'. It is solely used for recording of preimages relating to delayed messages needed for MEL validation
type RecordingDatabase struct {
db ethdb.KeyValueStore
preimages map[common.Hash][]byte
type DelayedMsgDatabase struct {
db ethdb.KeyValueStore
preimages map[common.Hash][]byte
initialized bool
}

func NewRecordingDatabase(db ethdb.KeyValueStore) *RecordingDatabase {
return &RecordingDatabase{db, make(map[common.Hash][]byte)}
func NewDelayedMsgDatabase(db ethdb.KeyValueStore) *DelayedMsgDatabase {
return &DelayedMsgDatabase{db, make(map[common.Hash][]byte), false}
}

func (r *RecordingDatabase) Initialize(ctx context.Context, state *mel.State) error {
func (r *DelayedMsgDatabase) initialize(ctx context.Context, state *mel.State) error {
var acc *merkleAccumulator.MerkleAccumulator
for i := state.ParentChainBlockNumber; i > 0; i-- {
seenState, err := getState(ctx, r.db, i)
Expand Down Expand Up @@ -79,13 +83,19 @@ func (r *RecordingDatabase) Initialize(ctx context.Context, state *mel.State) er
return nil
}

func (r *RecordingDatabase) Preimages() map[common.Hash][]byte { return r.preimages }
func (r *DelayedMsgDatabase) Preimages() map[common.Hash][]byte { return r.preimages }

func (r *RecordingDatabase) ReadDelayedMessage(ctx context.Context, state *mel.State, index uint64) (*mel.DelayedInboxMessage, error) {
func (r *DelayedMsgDatabase) ReadDelayedMessage(ctx context.Context, state *mel.State, index uint64) (*mel.DelayedInboxMessage, error) {
if index == 0 { // Init message
// This message cannot be found in the database as it is supposed to be seen and read in the same block, so we persist that in DelayedMessageBacklog
return state.GetDelayedMessageBacklog().GetInitMsg(), nil
}
if !r.initialized {
if err := r.initialize(ctx, state); err != nil {
return nil, fmt.Errorf("error initializing recording database for MEL validation: %w", err)
}
r.initialized = true
}
delayed, err := fetchDelayedMessage(r.db, index)
if err != nil {
return nil, err
Expand All @@ -98,3 +108,19 @@ func (r *RecordingDatabase) ReadDelayedMessage(ctx context.Context, state *mel.S
r.preimages[common.BytesToHash(hashDelayedHash)] = delayedMsgBytes
return delayed, nil
}

func fetchDelayedMessage(db ethdb.KeyValueStore, index uint64) (*mel.DelayedInboxMessage, error) {
delayed, err := read.Value[mel.DelayedInboxMessage](db, read.Key(schema.MelDelayedMessagePrefix, index))
if err != nil {
return nil, err
}
return &delayed, nil
}

func getState(ctx context.Context, db ethdb.KeyValueStore, parentChainBlockNumber uint64) (*mel.State, error) {
state, err := read.Value[mel.State](db, read.Key(schema.MelStatePrefix, parentChainBlockNumber))
if err != nil {
return nil, err
}
return &state, nil
}
4 changes: 2 additions & 2 deletions arbnode/mel/runner/backlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import (
)

// InitializeDelayedMessageBacklog is to be only called by the Start fsm step of MEL. This function fills the backlog based on the seen and read count from the given mel state
func InitializeDelayedMessageBacklog(ctx context.Context, d *mel.DelayedMessageBacklog, db *Database, state *mel.State, finalizedAndReadIndexFetcher func(context.Context) (uint64, error)) error {
func InitializeDelayedMessageBacklog(ctx context.Context, d *mel.DelayedMessageBacklog, db *Database, state *mel.State, finalizedAndReadIndexFetcher func() (uint64, error)) error {
if state.DelayedMessagesSeen == 0 && state.DelayedMessagesRead == 0 { // this is the first mel state so no need to initialize backlog even if the state isn't finalized yet
return nil
}
finalizedDelayedMessagesRead := state.DelayedMessagesRead // Assume to be finalized, then update if needed
var err error
if finalizedAndReadIndexFetcher != nil {
finalizedDelayedMessagesRead, err = finalizedAndReadIndexFetcher(ctx)
finalizedDelayedMessagesRead, err = finalizedAndReadIndexFetcher()
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion arbnode/mel/runner/backlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func TestDelayedMessageBacklogInitialization(t *testing.T) {
require.NoError(t, err)
newDelayedMessageBacklog, err := mel.NewDelayedMessageBacklog(100, func() (uint64, error) { return 0, nil })
require.NoError(t, err)
require.NoError(t, InitializeDelayedMessageBacklog(ctx, newDelayedMessageBacklog, melDB, newState, func(context.Context) (uint64, error) { return 7, nil }))
require.NoError(t, InitializeDelayedMessageBacklog(ctx, newDelayedMessageBacklog, melDB, newState, func() (uint64, error) { return 7, nil }))
// Notice that instead of having seenUnread list from delayed index 13 to 25 inclusive we will have it from 7 to 25 as only till block=7 the chain has finalized and that block has DelayedMessagesRead=7
require.True(t, newDelayedMessageBacklog.Len() == 19)
newState.SetDelayedMessageBacklog(newDelayedMessageBacklog)
Expand Down
30 changes: 4 additions & 26 deletions arbnode/mel/runner/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,11 @@ func (d *Database) setHeadMelStateBlockNum(batch ethdb.KeyValueWriter, parentCha
}

func (d *Database) GetHeadMelStateBlockNum() (uint64, error) {
parentChainBlockNumberBytes, err := d.db.Get(schema.HeadMelStateBlockNumKey)
if err != nil {
return 0, err
}
var parentChainBlockNumber uint64
err = rlp.DecodeBytes(parentChainBlockNumberBytes, &parentChainBlockNumber)
if err != nil {
return 0, err
}
return parentChainBlockNumber, nil
return read.Value[uint64](d.db, schema.HeadMelStateBlockNumKey)
}

func (d *Database) State(ctx context.Context, parentChainBlockNumber uint64) (*mel.State, error) {
return getState(ctx, d.db, parentChainBlockNumber)
}

func getState(ctx context.Context, db ethdb.KeyValueStore, parentChainBlockNumber uint64) (*mel.State, error) {
state, err := read.Value[mel.State](db, read.Key(schema.MelStatePrefix, parentChainBlockNumber))
state, err := read.Value[mel.State](d.db, read.Key(schema.MelStatePrefix, parentChainBlockNumber))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -116,15 +103,10 @@ func (d *Database) SaveBatchMetas(ctx context.Context, state *mel.State, batchMe
}

func (d *Database) fetchBatchMetadata(seqNum uint64) (*mel.BatchMetadata, error) {
key := read.Key(schema.MelSequencerBatchMetaPrefix, seqNum)
batchMetadataBytes, err := d.db.Get(key)
batchMetadata, err := read.Value[mel.BatchMetadata](d.db, read.Key(schema.MelSequencerBatchMetaPrefix, seqNum))
if err != nil {
return nil, err
}
var batchMetadata mel.BatchMetadata
if err = rlp.DecodeBytes(batchMetadataBytes, &batchMetadata); err != nil {
return nil, err
}
return &batchMetadata, nil
}

Expand Down Expand Up @@ -167,11 +149,7 @@ func (d *Database) ReadDelayedMessage(ctx context.Context, state *mel.State, ind
}

func (d *Database) fetchDelayedMessage(index uint64) (*mel.DelayedInboxMessage, error) {
return fetchDelayedMessage(d.db, index)
}

func fetchDelayedMessage(db ethdb.KeyValueStore, index uint64) (*mel.DelayedInboxMessage, error) {
delayed, err := read.Value[mel.DelayedInboxMessage](db, read.Key(schema.MelDelayedMessagePrefix, index))
delayed, err := read.Value[mel.DelayedInboxMessage](d.db, read.Key(schema.MelDelayedMessagePrefix, index))
if err != nil {
return nil, err
}
Expand Down
2 changes: 2 additions & 0 deletions changelog/ganeshvanahalli-nit-4194.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### Added
- Introduces recording of preimages related to posted sequencer batches to DA providers for MEL validation
4 changes: 2 additions & 2 deletions cmd/mel-replay/delayed_message_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/ethereum/go-ethereum/rlp"

"github.com/offchainlabs/nitro/arbnode/mel"
melrecording "github.com/offchainlabs/nitro/arbnode/mel/recording"
"github.com/offchainlabs/nitro/arbnode/mel/runner"
"github.com/offchainlabs/nitro/arbos/arbostypes"
"github.com/offchainlabs/nitro/arbutil"
Expand Down Expand Up @@ -69,8 +70,7 @@ func TestRecordingPreimagesForReadDelayedMessage(t *testing.T) {
require.NoError(t, state.GenerateDelayedMessagesSeenMerklePartialsAndRoot())
require.NoError(t, melDB.SaveState(ctx, state))

recordingDB := melrunner.NewRecordingDatabase(db)
require.NoError(t, recordingDB.Initialize(ctx, state))
recordingDB := melrecording.NewDelayedMsgDatabase(db)
for i := startBlockNum; i < numMsgs; i++ {
require.NoError(t, state.AccumulateDelayedMessage(delayedMessages[i]))
state.DelayedMessagesSeen++
Expand Down
12 changes: 12 additions & 0 deletions cmd/replay/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,18 @@ func (r *DACertificatePreimageReader) CollectPreimages(
})
}

func (r *DACertificatePreimageReader) RecoverPayloadAndPreimages(
batchNum uint64,
batchBlockHash common.Hash,
sequencerMsg []byte,
) containers.PromiseInterface[daprovider.PayloadAndPreimagesResult] {
return containers.DoPromise(context.Background(), func(ctx context.Context) (daprovider.PayloadAndPreimagesResult, error) {
// Stub implementation: RecoverPayloadAndPreimages is only called
// by the MEL validator to gather preimages before validation
return daprovider.PayloadAndPreimagesResult{Preimages: make(daprovider.PreimagesMap), Payload: nil}, nil
})
}

// To generate:
// key, _ := crypto.HexToECDSA("0000000000000000000000000000000000000000000000000000000000000001")
// sig, _ := crypto.Sign(make([]byte, 32), key)
Expand Down
15 changes: 15 additions & 0 deletions daprovider/anytrust/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,21 @@ func (d *reader) CollectPreimages(
})
}

// RecoverPayloadAndPreimages fetches the underlying payload and collects preimages from the DA provider given the batch header information
func (d *reader) RecoverPayloadAndPreimages(
batchNum uint64,
batchBlockHash common.Hash,
sequencerMsg []byte,
) containers.PromiseInterface[daprovider.PayloadAndPreimagesResult] {
return containers.DoPromise(context.Background(), func(ctx context.Context) (daprovider.PayloadAndPreimagesResult, error) {
payload, preimages, err := d.recoverInternal(ctx, batchNum, sequencerMsg, true, true)
return daprovider.PayloadAndPreimagesResult{
Payload: payload,
Preimages: preimages,
}, err
})
}

// NewWriter is generally meant to be only used by nitro.
// DA Providers should implement methods in the DAProviderWriter interface independently
func NewWriter(anyTrustWriter Writer, maxMessageSize int) *writer {
Expand Down
16 changes: 16 additions & 0 deletions daprovider/daclient/daclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,22 @@ func (c *Client) CollectPreimages(
})
}

// RecoverPayloadAndPreimages fetches the underlying payload and collects preimages from the DA provider given the batch header information
func (c *Client) RecoverPayloadAndPreimages(
batchNum uint64,
batchBlockHash common.Hash,
sequencerMsg []byte,
) containers.PromiseInterface[daprovider.PayloadAndPreimagesResult] {
return containers.DoPromise(context.Background(), func(ctx context.Context) (daprovider.PayloadAndPreimagesResult, error) {
var result daprovider.PayloadAndPreimagesResult
err := c.CallContext(ctx, &result, "daprovider_recoverPayloadAndPreimages", hexutil.Uint64(batchNum), batchBlockHash, hexutil.Bytes(sequencerMsg))
if err != nil {
err = fmt.Errorf("error returned from daprovider_recoverPayloadAndPreimages rpc method, err: %w", err)
}
return result, err
})
}

func (c *Client) Store(
message []byte,
timeout uint64,
Expand Down
Loading
Loading