mirror of
https://github.com/arnaucube/hermez-node.git
synced 2026-02-07 11:26:44 +01:00
Merge pull request #437 from hermeznetwork/feature/ethprivkey
Load ethereum private key
This commit is contained in:
@@ -38,7 +38,8 @@ TokenHEZ = "0x5D94e3e7aeC542aB0F9129B9a7BAdeb5B3Ca0f77"
|
|||||||
TokenHEZName = "Hermez Network Token"
|
TokenHEZName = "Hermez Network Token"
|
||||||
|
|
||||||
[Coordinator]
|
[Coordinator]
|
||||||
ForgerAddress = "0x6BB84Cc84D4A34467aD12a2039A312f7029e2071"
|
# ForgerAddress = "0x05c23b938a85ab26A36E6314a0D02080E9ca6BeD" # Non-Boot Coordinator
|
||||||
|
ForgerAddress = "0xb4124ceb3451635dacedd11767f004d8a28c6ee7" # Boot Coordinator
|
||||||
ConfirmBlocks = 10
|
ConfirmBlocks = 10
|
||||||
L1BatchTimeoutPerc = 0.6
|
L1BatchTimeoutPerc = 0.6
|
||||||
ProofServerPollInterval = "1s"
|
ProofServerPollInterval = "1s"
|
||||||
@@ -60,7 +61,7 @@ Path = "/tmp/iden3-test/hermez/txselector"
|
|||||||
Path = "/tmp/iden3-test/hermez/batchbuilder"
|
Path = "/tmp/iden3-test/hermez/batchbuilder"
|
||||||
|
|
||||||
[[Coordinator.ServerProofs]]
|
[[Coordinator.ServerProofs]]
|
||||||
URL = "http://localhost:3000"
|
URL = "http://localhost:3000/api"
|
||||||
|
|
||||||
[Coordinator.EthClient]
|
[Coordinator.EthClient]
|
||||||
CallGasLimit = 300000
|
CallGasLimit = 300000
|
||||||
@@ -73,8 +74,13 @@ CheckLoopInterval = "500ms"
|
|||||||
Attempts = 8
|
Attempts = 8
|
||||||
AttemptsDelay = "200ms"
|
AttemptsDelay = "200ms"
|
||||||
|
|
||||||
|
[Coordinator.EthClient.Keystore]
|
||||||
|
Path = "/tmp/iden3-test/hermez/ethkeystore"
|
||||||
|
Password = "yourpasswordhere"
|
||||||
|
|
||||||
[Coordinator.API]
|
[Coordinator.API]
|
||||||
Coordinator = true
|
Coordinator = true
|
||||||
|
|
||||||
[Coordinator.Debug]
|
[Coordinator.Debug]
|
||||||
BatchPath = "/tmp/iden3-test/hermez/batchesdebug"
|
BatchPath = "/tmp/iden3-test/hermez/batchesdebug"
|
||||||
|
LightScrypt = true
|
||||||
|
|||||||
7
cli/node/load-sk-example.sh
Executable file
7
cli/node/load-sk-example.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Non-Boot Coordinator
|
||||||
|
go run . --mode coord --cfg cfg.buidler.toml importkey --privatekey 0x30f5fddb34cd4166adb2c6003fa6b18f380fd2341376be42cf1c7937004ac7a3
|
||||||
|
|
||||||
|
# Boot Coordinator
|
||||||
|
go run . --mode coord --cfg cfg.buidler.toml importkey --privatekey 0xa8a54b2d8197bc0b19bb8a084031be71835580a01e70a45a13babd16c9bc1563
|
||||||
@@ -4,7 +4,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
ethKeystore "github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/hermeznetwork/hermez-node/config"
|
"github.com/hermeznetwork/hermez-node/config"
|
||||||
"github.com/hermeznetwork/hermez-node/log"
|
"github.com/hermeznetwork/hermez-node/log"
|
||||||
"github.com/hermeznetwork/hermez-node/node"
|
"github.com/hermeznetwork/hermez-node/node"
|
||||||
@@ -15,18 +18,41 @@ import (
|
|||||||
const (
|
const (
|
||||||
flagCfg = "cfg"
|
flagCfg = "cfg"
|
||||||
flagMode = "mode"
|
flagMode = "mode"
|
||||||
|
flagSK = "privatekey"
|
||||||
modeSync = "sync"
|
modeSync = "sync"
|
||||||
modeCoord = "coord"
|
modeCoord = "coord"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdInit(c *cli.Context) error {
|
func cmdImportKey(c *cli.Context) error {
|
||||||
log.Info("Init")
|
_cfg, err := parseCli(c)
|
||||||
cfg, err := parseCli(c)
|
if err != nil {
|
||||||
|
return tracerr.Wrap(fmt.Errorf("error parsing flags and config: %w", err))
|
||||||
|
}
|
||||||
|
if _cfg.mode != node.ModeCoordinator {
|
||||||
|
return tracerr.Wrap(fmt.Errorf("importkey must use mode coordinator"))
|
||||||
|
}
|
||||||
|
cfg := _cfg.node
|
||||||
|
|
||||||
|
scryptN := ethKeystore.StandardScryptN
|
||||||
|
scryptP := ethKeystore.StandardScryptP
|
||||||
|
if cfg.Coordinator.Debug.LightScrypt {
|
||||||
|
scryptN = ethKeystore.LightScryptN
|
||||||
|
scryptP = ethKeystore.LightScryptP
|
||||||
|
}
|
||||||
|
keyStore := ethKeystore.NewKeyStore(cfg.Coordinator.EthClient.Keystore.Path,
|
||||||
|
scryptN, scryptP)
|
||||||
|
hexKey := c.String(flagSK)
|
||||||
|
hexKey = strings.TrimPrefix(hexKey, "0x")
|
||||||
|
sk, err := crypto.HexToECDSA(hexKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tracerr.Wrap(err)
|
return tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
fmt.Println("TODO", cfg)
|
acc, err := keyStore.ImportECDSA(sk, cfg.Coordinator.EthClient.Keystore.Password)
|
||||||
|
if err != nil {
|
||||||
return tracerr.Wrap(err)
|
return tracerr.Wrap(err)
|
||||||
|
}
|
||||||
|
log.Infow("Imported private key", "addr", acc.Address.Hex())
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdRun(c *cli.Context) error {
|
func cmdRun(c *cli.Context) error {
|
||||||
@@ -122,10 +148,16 @@ func main() {
|
|||||||
|
|
||||||
app.Commands = []*cli.Command{
|
app.Commands = []*cli.Command{
|
||||||
{
|
{
|
||||||
Name: "init",
|
Name: "importkey",
|
||||||
Aliases: []string{},
|
Aliases: []string{},
|
||||||
Usage: "Initialize the hermez-node",
|
Usage: "Import ethereum private key",
|
||||||
Action: cmdInit,
|
Action: cmdImportKey,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flagSK,
|
||||||
|
Usage: "ethereum `PRIVATE_KEY` in hex",
|
||||||
|
Required: true,
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "run",
|
Name: "run",
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ type Coordinator struct {
|
|||||||
// AttemptsDelay is delay between attempts do do an eth client
|
// AttemptsDelay is delay between attempts do do an eth client
|
||||||
// RPC call
|
// RPC call
|
||||||
AttemptsDelay Duration `validate:"required"`
|
AttemptsDelay Duration `validate:"required"`
|
||||||
|
Keystore struct {
|
||||||
|
Path string `validate:"required"`
|
||||||
|
Password string `validate:"required"`
|
||||||
|
} `validate:"required"`
|
||||||
} `validate:"required"`
|
} `validate:"required"`
|
||||||
API struct {
|
API struct {
|
||||||
Coordinator bool
|
Coordinator bool
|
||||||
@@ -91,6 +95,9 @@ type Coordinator struct {
|
|||||||
// BatchPath if set, specifies the path where batchInfo is stored
|
// BatchPath if set, specifies the path where batchInfo is stored
|
||||||
// in JSON in every step/update of the pipeline
|
// in JSON in every step/update of the pipeline
|
||||||
BatchPath string
|
BatchPath string
|
||||||
|
// LightScrypt if set, uses light parameters for the ethereum
|
||||||
|
// keystore encryption algorithm.
|
||||||
|
LightScrypt bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -111,6 +112,12 @@ func NewCoordinator(cfg Config,
|
|||||||
cfg.EthClientAttempts))
|
cfg.EthClientAttempts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.DebugBatchPath != "" {
|
||||||
|
if err := os.MkdirAll(cfg.DebugBatchPath, 0744); err != nil {
|
||||||
|
return nil, tracerr.Wrap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
purger := Purger{
|
purger := Purger{
|
||||||
cfg: cfg.Purger,
|
cfg: cfg.Purger,
|
||||||
lastPurgeBlock: 0,
|
lastPurgeBlock: 0,
|
||||||
@@ -147,9 +154,10 @@ func NewCoordinator(cfg Config,
|
|||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Coordinator) newPipeline(ctx context.Context) (*Pipeline, error) {
|
func (c *Coordinator) newPipeline(ctx context.Context,
|
||||||
|
stats *synchronizer.Stats) (*Pipeline, error) {
|
||||||
return NewPipeline(ctx, c.cfg, c.historyDB, c.l2DB, c.txSelector,
|
return NewPipeline(ctx, c.cfg, c.historyDB, c.l2DB, c.txSelector,
|
||||||
c.batchBuilder, c.purger, c.txManager, c.provers, &c.consts)
|
c.batchBuilder, c.purger, c.txManager, c.provers, stats, &c.consts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MsgSyncBlock indicates an update to the Synchronizer stats
|
// MsgSyncBlock indicates an update to the Synchronizer stats
|
||||||
@@ -226,7 +234,7 @@ func (c *Coordinator) syncStats(ctx context.Context, stats *synchronizer.Stats)
|
|||||||
stats.Eth.LastBlock.Num, "batch", stats.Sync.LastBatch)
|
stats.Eth.LastBlock.Num, "batch", stats.Sync.LastBatch)
|
||||||
batchNum := common.BatchNum(stats.Sync.LastBatch)
|
batchNum := common.BatchNum(stats.Sync.LastBatch)
|
||||||
var err error
|
var err error
|
||||||
if c.pipeline, err = c.newPipeline(ctx); err != nil {
|
if c.pipeline, err = c.newPipeline(ctx, stats); err != nil {
|
||||||
return tracerr.Wrap(err)
|
return tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
if err := c.pipeline.Start(batchNum, stats.Sync.LastForgeL1TxsNum,
|
if err := c.pipeline.Start(batchNum, stats.Sync.LastForgeL1TxsNum,
|
||||||
@@ -295,22 +303,16 @@ func (c *Coordinator) handleStopPipeline(ctx context.Context, reason string) err
|
|||||||
func (c *Coordinator) handleMsg(ctx context.Context, msg interface{}) error {
|
func (c *Coordinator) handleMsg(ctx context.Context, msg interface{}) error {
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case MsgSyncBlock:
|
case MsgSyncBlock:
|
||||||
if err := c.handleMsgSyncBlock(ctx, &msg); common.IsErrDone(err) {
|
if err := c.handleMsgSyncBlock(ctx, &msg); err != nil {
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
return tracerr.Wrap(fmt.Errorf("Coordinator.handleMsgSyncBlock error: %w", err))
|
return tracerr.Wrap(fmt.Errorf("Coordinator.handleMsgSyncBlock error: %w", err))
|
||||||
}
|
}
|
||||||
case MsgSyncReorg:
|
case MsgSyncReorg:
|
||||||
if err := c.handleReorg(ctx, &msg.Stats); common.IsErrDone(err) {
|
if err := c.handleReorg(ctx, &msg.Stats); err != nil {
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
return tracerr.Wrap(fmt.Errorf("Coordinator.handleReorg error: %w", err))
|
return tracerr.Wrap(fmt.Errorf("Coordinator.handleReorg error: %w", err))
|
||||||
}
|
}
|
||||||
case MsgStopPipeline:
|
case MsgStopPipeline:
|
||||||
log.Infow("Coordinator received MsgStopPipeline", "reason", msg.Reason)
|
log.Infow("Coordinator received MsgStopPipeline", "reason", msg.Reason)
|
||||||
if err := c.handleStopPipeline(ctx, msg.Reason); common.IsErrDone(err) {
|
if err := c.handleStopPipeline(ctx, msg.Reason); err != nil {
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
return tracerr.Wrap(fmt.Errorf("Coordinator.handleStopPipeline: %w", err))
|
return tracerr.Wrap(fmt.Errorf("Coordinator.handleStopPipeline: %w", err))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -341,7 +343,9 @@ func (c *Coordinator) Start() {
|
|||||||
c.wg.Done()
|
c.wg.Done()
|
||||||
return
|
return
|
||||||
case msg := <-c.msgCh:
|
case msg := <-c.msgCh:
|
||||||
if err := c.handleMsg(c.ctx, msg); err != nil {
|
if err := c.handleMsg(c.ctx, msg); c.ctx.Err() != nil {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
log.Errorw("Coordinator.handleMsg", "err", err)
|
log.Errorw("Coordinator.handleMsg", "err", err)
|
||||||
waitDuration = time.Duration(c.cfg.SyncRetryInterval)
|
waitDuration = time.Duration(c.cfg.SyncRetryInterval)
|
||||||
continue
|
continue
|
||||||
@@ -352,7 +356,9 @@ func (c *Coordinator) Start() {
|
|||||||
waitDuration = time.Duration(longWaitDuration)
|
waitDuration = time.Duration(longWaitDuration)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := c.syncStats(c.ctx, c.stats); err != nil {
|
if err := c.syncStats(c.ctx, c.stats); c.ctx.Err() != nil {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
log.Errorw("Coordinator.syncStats", "err", err)
|
log.Errorw("Coordinator.syncStats", "err", err)
|
||||||
waitDuration = time.Duration(c.cfg.SyncRetryInterval)
|
waitDuration = time.Duration(c.cfg.SyncRetryInterval)
|
||||||
continue
|
continue
|
||||||
@@ -456,7 +462,8 @@ func (t *TxManager) rollupForgeBatch(ctx context.Context, batchInfo *BatchInfo)
|
|||||||
return tracerr.Wrap(err)
|
return tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
log.Errorw("TxManager ethClient.RollupForgeBatch",
|
log.Errorw("TxManager ethClient.RollupForgeBatch",
|
||||||
"attempt", attempt, "err", err, "block", t.lastBlock)
|
"attempt", attempt, "err", err, "block", t.lastBlock,
|
||||||
|
"batchNum", batchInfo.BatchNum)
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -484,6 +491,9 @@ func (t *TxManager) ethTransactionReceipt(ctx context.Context, batchInfo *BatchI
|
|||||||
var err error
|
var err error
|
||||||
for attempt := 0; attempt < t.cfg.EthClientAttempts; attempt++ {
|
for attempt := 0; attempt < t.cfg.EthClientAttempts; attempt++ {
|
||||||
receipt, err = t.ethClient.EthTransactionReceipt(ctx, txHash)
|
receipt, err = t.ethClient.EthTransactionReceipt(ctx, txHash)
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorw("TxManager ethClient.EthTransactionReceipt",
|
log.Errorw("TxManager ethClient.EthTransactionReceipt",
|
||||||
"attempt", attempt, "err", err)
|
"attempt", attempt, "err", err)
|
||||||
@@ -621,6 +631,7 @@ func NewPipeline(ctx context.Context,
|
|||||||
purger *Purger,
|
purger *Purger,
|
||||||
txManager *TxManager,
|
txManager *TxManager,
|
||||||
provers []prover.Client,
|
provers []prover.Client,
|
||||||
|
stats *synchronizer.Stats,
|
||||||
scConsts *synchronizer.SCConsts,
|
scConsts *synchronizer.SCConsts,
|
||||||
) (*Pipeline, error) {
|
) (*Pipeline, error) {
|
||||||
proversPool := NewProversPool(len(provers))
|
proversPool := NewProversPool(len(provers))
|
||||||
@@ -647,6 +658,7 @@ func NewPipeline(ctx context.Context,
|
|||||||
purger: purger,
|
purger: purger,
|
||||||
txManager: txManager,
|
txManager: txManager,
|
||||||
consts: *scConsts,
|
consts: *scConsts,
|
||||||
|
stats: *stats,
|
||||||
statsCh: make(chan synchronizer.Stats, queueLen),
|
statsCh: make(chan synchronizer.Stats, queueLen),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -697,7 +709,7 @@ func (p *Pipeline) Start(batchNum common.BatchNum, lastForgeL1TxsNum int64,
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-p.ctx.Done():
|
case <-p.ctx.Done():
|
||||||
log.Debug("Pipeline forgeBatch loop done")
|
log.Info("Pipeline forgeBatch loop done")
|
||||||
p.wg.Done()
|
p.wg.Done()
|
||||||
return
|
return
|
||||||
case syncStats := <-p.statsCh:
|
case syncStats := <-p.statsCh:
|
||||||
@@ -705,7 +717,7 @@ func (p *Pipeline) Start(batchNum common.BatchNum, lastForgeL1TxsNum int64,
|
|||||||
default:
|
default:
|
||||||
batchNum = p.batchNum + 1
|
batchNum = p.batchNum + 1
|
||||||
batchInfo, err := p.forgeBatch(p.ctx, batchNum, selectionConfig)
|
batchInfo, err := p.forgeBatch(p.ctx, batchNum, selectionConfig)
|
||||||
if common.IsErrDone(err) {
|
if p.ctx.Err() != nil {
|
||||||
continue
|
continue
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
log.Errorw("forgeBatch", "err", err)
|
log.Errorw("forgeBatch", "err", err)
|
||||||
@@ -713,14 +725,16 @@ func (p *Pipeline) Start(batchNum common.BatchNum, lastForgeL1TxsNum int64,
|
|||||||
}
|
}
|
||||||
// 6. Wait for an available server proof (blocking call)
|
// 6. Wait for an available server proof (blocking call)
|
||||||
serverProof, err := p.proversPool.Get(p.ctx)
|
serverProof, err := p.proversPool.Get(p.ctx)
|
||||||
if common.IsErrDone(err) {
|
if p.ctx.Err() != nil {
|
||||||
continue
|
continue
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
log.Errorw("proversPool.Get", "err", err)
|
log.Errorw("proversPool.Get", "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
batchInfo.ServerProof = serverProof
|
batchInfo.ServerProof = serverProof
|
||||||
if err := p.sendServerProof(p.ctx, batchInfo); err != nil {
|
if err := p.sendServerProof(p.ctx, batchInfo); p.ctx.Err() != nil {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
log.Errorw("sendServerProof", "err", err)
|
log.Errorw("sendServerProof", "err", err)
|
||||||
batchInfo.ServerProof = nil
|
batchInfo.ServerProof = nil
|
||||||
p.proversPool.Add(serverProof)
|
p.proversPool.Add(serverProof)
|
||||||
@@ -737,17 +751,17 @@ func (p *Pipeline) Start(batchNum common.BatchNum, lastForgeL1TxsNum int64,
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-p.ctx.Done():
|
case <-p.ctx.Done():
|
||||||
log.Debug("Pipeline waitServerProofSendEth loop done")
|
log.Info("Pipeline waitServerProofSendEth loop done")
|
||||||
p.wg.Done()
|
p.wg.Done()
|
||||||
return
|
return
|
||||||
case batchInfo := <-batchChSentServerProof:
|
case batchInfo := <-batchChSentServerProof:
|
||||||
err := p.waitServerProof(p.ctx, batchInfo)
|
err := p.waitServerProof(p.ctx, batchInfo)
|
||||||
if common.IsErrDone(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// We are done with this serverProof, add it back to the pool
|
// We are done with this serverProof, add it back to the pool
|
||||||
p.proversPool.Add(batchInfo.ServerProof)
|
p.proversPool.Add(batchInfo.ServerProof)
|
||||||
batchInfo.ServerProof = nil
|
batchInfo.ServerProof = nil
|
||||||
|
if p.ctx.Err() != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorw("waitServerProof", "err", err)
|
log.Errorw("waitServerProof", "err", err)
|
||||||
continue
|
continue
|
||||||
@@ -765,7 +779,7 @@ func (p *Pipeline) Stop(ctx context.Context) {
|
|||||||
log.Fatal("Pipeline already stopped")
|
log.Fatal("Pipeline already stopped")
|
||||||
}
|
}
|
||||||
p.started = false
|
p.started = false
|
||||||
log.Debug("Stopping Pipeline...")
|
log.Info("Stopping Pipeline...")
|
||||||
p.cancel()
|
p.cancel()
|
||||||
p.wg.Wait()
|
p.wg.Wait()
|
||||||
for _, prover := range p.provers {
|
for _, prover := range p.provers {
|
||||||
@@ -899,7 +913,7 @@ func (p *Pipeline) waitServerProof(ctx context.Context, batchInfo *BatchInfo) er
|
|||||||
}
|
}
|
||||||
batchInfo.Proof = proof
|
batchInfo.Proof = proof
|
||||||
batchInfo.PublicInputs = pubInputs
|
batchInfo.PublicInputs = pubInputs
|
||||||
batchInfo.ForgeBatchArgs = p.prepareForgeBatchArgs(batchInfo)
|
batchInfo.ForgeBatchArgs = prepareForgeBatchArgs(batchInfo)
|
||||||
batchInfo.TxStatus = TxStatusPending
|
batchInfo.TxStatus = TxStatusPending
|
||||||
p.cfg.debugBatchStore(batchInfo)
|
p.cfg.debugBatchStore(batchInfo)
|
||||||
return nil
|
return nil
|
||||||
@@ -921,7 +935,7 @@ func (p *Pipeline) shouldL1L2Batch() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pipeline) prepareForgeBatchArgs(batchInfo *BatchInfo) *eth.RollupForgeBatchArgs {
|
func prepareForgeBatchArgs(batchInfo *BatchInfo) *eth.RollupForgeBatchArgs {
|
||||||
proof := batchInfo.Proof
|
proof := batchInfo.Proof
|
||||||
zki := batchInfo.ZKInputs
|
zki := batchInfo.ZKInputs
|
||||||
return ð.RollupForgeBatchArgs{
|
return ð.RollupForgeBatchArgs{
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
ethKeystore "github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"github.com/hermeznetwork/hermez-node/batchbuilder"
|
"github.com/hermeznetwork/hermez-node/batchbuilder"
|
||||||
"github.com/hermeznetwork/hermez-node/common"
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
dbUtils "github.com/hermeznetwork/hermez-node/db"
|
dbUtils "github.com/hermeznetwork/hermez-node/db"
|
||||||
@@ -26,6 +29,7 @@ import (
|
|||||||
"github.com/hermeznetwork/hermez-node/txprocessor"
|
"github.com/hermeznetwork/hermez-node/txprocessor"
|
||||||
"github.com/hermeznetwork/hermez-node/txselector"
|
"github.com/hermeznetwork/hermez-node/txselector"
|
||||||
"github.com/hermeznetwork/tracerr"
|
"github.com/hermeznetwork/tracerr"
|
||||||
|
"github.com/iden3/go-merkletree"
|
||||||
"github.com/iden3/go-merkletree/db/pebble"
|
"github.com/iden3/go-merkletree/db/pebble"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -428,8 +432,9 @@ func TestPipelineShouldL1L2Batch(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ethClient := test.NewClient(true, &timer, &bidder, ethClientSetup)
|
ethClient := test.NewClient(true, &timer, &bidder, ethClientSetup)
|
||||||
modules := newTestModules(t)
|
modules := newTestModules(t)
|
||||||
|
var stats synchronizer.Stats
|
||||||
coord := newTestCoordinator(t, forger, ethClient, ethClientSetup, modules)
|
coord := newTestCoordinator(t, forger, ethClient, ethClientSetup, modules)
|
||||||
pipeline, err := coord.newPipeline(ctx)
|
pipeline, err := coord.newPipeline(ctx, &stats)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
pipeline.vars = coord.vars
|
pipeline.vars = coord.vars
|
||||||
|
|
||||||
@@ -439,8 +444,6 @@ func TestPipelineShouldL1L2Batch(t *testing.T) {
|
|||||||
l1BatchTimeoutPerc := pipeline.cfg.L1BatchTimeoutPerc
|
l1BatchTimeoutPerc := pipeline.cfg.L1BatchTimeoutPerc
|
||||||
l1BatchTimeout := ethClientSetup.RollupVariables.ForgeL1L2BatchTimeout
|
l1BatchTimeout := ethClientSetup.RollupVariables.ForgeL1L2BatchTimeout
|
||||||
|
|
||||||
var stats synchronizer.Stats
|
|
||||||
|
|
||||||
startBlock := int64(100)
|
startBlock := int64(100)
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -576,11 +579,6 @@ func TestPipeline1(t *testing.T) {
|
|||||||
modules := newTestModules(t)
|
modules := newTestModules(t)
|
||||||
coord := newTestCoordinator(t, forger, ethClient, ethClientSetup, modules)
|
coord := newTestCoordinator(t, forger, ethClient, ethClientSetup, modules)
|
||||||
sync := newTestSynchronizer(t, ethClient, ethClientSetup, modules)
|
sync := newTestSynchronizer(t, ethClient, ethClientSetup, modules)
|
||||||
pipeline, err := coord.newPipeline(ctx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.NotNil(t, sync)
|
|
||||||
require.NotNil(t, pipeline)
|
|
||||||
|
|
||||||
// preload the synchronier (via the test ethClient) some tokens and
|
// preload the synchronier (via the test ethClient) some tokens and
|
||||||
// users with positive balances
|
// users with positive balances
|
||||||
@@ -589,6 +587,9 @@ func TestPipeline1(t *testing.T) {
|
|||||||
batchNum := common.BatchNum(syncStats.Sync.LastBatch)
|
batchNum := common.BatchNum(syncStats.Sync.LastBatch)
|
||||||
syncSCVars := sync.SCVars()
|
syncSCVars := sync.SCVars()
|
||||||
|
|
||||||
|
pipeline, err := coord.newPipeline(ctx, syncStats)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Insert some l2txs in the Pool
|
// Insert some l2txs in the Pool
|
||||||
setPool := `
|
setPool := `
|
||||||
Type: PoolL2
|
Type: PoolL2
|
||||||
@@ -713,6 +714,80 @@ func TestCoordinatorStress(t *testing.T) {
|
|||||||
coord.Stop()
|
coord.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRollupForgeBatch(t *testing.T) {
|
||||||
|
if os.Getenv("TEST_ROLLUP_FORGE_BATCH") == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const web3URL = "http://localhost:8545"
|
||||||
|
const password = "test"
|
||||||
|
addr := ethCommon.HexToAddress("0xb4124ceb3451635dacedd11767f004d8a28c6ee7")
|
||||||
|
sk, err := crypto.HexToECDSA(
|
||||||
|
"a8a54b2d8197bc0b19bb8a084031be71835580a01e70a45a13babd16c9bc1563")
|
||||||
|
require.NoError(t, err)
|
||||||
|
rollupAddr := ethCommon.HexToAddress("0x8EEaea23686c319133a7cC110b840d1591d9AeE0")
|
||||||
|
pathKeystore, err := ioutil.TempDir("", "tmpKeystore")
|
||||||
|
require.NoError(t, err)
|
||||||
|
deleteme = append(deleteme, pathKeystore)
|
||||||
|
ctx := context.Background()
|
||||||
|
batchInfo := &BatchInfo{}
|
||||||
|
proofClient := &prover.MockClient{}
|
||||||
|
chainID := uint16(0)
|
||||||
|
|
||||||
|
ethClient, err := ethclient.Dial(web3URL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
ethCfg := eth.EthereumConfig{
|
||||||
|
CallGasLimit: 300000,
|
||||||
|
DeployGasLimit: 1000000,
|
||||||
|
GasPriceDiv: 100,
|
||||||
|
ReceiptTimeout: 60 * time.Second,
|
||||||
|
IntervalReceiptLoop: 500 * time.Millisecond,
|
||||||
|
}
|
||||||
|
scryptN := ethKeystore.LightScryptN
|
||||||
|
scryptP := ethKeystore.LightScryptP
|
||||||
|
keyStore := ethKeystore.NewKeyStore(pathKeystore,
|
||||||
|
scryptN, scryptP)
|
||||||
|
account, err := keyStore.ImportECDSA(sk, password)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, account.Address, addr)
|
||||||
|
err = keyStore.Unlock(account, password)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
client, err := eth.NewClient(ethClient, &account, keyStore, ð.ClientConfig{
|
||||||
|
Ethereum: ethCfg,
|
||||||
|
Rollup: eth.RollupConfig{
|
||||||
|
Address: rollupAddr,
|
||||||
|
},
|
||||||
|
Auction: eth.AuctionConfig{
|
||||||
|
Address: ethCommon.Address{},
|
||||||
|
TokenHEZ: eth.TokenConfig{
|
||||||
|
Address: ethCommon.Address{},
|
||||||
|
Name: "HEZ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
WDelayer: eth.WDelayerConfig{
|
||||||
|
Address: ethCommon.Address{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
zkInputs := common.NewZKInputs(chainID, 100, 24, 512, 32, big.NewInt(1))
|
||||||
|
zkInputs.Metadata.NewStateRootRaw = &merkletree.Hash{1}
|
||||||
|
zkInputs.Metadata.NewExitRootRaw = &merkletree.Hash{2}
|
||||||
|
batchInfo.ZKInputs = zkInputs
|
||||||
|
err = proofClient.CalculateProof(ctx, batchInfo.ZKInputs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
proof, pubInputs, err := proofClient.GetProof(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
batchInfo.Proof = proof
|
||||||
|
batchInfo.PublicInputs = pubInputs
|
||||||
|
|
||||||
|
batchInfo.ForgeBatchArgs = prepareForgeBatchArgs(batchInfo)
|
||||||
|
_, err = client.RollupForgeBatch(batchInfo.ForgeBatchArgs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
batchInfo.Proof = proof
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Test Reorg
|
// TODO: Test Reorg
|
||||||
// TODO: Test Pipeline
|
// TODO: Test Pipeline
|
||||||
// TODO: Test TxMonitor
|
// TODO: Test TxMonitor
|
||||||
|
|||||||
@@ -112,6 +112,10 @@ func TestNewStateDBIntermediateState(t *testing.T) {
|
|||||||
sdb, err = NewStateDB(dir, 128, TypeTxSelector, 0)
|
sdb, err = NewStateDB(dir, 128, TypeTxSelector, 0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
bn, err = sdb.db.GetCurrentBatch()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, common.BatchNum(1), bn)
|
||||||
|
|
||||||
v, err = sdb.db.DB().Get(k0)
|
v, err = sdb.db.DB().Get(k0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, v0, v)
|
assert.Equal(t, v0, v)
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ type ClientConfig struct {
|
|||||||
|
|
||||||
// NewClient creates a new Client to interact with Ethereum and the Hermez smart contracts.
|
// NewClient creates a new Client to interact with Ethereum and the Hermez smart contracts.
|
||||||
func NewClient(client *ethclient.Client, account *accounts.Account, ks *ethKeystore.KeyStore, cfg *ClientConfig) (*Client, error) {
|
func NewClient(client *ethclient.Client, account *accounts.Account, ks *ethKeystore.KeyStore, cfg *ClientConfig) (*Client, error) {
|
||||||
ethereumClient := NewEthereumClient(client, account, ks, &cfg.Ethereum)
|
ethereumClient, err := NewEthereumClient(client, account, ks, &cfg.Ethereum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, tracerr.Wrap(err)
|
||||||
|
}
|
||||||
auctionClient, err := NewAuctionClient(ethereumClient, cfg.Auction.Address, cfg.Auction.TokenHEZ)
|
auctionClient, err := NewAuctionClient(ethereumClient, cfg.Auction.Address, cfg.Auction.TokenHEZ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, tracerr.Wrap(err)
|
return nil, tracerr.Wrap(err)
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ type EthereumConfig struct {
|
|||||||
// EthereumClient is an ethereum client to call Smart Contract methods and check blockchain information.
|
// EthereumClient is an ethereum client to call Smart Contract methods and check blockchain information.
|
||||||
type EthereumClient struct {
|
type EthereumClient struct {
|
||||||
client *ethclient.Client
|
client *ethclient.Client
|
||||||
|
chainID *big.Int
|
||||||
account *accounts.Account
|
account *accounts.Account
|
||||||
ks *ethKeystore.KeyStore
|
ks *ethKeystore.KeyStore
|
||||||
ReceiptTimeout time.Duration
|
ReceiptTimeout time.Duration
|
||||||
@@ -82,7 +83,7 @@ type EthereumClient struct {
|
|||||||
|
|
||||||
// NewEthereumClient creates a EthereumClient instance. The account is not mandatory (it can
|
// NewEthereumClient creates a EthereumClient instance. The account is not mandatory (it can
|
||||||
// be nil). If the account is nil, CallAuth will fail with ErrAccountNil.
|
// be nil). If the account is nil, CallAuth will fail with ErrAccountNil.
|
||||||
func NewEthereumClient(client *ethclient.Client, account *accounts.Account, ks *ethKeystore.KeyStore, config *EthereumConfig) *EthereumClient {
|
func NewEthereumClient(client *ethclient.Client, account *accounts.Account, ks *ethKeystore.KeyStore, config *EthereumConfig) (*EthereumClient, error) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = &EthereumConfig{
|
config = &EthereumConfig{
|
||||||
CallGasLimit: defaultCallGasLimit,
|
CallGasLimit: defaultCallGasLimit,
|
||||||
@@ -92,7 +93,7 @@ func NewEthereumClient(client *ethclient.Client, account *accounts.Account, ks *
|
|||||||
IntervalReceiptLoop: defaultIntervalReceiptLoop,
|
IntervalReceiptLoop: defaultIntervalReceiptLoop,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &EthereumClient{
|
c := &EthereumClient{
|
||||||
client: client,
|
client: client,
|
||||||
account: account,
|
account: account,
|
||||||
ks: ks,
|
ks: ks,
|
||||||
@@ -100,6 +101,12 @@ func NewEthereumClient(client *ethclient.Client, account *accounts.Account, ks *
|
|||||||
config: config,
|
config: config,
|
||||||
opts: newCallOpts(),
|
opts: newCallOpts(),
|
||||||
}
|
}
|
||||||
|
chainID, err := c.EthChainID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, tracerr.Wrap(err)
|
||||||
|
}
|
||||||
|
c.chainID = chainID
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EthChainID returns the ChainID of the ethereum network
|
// EthChainID returns the ChainID of the ethereum network
|
||||||
@@ -147,8 +154,7 @@ func (c *EthereumClient) CallAuth(gasLimit uint64,
|
|||||||
gasPrice.Add(gasPrice, inc)
|
gasPrice.Add(gasPrice, inc)
|
||||||
log.Debugw("Transaction metadata", "gasPrice", gasPrice)
|
log.Debugw("Transaction metadata", "gasPrice", gasPrice)
|
||||||
|
|
||||||
// TODO: Set the correct chainID
|
auth, err := bind.NewKeyStoreTransactorWithChainID(c.ks, *c.account, c.chainID)
|
||||||
auth, err := bind.NewKeyStoreTransactorWithChainID(c.ks, *c.account, big.NewInt(0))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, tracerr.Wrap(err)
|
return nil, tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import (
|
|||||||
func TestEthERC20(t *testing.T) {
|
func TestEthERC20(t *testing.T) {
|
||||||
ethClient, err := ethclient.Dial(ethClientDialURL)
|
ethClient, err := ethclient.Dial(ethClientDialURL)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
client := NewEthereumClient(ethClient, auxAccount, ks, nil)
|
client, err := NewEthereumClient(ethClient, auxAccount, ks, nil)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
consts, err := client.EthERC20Consts(tokenHEZAddressConst)
|
consts, err := client.EthERC20Consts(tokenHEZAddressConst)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|||||||
@@ -164,7 +164,10 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Controllable Governance Address
|
// Controllable Governance Address
|
||||||
ethereumClientGov := NewEthereumClient(ethClient, governanceAccount, ks, nil)
|
ethereumClientGov, err := NewEthereumClient(ethClient, governanceAccount, ks, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
auctionClient, err = NewAuctionClient(ethereumClientGov, auctionAddressConst, tokenHEZ)
|
auctionClient, err = NewAuctionClient(ethereumClientGov, auctionAddressConst, tokenHEZ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -186,10 +189,22 @@ func TestMain(m *testing.M) {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ethereumClientEmergencyCouncil = NewEthereumClient(ethClient, emergencyCouncilAccount, ks, nil)
|
ethereumClientEmergencyCouncil, err = NewEthereumClient(ethClient, emergencyCouncilAccount, ks, nil)
|
||||||
ethereumClientAux = NewEthereumClient(ethClient, auxAccount, ks, nil)
|
if err != nil {
|
||||||
ethereumClientAux2 = NewEthereumClient(ethClient, aux2Account, ks, nil)
|
log.Fatal(err)
|
||||||
ethereumClientHermez = NewEthereumClient(ethClient, hermezRollupTestAccount, ks, nil)
|
}
|
||||||
|
ethereumClientAux, err = NewEthereumClient(ethClient, auxAccount, ks, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
ethereumClientAux2, err = NewEthereumClient(ethClient, aux2Account, ks, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
ethereumClientHermez, err = NewEthereumClient(ethClient, hermezRollupTestAccount, ks, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
exitVal = m.Run()
|
exitVal = m.Run()
|
||||||
}
|
}
|
||||||
|
|||||||
29
node/node.go
29
node/node.go
@@ -7,6 +7,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
ethKeystore "github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"github.com/gin-contrib/cors"
|
"github.com/gin-contrib/cors"
|
||||||
@@ -87,6 +89,8 @@ func NewNode(mode Mode, cfg *config.Node) (*Node, error) {
|
|||||||
return nil, tracerr.Wrap(err)
|
return nil, tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
var ethCfg eth.EthereumConfig
|
var ethCfg eth.EthereumConfig
|
||||||
|
var account *accounts.Account
|
||||||
|
var keyStore *ethKeystore.KeyStore
|
||||||
if mode == ModeCoordinator {
|
if mode == ModeCoordinator {
|
||||||
ethCfg = eth.EthereumConfig{
|
ethCfg = eth.EthereumConfig{
|
||||||
CallGasLimit: cfg.Coordinator.EthClient.CallGasLimit,
|
CallGasLimit: cfg.Coordinator.EthClient.CallGasLimit,
|
||||||
@@ -95,8 +99,31 @@ func NewNode(mode Mode, cfg *config.Node) (*Node, error) {
|
|||||||
ReceiptTimeout: cfg.Coordinator.EthClient.ReceiptTimeout.Duration,
|
ReceiptTimeout: cfg.Coordinator.EthClient.ReceiptTimeout.Duration,
|
||||||
IntervalReceiptLoop: cfg.Coordinator.EthClient.ReceiptLoopInterval.Duration,
|
IntervalReceiptLoop: cfg.Coordinator.EthClient.ReceiptLoopInterval.Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scryptN := ethKeystore.StandardScryptN
|
||||||
|
scryptP := ethKeystore.StandardScryptP
|
||||||
|
if cfg.Coordinator.Debug.LightScrypt {
|
||||||
|
scryptN = ethKeystore.LightScryptN
|
||||||
|
scryptP = ethKeystore.LightScryptP
|
||||||
}
|
}
|
||||||
client, err := eth.NewClient(ethClient, nil, nil, ð.ClientConfig{
|
keyStore = ethKeystore.NewKeyStore(cfg.Coordinator.EthClient.Keystore.Path,
|
||||||
|
scryptN, scryptP)
|
||||||
|
if !keyStore.HasAddress(cfg.Coordinator.ForgerAddress) {
|
||||||
|
return nil, tracerr.Wrap(fmt.Errorf(
|
||||||
|
"ethereum keystore doesn't have the key for address %v",
|
||||||
|
cfg.Coordinator.ForgerAddress))
|
||||||
|
}
|
||||||
|
account = &accounts.Account{
|
||||||
|
Address: cfg.Coordinator.ForgerAddress,
|
||||||
|
}
|
||||||
|
if err := keyStore.Unlock(*account,
|
||||||
|
cfg.Coordinator.EthClient.Keystore.Password); err != nil {
|
||||||
|
return nil, tracerr.Wrap(err)
|
||||||
|
}
|
||||||
|
log.Infow("Forger ethereum account unlocked in the keystore",
|
||||||
|
"addr", cfg.Coordinator.ForgerAddress)
|
||||||
|
}
|
||||||
|
client, err := eth.NewClient(ethClient, account, keyStore, ð.ClientConfig{
|
||||||
Ethereum: ethCfg,
|
Ethereum: ethCfg,
|
||||||
Rollup: eth.RollupConfig{
|
Rollup: eth.RollupConfig{
|
||||||
Address: cfg.SmartContracts.Rollup,
|
Address: cfg.SmartContracts.Rollup,
|
||||||
|
|||||||
Reference in New Issue
Block a user