Browse Source

Implement initial version of test.Client

feature/sql-semaphore1
Eduard S 3 years ago
parent
commit
bcd93fee36
9 changed files with 621 additions and 106 deletions
  1. +1
    -0
      .gitignore
  2. +20
    -0
      eth/auction.go
  3. +16
    -12
      eth/ethereum.go
  4. +57
    -23
      eth/rollup.go
  5. +1
    -0
      go.mod
  6. +2
    -0
      go.sum
  7. +12
    -13
      synchronizer/synchronizer.go
  8. +313
    -33
      test/ethclient.go
  9. +199
    -25
      test/ethclient_test.go

+ 1
- 0
.gitignore

@ -0,0 +1 @@
gotest.sh

+ 20
- 0
eth/auction.go

@ -171,6 +171,26 @@ type AuctionEvents struct { //nolint:structcheck
HEZClaimed []AuctionEventHEZClaimed
}
// NewAuctionEvents creates an empty AuctionEvents with the slices initialized.
func NewAuctionEvents() AuctionEvents {
return AuctionEvents{
NewBid: make([]AuctionEventNewBid, 0),
NewSlotDeadline: make([]AuctionEventNewSlotDeadline, 0),
NewClosedAuctionSlots: make([]AuctionEventNewClosedAuctionSlots, 0),
NewOutbidding: make([]AuctionEventNewOutbidding, 0),
NewDonationAddress: make([]AuctionEventNewDonationAddress, 0),
NewBootCoordinator: make([]AuctionEventNewBootCoordinator, 0),
NewOpenAuctionSlots: make([]AuctionEventNewOpenAuctionSlots, 0),
NewAllocationRatio: make([]AuctionEventNewAllocationRatio, 0),
NewCoordinator: make([]AuctionEventNewCoordinator, 0),
CoordinatorUpdated: make([]AuctionEventCoordinatorUpdated, 0),
NewForgeAllocated: make([]AuctionEventNewForgeAllocated, 0),
NewMinBidEpoch: make([]AuctionEventNewMinBidEpoch, 0),
NewForge: make([]AuctionEventNewForge, 0),
HEZClaimed: make([]AuctionEventHEZClaimed, 0),
}
}
// AuctionInterface is the inteface to to Auction Smart Contract
type AuctionInterface interface {
//

+ 16
- 12
eth/ethereum.go

@ -18,9 +18,9 @@ import (
// EthereumInterface is the interface to Ethereum
type EthereumInterface interface {
EthCurrentBlock() (*big.Int, error)
EthHeaderByNumber(context.Context, *big.Int) (*types.Header, error)
EthBlockByNumber(context.Context, *big.Int) (*common.Block, error)
EthCurrentBlock() (int64, error)
// EthHeaderByNumber(context.Context, *big.Int) (*types.Header, error)
EthBlockByNumber(context.Context, int64) (*common.Block, error)
}
var (
@ -214,29 +214,33 @@ func (c *EthereumClient) waitReceipt(ctx context.Context, tx *types.Transaction,
}
// EthCurrentBlock returns the current block number in the blockchain
func (c *EthereumClient) EthCurrentBlock() (*big.Int, error) {
func (c *EthereumClient) EthCurrentBlock() (int64, error) {
ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
defer cancel()
header, err := c.client.HeaderByNumber(ctx, nil)
if err != nil {
return nil, err
return 0, err
}
return header.Number, nil
return header.Number.Int64(), nil
}
// EthHeaderByNumber internally calls ethclient.Client HeaderByNumber
func (c *EthereumClient) EthHeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
return c.client.HeaderByNumber(ctx, number)
}
// func (c *EthereumClient) EthHeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
// return c.client.HeaderByNumber(ctx, number)
// }
// EthBlockByNumber internally calls ethclient.Client BlockByNumber and returns *common.Block
func (c *EthereumClient) EthBlockByNumber(ctx context.Context, number *big.Int) (*common.Block, error) {
block, err := c.client.BlockByNumber(ctx, number)
func (c *EthereumClient) EthBlockByNumber(ctx context.Context, number int64) (*common.Block, error) {
blockNum := big.NewInt(number)
if number == 0 {
blockNum = nil
}
block, err := c.client.BlockByNumber(ctx, blockNum)
if err != nil {
return nil, err
}
b := &common.Block{
EthBlockNum: block.Number().Uint64(),
EthBlockNum: block.Number().Int64(),
Timestamp: time.Unix(int64(block.Time()), 0),
Hash: block.Hash(),
}

+ 57
- 23
eth/rollup.go

@ -5,10 +5,19 @@ import (
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/utils"
"github.com/iden3/go-iden3-crypto/babyjub"
)
const (
// FeeIdxCoordinatorLen is the number of tokens the coordinator can use
// to collect fees (determines the number of tokens that the
// coordinator can collect fees from). This value is determined by the
// circuit.
FeeIdxCoordinatorLen = 64
)
// RollupConstants are the constants of the Rollup Smart Contract
type RollupConstants struct {
// Maxim Deposit allowed
@ -41,21 +50,28 @@ type RollupVariables struct {
// QueueStruct is the queue of L1Txs for a batch
//nolint:structcheck
type QueueStruct struct {
L1TxQueue [][]byte
CurrentIndex int64
L1TxQueue []common.L1Tx
TotalL1TxFee *big.Int
}
// NewQueueStruct creates a new clear QueueStruct.
func NewQueueStruct() *QueueStruct {
return &QueueStruct{
L1TxQueue: make([]common.L1Tx, 0),
TotalL1TxFee: big.NewInt(0),
}
}
// RollupState represents the state of the Rollup in the Smart Contract
//nolint:structcheck,unused
type RollupState struct {
StateRoot *big.Int
ExitRoots []*big.Int
ExiNullifierMap map[[256 / 8]byte]bool
ExitNullifierMap map[[256 / 8]byte]bool
TokenList []ethCommon.Address
TokenMap map[ethCommon.Address]bool
mapL1TxQueue map[int64]QueueStruct
LastLTxBatch int64
MapL1TxQueue map[int64]*QueueStruct
LastL1L2Batch int64
CurrentToForgeL1TxsNum int64
LastToForgeL1TxsNum int64
CurrentIdx int64
@ -63,9 +79,7 @@ type RollupState struct {
// RollupEventL1UserTx is an event of the Rollup Smart Contract
type RollupEventL1UserTx struct {
L1UserTx []byte
ToForgeL1TxsNum int64
Position int
L1Tx common.L1Tx
}
// RollupEventAddToken is an event of the Rollup Smart Contract
@ -76,7 +90,8 @@ type RollupEventAddToken struct {
// RollupEventForgeBatch is an event of the Rollup Smart Contract
type RollupEventForgeBatch struct {
BatchNum int64
BatchNum int64
EthTxHash ethCommon.Hash
}
// RollupEventUpdateForgeL1Timeout is an event of the Rollup Smart Contract
@ -117,21 +132,35 @@ type RollupEvents struct { //nolint:structcheck
Withdraw []RollupEventWithdraw
}
// NewRollupEvents creates an empty RollupEvents with the slices initialized.
func NewRollupEvents() RollupEvents {
return RollupEvents{
L1UserTx: make([]RollupEventL1UserTx, 0),
AddToken: make([]RollupEventAddToken, 0),
ForgeBatch: make([]RollupEventForgeBatch, 0),
UpdateForgeL1Timeout: make([]RollupEventUpdateForgeL1Timeout, 0),
UpdateFeeL1UserTx: make([]RollupEventUpdateFeeL1UserTx, 0),
UpdateFeeAddToken: make([]RollupEventUpdateFeeAddToken, 0),
UpdateTokenHez: make([]RollupEventUpdateTokenHez, 0),
Withdraw: make([]RollupEventWithdraw, 0),
}
}
// RollupForgeBatchArgs are the arguments to the ForgeBatch function in the Rollup Smart Contract
//nolint:structcheck,unused
type RollupForgeBatchArgs struct {
proofA [2]*big.Int
proofB [2][2]*big.Int
proofC [2]*big.Int
newLastIdx int64
newStRoot *big.Int
newExitRoot *big.Int
// TODO: Replace compressedL1CoordinatorTx, l2TxsData, feeIdxCoordinator for vectors
compressedL1CoordinatorTx []byte
l2TxsData []byte
feeIdxCoordinator []byte
verifierIdx int64
l1Batch bool
ProofA [2]*big.Int
ProofB [2][2]*big.Int
ProofC [2]*big.Int
NewLastIdx int64
NewStRoot *big.Int
NewExitRoot *big.Int
L1CoordinatorTxs []*common.L1Tx
L2Txs []*common.L2Tx
FeeIdxCoordinator []common.Idx
// Circuit selector
VerifierIdx int64
L1Batch bool
}
// RollupInterface is the inteface to to Rollup Smart Contract
@ -176,7 +205,7 @@ type RollupInterface interface {
RollupConstants() (*RollupConstants, error)
RollupEventsByBlock(blockNum int64) (*RollupEvents, *ethCommon.Hash, error)
RollupForgeBatchArgs(*types.Transaction) (*RollupForgeBatchArgs, error)
RollupForgeBatchArgs(ethCommon.Hash) (*RollupForgeBatchArgs, error)
}
//
@ -293,6 +322,11 @@ func (c *RollupClient) RollupEventsByBlock(blockNum int64) (*RollupEvents, *ethC
}
// RollupForgeBatchArgs returns the arguments used in a ForgeBatch call in the Rollup Smart Contract in the given transaction
func (c *RollupClient) RollupForgeBatchArgs(transaction *types.Transaction) (*RollupForgeBatchArgs, error) {
func (c *RollupClient) RollupForgeBatchArgs(ethTxHash ethCommon.Hash) (*RollupForgeBatchArgs, error) {
// tx := client.TransactionByHash(ethTxHash) -> types.Transaction
// txData := types.Transaction -> Data()
// m := abi.MethodById(txData) -> Method
// m.Inputs.Unpack(txData) -> Args
// client.TransactionReceipt()?
return nil, errTODO
}

+ 1
- 0
go.mod

@ -13,6 +13,7 @@ require (
github.com/jinzhu/gorm v1.9.15
github.com/jmoiron/sqlx v1.2.0
github.com/lib/pq v1.8.0
github.com/mitchellh/copystructure v1.0.0
github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351
github.com/russross/meddler v1.0.0
github.com/stretchr/testify v1.6.1

+ 2
- 0
go.sum

@ -399,6 +399,7 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -408,6 +409,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

+ 12
- 13
synchronizer/synchronizer.go

@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"errors"
"math/big"
"sync"
ethCommon "github.com/ethereum/go-ethereum/common"
@ -50,7 +49,7 @@ type BlockData struct {
// Status is returned by the Status method
type Status struct {
CurrentBlock uint64
CurrentBlock int64
CurrentBatch common.BatchNum
CurrentForgerAddr ethCommon.Address
NextForgerAddr ethCommon.Address
@ -82,15 +81,15 @@ func (s *Synchronizer) Sync() error {
s.mux.Lock()
defer s.mux.Unlock()
var lastStoredForgeL1TxsNum uint64
var lastStoredForgeL1TxsNum int64
// TODO: Get this information from ethClient once it's implemented
// for the moment we will get the latestblock - 20 as firstSavedBlock
latestBlock, err := s.ethClient.EthBlockByNumber(context.Background(), nil)
latestBlock, err := s.ethClient.EthBlockByNumber(context.Background(), 0)
if err != nil {
return err
}
s.firstSavedBlock, err = s.ethClient.EthBlockByNumber(context.Background(), big.NewInt(int64(latestBlock.EthBlockNum-blocksToSync)))
s.firstSavedBlock, err = s.ethClient.EthBlockByNumber(context.Background(), latestBlock.EthBlockNum-blocksToSync)
if err != nil {
return err
}
@ -107,7 +106,7 @@ func (s *Synchronizer) Sync() error {
lastSavedBlock = s.firstSavedBlock
} else {
// Get the latest block we have in History DB from blockchain to detect a reorg
ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), big.NewInt(int64(lastSavedBlock.EthBlockNum)))
ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), lastSavedBlock.EthBlockNum)
if err != nil {
return err
}
@ -135,10 +134,10 @@ func (s *Synchronizer) Sync() error {
return err
}
log.Debugf("Blocks to sync: %v (lastSavedBlock: %v, latestBlock: %v)", latestBlockNum.Uint64()-lastSavedBlock.EthBlockNum, lastSavedBlock.EthBlockNum, latestBlockNum)
log.Debugf("Blocks to sync: %v (lastSavedBlock: %v, latestBlock: %v)", latestBlockNum-lastSavedBlock.EthBlockNum, lastSavedBlock.EthBlockNum, latestBlockNum)
for lastSavedBlock.EthBlockNum < latestBlockNum.Uint64() {
ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), big.NewInt(int64(lastSavedBlock.EthBlockNum+1)))
for lastSavedBlock.EthBlockNum < latestBlockNum {
ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), lastSavedBlock.EthBlockNum+1)
if err != nil {
return err
}
@ -181,7 +180,7 @@ func (s *Synchronizer) reorg(uncleBlock *common.Block) error {
// Iterate History DB and the blokchain looking for the latest valid block
for !found && blockNum > s.firstSavedBlock.EthBlockNum {
header, err := s.ethClient.EthHeaderByNumber(context.Background(), big.NewInt(int64(blockNum)))
ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), blockNum)
if err != nil {
return err
}
@ -190,7 +189,7 @@ func (s *Synchronizer) reorg(uncleBlock *common.Block) error {
if err != nil {
return err
}
if block.Hash == header.Hash() {
if block.Hash == ethBlock.Hash {
found = true
log.Debugf("Found valid block: %v", blockNum)
} else {
@ -255,12 +254,12 @@ func (s *Synchronizer) Status() (*Status, error) {
// TODO: Get CurrentForgerAddr & NextForgerAddr
// Check if Synchronizer is synchronized
status.Synchronized = status.CurrentBlock == latestBlockNum.Uint64()
status.Synchronized = status.CurrentBlock == latestBlockNum
return status, nil
}
// rollupSync gets information from the Rollup Contract
func (s *Synchronizer) rollupSync(block *common.Block, lastStoredForgeL1TxsNum uint64) (*BlockData, []*BatchData, error) {
func (s *Synchronizer) rollupSync(block *common.Block, lastStoredForgeL1TxsNum int64) (*BlockData, []*BatchData, error) {
// To be implemented
return nil, nil, nil
}

+ 313
- 33
test/ethclient.go

@ -2,6 +2,8 @@ package test
import (
"context"
"encoding/binary"
"encoding/json"
"fmt"
"math/big"
"time"
@ -13,67 +15,273 @@ import (
"github.com/hermeznetwork/hermez-node/log"
"github.com/hermeznetwork/hermez-node/utils"
"github.com/iden3/go-iden3-crypto/babyjub"
"github.com/mitchellh/copystructure"
)
// RollupBlock stores all the data related to the Rollup SC from an ethereum block
type RollupBlock struct {
State eth.RollupState
Vars eth.RollupVariables
Events eth.RollupEvents
}
// AuctionBlock stores all the data related to the Auction SC from an ethereum block
type AuctionBlock struct {
State eth.AuctionState
Vars eth.AuctionVariables
Events eth.AuctionEvents
}
// EthereumBlock stores all the generic data related to the an ethereum block
type EthereumBlock struct {
BlockNum int64
Time int64
Hash ethCommon.Hash
ParentHash ethCommon.Hash
// state ethState
}
// Block represents a ethereum block
type Block struct {
Rollup *RollupBlock
Auction *AuctionBlock
Eth *EthereumBlock
}
// type ethState struct {
// blockNum int64
// }
// type state struct {
// rollupState eth.RollupState
// rollupVars eth.RollupVariables
// auctionState eth.AuctionState
// auctionVars eth.AuctionVariables
// eth ethState
// }
// ClientSetup is used to initialize the constants of the Smart Contracts and
// other details of the test Client
type ClientSetup struct {
RollupConstants *eth.RollupConstants
RollupVariables *eth.RollupVariables
AuctionConstants *eth.AuctionConstants
AuctionVariables *eth.AuctionVariables
VerifyProof bool
}
// Timer is an interface to simulate a source of time, useful to advance time
// virtually.
type Timer interface {
Time() int64
}
// type forgeBatchArgs struct {
// ethTx *types.Transaction
// blockNum int64
// blockHash ethCommon.Hash
// }
// Client implements the eth.ClientInterface interface, allowing to manipulate the
// values for testing, working with deterministic results.
type Client struct {
log bool
blockNum *big.Int
log bool
rollupConstants *eth.RollupConstants
auctionConstants *eth.AuctionConstants
blocks map[int64]*Block
// state state
blockNum int64 // last mined block num
maxBlockNum int64 // highest block num calculated
timer Timer
hasher hasher
forgeBatchArgsPending map[ethCommon.Hash]*eth.RollupForgeBatchArgs
forgeBatchArgs map[ethCommon.Hash]*eth.RollupForgeBatchArgs
}
// NewClient returns a new test Client that implements the eth.IClient
// interface, at the given initialBlockNumber.
func NewClient(l bool, initialBlockNumber int64) *Client {
func NewClient(l bool, timer Timer, setup *ClientSetup) *Client {
blocks := make(map[int64]*Block)
blockNum := int64(0)
hasher := hasher{}
// Add ethereum genesis block
mapL1TxQueue := make(map[int64]*eth.QueueStruct)
mapL1TxQueue[0] = eth.NewQueueStruct()
mapL1TxQueue[1] = eth.NewQueueStruct()
blockCurrent := Block{
Rollup: &RollupBlock{
State: eth.RollupState{
StateRoot: big.NewInt(0),
ExitRoots: make([]*big.Int, 0),
ExitNullifierMap: make(map[[256 / 8]byte]bool),
TokenList: make([]ethCommon.Address, 0),
TokenMap: make(map[ethCommon.Address]bool),
MapL1TxQueue: mapL1TxQueue,
LastL1L2Batch: 0,
CurrentToForgeL1TxsNum: 0,
LastToForgeL1TxsNum: 1,
CurrentIdx: 0,
},
Vars: *setup.RollupVariables,
Events: eth.NewRollupEvents(),
},
Auction: &AuctionBlock{
State: eth.AuctionState{
Slots: make(map[int64]eth.SlotState),
PendingBalances: make(map[ethCommon.Address]*big.Int),
Coordinators: make(map[ethCommon.Address]eth.Coordinator),
},
Vars: *setup.AuctionVariables,
Events: eth.NewAuctionEvents(),
},
Eth: &EthereumBlock{
BlockNum: blockNum,
Time: timer.Time(),
Hash: hasher.Next(),
ParentHash: ethCommon.Hash{},
},
}
blocks[blockNum] = &blockCurrent
blockNextRaw, err := copystructure.Copy(&blockCurrent)
if err != nil {
panic(err)
}
blockNext := blockNextRaw.(*Block)
blocks[blockNum+1] = blockNext
return &Client{
log: l,
blockNum: big.NewInt(initialBlockNumber),
log: l,
rollupConstants: setup.RollupConstants,
auctionConstants: setup.AuctionConstants,
blocks: blocks,
timer: timer,
hasher: hasher,
forgeBatchArgsPending: make(map[ethCommon.Hash]*eth.RollupForgeBatchArgs),
forgeBatchArgs: make(map[ethCommon.Hash]*eth.RollupForgeBatchArgs),
}
}
// Advance moves one block forward
func (c *Client) Advance() {
c.blockNum = c.blockNum.Add(c.blockNum, big.NewInt(1))
//
// Mock Control
//
// Debugf calls log.Debugf if c.log is true
func (c *Client) Debugf(template string, args ...interface{}) {
if c.log {
log.Debugf("TestEthClient blockNum advanced: %d", c.blockNum)
log.Debugf(template, args...)
}
}
// SetBlockNum sets the Client.blockNum to the given blockNum
func (c *Client) SetBlockNum(blockNum *big.Int) {
c.blockNum = blockNum
// Debugw calls log.Debugw if c.log is true
func (c *Client) Debugw(template string, kv ...interface{}) {
if c.log {
log.Debugf("TestEthClient blockNum set to: %d", c.blockNum)
log.Debugw(template, kv...)
}
}
// EthCurrentBlock returns the current blockNum
func (c *Client) EthCurrentBlock() (*big.Int, error) {
return c.blockNum, nil
type hasher struct {
counter uint64
}
func newHeader(number *big.Int) *types.Header {
return &types.Header{
Number: number,
Time: uint64(number.Int64()),
// Next returns the next hash
func (h *hasher) Next() ethCommon.Hash {
var hash ethCommon.Hash
binary.LittleEndian.PutUint64(hash[:], h.counter)
h.counter++
return hash
}
func (c *Client) nextBlock() *Block {
return c.blocks[c.blockNum+1]
}
// CtlMineBlock moves one block forward
func (c *Client) CtlMineBlock() {
blockCurrent := c.nextBlock()
c.blockNum++
c.maxBlockNum = c.blockNum
blockCurrent.Eth = &EthereumBlock{
BlockNum: c.blockNum,
Time: c.timer.Time(),
Hash: c.hasher.Next(),
ParentHash: blockCurrent.Eth.Hash,
}
for ethTxHash, forgeBatchArgs := range c.forgeBatchArgsPending {
c.forgeBatchArgs[ethTxHash] = forgeBatchArgs
}
c.forgeBatchArgsPending = make(map[ethCommon.Hash]*eth.RollupForgeBatchArgs)
blockNextRaw, err := copystructure.Copy(blockCurrent)
if err != nil {
panic(err)
}
blockNext := blockNextRaw.(*Block)
blockNext.Rollup.Events = eth.NewRollupEvents()
blockNext.Auction.Events = eth.NewAuctionEvents()
c.blocks[c.blockNum+1] = blockNext
c.Debugw("TestClient mined block", "blockNum", c.blockNum)
}
// CtlRollback discards the last mined block. Use this to replace a mined
// block to simulate reorgs.
func (c *Client) CtlRollback() {
if c.blockNum == 0 {
panic("Can't rollback at blockNum = 0")
}
delete(c.blocks, c.blockNum+1) // delete next block
delete(c.blocks, c.blockNum) // delete current block
c.blockNum--
blockCurrent := c.blocks[c.blockNum]
blockNextRaw, err := copystructure.Copy(blockCurrent)
if err != nil {
panic(err)
}
blockNext := blockNextRaw.(*Block)
blockNext.Rollup.Events = eth.NewRollupEvents()
blockNext.Auction.Events = eth.NewAuctionEvents()
c.blocks[c.blockNum+1] = blockNext
}
//
// Ethereum
//
// EthCurrentBlock returns the current blockNum
func (c *Client) EthCurrentBlock() (int64, error) {
if c.blockNum < c.maxBlockNum {
panic("blockNum has decreased. " +
"After a rollback you must mine to reach the same or higher blockNum")
}
return c.blockNum, nil
}
// func newHeader(number *big.Int) *types.Header {
// return &types.Header{
// Number: number,
// Time: uint64(number.Int64()),
// }
// }
// EthHeaderByNumber returns the *types.Header for the given block number in a
// deterministic way.
func (c *Client) EthHeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
return newHeader(number), nil
}
// func (c *Client) EthHeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
// return newHeader(number), nil
// }
// EthBlockByNumber returns the *common.Block for the given block number in a
// deterministic way.
func (c *Client) EthBlockByNumber(ctx context.Context, number *big.Int) (*common.Block, error) {
header := newHeader(number)
func (c *Client) EthBlockByNumber(ctx context.Context, blockNum int64) (*common.Block, error) {
block, ok := c.blocks[blockNum]
if !ok {
return nil, fmt.Errorf("block not found")
}
return &common.Block{
EthBlockNum: uint64(number.Int64()),
Timestamp: time.Unix(number.Int64(), 0),
Hash: header.Hash(),
EthBlockNum: blockNum,
Timestamp: time.Unix(block.Eth.Time, 0),
Hash: block.Eth.Hash,
ParentHash: block.Eth.ParentHash,
}, nil
}
@ -83,14 +291,78 @@ var errTODO = fmt.Errorf("TODO: Not implemented yet")
// Rollup
//
// CtlAddL1TxUser adds an L1TxUser to the L1UserTxs queue of the Rollup
func (c *Client) CtlAddL1TxUser(l1Tx *common.L1Tx) {
nextBlock := c.nextBlock()
r := nextBlock.Rollup
queue := r.State.MapL1TxQueue[r.State.LastToForgeL1TxsNum]
if len(queue.L1TxQueue) >= c.rollupConstants.MaxL1UserTx {
r.State.LastToForgeL1TxsNum++
r.State.MapL1TxQueue[r.State.LastToForgeL1TxsNum] = eth.NewQueueStruct()
queue = r.State.MapL1TxQueue[r.State.LastToForgeL1TxsNum]
}
if int64(l1Tx.FromIdx) > r.State.CurrentIdx {
panic("l1Tx.FromIdx > r.State.CurrentIdx")
}
if int(l1Tx.TokenID)+1 > len(r.State.TokenList) {
panic("l1Tx.TokenID + 1 > len(r.State.TokenList)")
}
queue.L1TxQueue = append(queue.L1TxQueue, *l1Tx)
r.Events.L1UserTx = append(r.Events.L1UserTx, eth.RollupEventL1UserTx{L1Tx: *l1Tx})
}
func (c *Client) newTransaction(name string, value interface{}) *types.Transaction {
data, err := json.Marshal(value)
if err != nil {
panic(err)
}
return types.NewTransaction(0, ethCommon.Address{}, nil, 0, nil,
data)
}
// RollupForgeBatch is the interface to call the smart contract function
func (c *Client) RollupForgeBatch(*eth.RollupForgeBatchArgs) (*types.Transaction, error) {
return nil, errTODO
}
// CtlAddBatch adds forged batch to the Rollup, without checking any ZKProof
func (c *Client) CtlAddBatch(args *eth.RollupForgeBatchArgs) {
nextBlock := c.nextBlock()
r := nextBlock.Rollup
r.State.StateRoot = args.NewStRoot
if args.NewLastIdx < r.State.CurrentIdx {
panic("args.NewLastIdx < r.State.CurrentIdx")
}
r.State.CurrentIdx = args.NewLastIdx
r.State.ExitRoots = append(r.State.ExitRoots, args.NewExitRoot)
if args.L1Batch {
r.State.CurrentToForgeL1TxsNum++
if r.State.CurrentToForgeL1TxsNum == r.State.LastToForgeL1TxsNum {
r.State.LastToForgeL1TxsNum++
r.State.MapL1TxQueue[r.State.LastToForgeL1TxsNum] = eth.NewQueueStruct()
}
}
ethTx := c.newTransaction("forgebatch", args)
c.forgeBatchArgsPending[ethTx.Hash()] = args
r.Events.ForgeBatch = append(r.Events.ForgeBatch, eth.RollupEventForgeBatch{
BatchNum: int64(len(r.State.ExitRoots)),
EthTxHash: ethTx.Hash(),
})
}
// RollupAddToken is the interface to call the smart contract function
func (c *Client) RollupAddToken(tokenAddress ethCommon.Address) (*types.Transaction, error) {
return nil, errTODO
nextBlock := c.nextBlock()
r := nextBlock.Rollup
if _, ok := r.State.TokenMap[tokenAddress]; ok {
return nil, fmt.Errorf("Token %v already registered", tokenAddress)
}
r.State.TokenMap[tokenAddress] = true
r.State.TokenList = append(r.State.TokenList, tokenAddress)
r.Events.AddToken = append(r.Events.AddToken, eth.RollupEventAddToken{Address: tokenAddress,
TokenID: uint32(len(r.State.TokenList) - 1)})
return c.newTransaction("addtoken", tokenAddress), nil
}
// RollupWithdrawSNARK is the interface to call the smart contract function
@ -185,12 +457,20 @@ func (c *Client) RollupConstants() (*eth.RollupConstants, error) {
// RollupEventsByBlock returns the events in a block that happened in the Rollup Smart Contract
func (c *Client) RollupEventsByBlock(blockNum int64) (*eth.RollupEvents, *ethCommon.Hash, error) {
return nil, nil, errTODO
block, ok := c.blocks[blockNum]
if !ok {
return nil, nil, fmt.Errorf("Block %v doesn't exist", blockNum)
}
return &block.Rollup.Events, &block.Eth.Hash, nil
}
// RollupForgeBatchArgs returns the arguments used in a ForgeBatch call in the Rollup Smart Contract in the given transaction
func (c *Client) RollupForgeBatchArgs(transaction *types.Transaction) (*eth.RollupForgeBatchArgs, error) {
return nil, errTODO
func (c *Client) RollupForgeBatchArgs(ethTxHash ethCommon.Hash) (*eth.RollupForgeBatchArgs, error) {
forgeBatchArgs, ok := c.forgeBatchArgs[ethTxHash]
if !ok {
return nil, fmt.Errorf("transaction not found")
}
return forgeBatchArgs, nil
}
//

+ 199
- 25
test/ethclient_test.go

@ -2,45 +2,219 @@ package test
import (
"context"
"crypto/ecdsa"
"encoding/binary"
"math/big"
"testing"
"time"
ethCommon "github.com/ethereum/go-ethereum/common"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/eth"
"github.com/iden3/go-iden3-crypto/babyjub"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var clientSetup *ClientSetup
func init() {
rollupConstants := &eth.RollupConstants{}
rollupVariables := &eth.RollupVariables{
MaxTxVerifiers: make([]int, 0),
TokenHEZ: ethCommon.Address{},
GovernanceAddress: ethCommon.Address{},
SafetyBot: ethCommon.Address{},
ConsensusContract: ethCommon.Address{},
WithdrawalContract: ethCommon.Address{},
FeeAddToken: big.NewInt(1),
ForgeL1Timeout: 16,
FeeL1UserTx: big.NewInt(2),
}
auctionConstants := &eth.AuctionConstants{}
auctionVariables := &eth.AuctionVariables{
DonationAddress: ethCommon.Address{},
BootCoordinator: ethCommon.Address{},
MinBidEpoch: [6]*big.Int{
big.NewInt(10), big.NewInt(11), big.NewInt(12),
big.NewInt(13), big.NewInt(14), big.NewInt(15)},
ClosedAuctionSlots: 0,
OpenAuctionSlots: 0,
AllocationRatio: [3]uint8{},
Outbidding: 0,
SlotDeadline: 0,
}
clientSetup = &ClientSetup{
RollupConstants: rollupConstants,
RollupVariables: rollupVariables,
AuctionConstants: auctionConstants,
AuctionVariables: auctionVariables,
}
}
type timer struct {
time int64
}
func (t *timer) Time() int64 {
currentTime := t.time
t.time++
return currentTime
}
func TestClientInterface(t *testing.T) {
var c eth.ClientInterface
client := NewClient(true, 1000)
var timer timer
client := NewClient(true, &timer, clientSetup)
c = client
require.NotNil(t, c)
}
func TestEthClient(t *testing.T) {
c := NewClient(true, 1000)
block, err := c.EthBlockByNumber(context.TODO(), big.NewInt(3))
assert.Nil(t, err)
assert.Equal(t, uint64(3), block.EthBlockNum)
assert.Equal(t, time.Unix(3, 0), block.Timestamp)
assert.Equal(t, "0x6b0ab5a7a0ebf5f05cef3b49bc7a9739de06469a4e05557d802ee828fdf5187e", block.Hash.Hex())
header, err := c.EthHeaderByNumber(context.TODO(), big.NewInt(4))
assert.Nil(t, err)
assert.Equal(t, big.NewInt(4), header.Number)
assert.Equal(t, uint64(4), header.Time)
assert.Equal(t, "0x66cdb12322040a5a345ad29cea66ca97c14d6142b53987010947c8c008e26913", header.Hash().Hex())
assert.Equal(t, big.NewInt(1000), c.blockNum)
c.Advance()
assert.Equal(t, big.NewInt(1001), c.blockNum)
c.Advance()
assert.Equal(t, big.NewInt(1002), c.blockNum)
c.SetBlockNum(big.NewInt(5000))
assert.Equal(t, big.NewInt(5000), c.blockNum)
c.Advance()
assert.Equal(t, big.NewInt(5001), c.blockNum)
token1Addr := ethCommon.HexToAddress("0x6b175474e89094c44da98b954eedeac495271d0f")
var timer timer
c := NewClient(true, &timer, clientSetup)
blockNum, err := c.EthCurrentBlock()
require.Nil(t, err)
assert.Equal(t, int64(0), blockNum)
block, err := c.EthBlockByNumber(context.TODO(), 0)
require.Nil(t, err)
assert.Equal(t, int64(0), block.EthBlockNum)
assert.Equal(t, time.Unix(0, 0), block.Timestamp)
assert.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", block.Hash.Hex())
assert.Equal(t, int64(0), c.blockNum)
// Mine some empty blocks
c.CtlMineBlock()
assert.Equal(t, int64(1), c.blockNum)
c.CtlMineBlock()
assert.Equal(t, int64(2), c.blockNum)
block, err = c.EthBlockByNumber(context.TODO(), 2)
require.Nil(t, err)
assert.Equal(t, int64(2), block.EthBlockNum)
assert.Equal(t, time.Unix(2, 0), block.Timestamp)
// Add a token
tx, err := c.RollupAddToken(token1Addr)
require.Nil(t, err)
assert.NotNil(t, tx)
// Add some L1UserTxs
// Create Accounts
const N = 16
var keys [N]*keys
for i := 0; i < N; i++ {
keys[i] = genKeys(int64(i))
l1UserTx := common.L1Tx{
FromIdx: common.Idx(0),
FromEthAddr: keys[i].Addr,
FromBJJ: keys[i].BJJPublicKey,
TokenID: common.TokenID(0),
LoadAmount: big.NewInt(10 + int64(i)),
}
c.CtlAddL1TxUser(&l1UserTx)
}
c.CtlMineBlock()
blockNum, err = c.EthCurrentBlock()
require.Nil(t, err)
rollupEvents, _, err := c.RollupEventsByBlock(blockNum)
require.Nil(t, err)
assert.Equal(t, N, len(rollupEvents.L1UserTx))
assert.Equal(t, 1, len(rollupEvents.AddToken))
// Forge a batch
c.CtlAddBatch(&eth.RollupForgeBatchArgs{
NewLastIdx: 0,
NewStRoot: big.NewInt(1),
NewExitRoot: big.NewInt(100),
L1CoordinatorTxs: []*common.L1Tx{},
L2Txs: []*common.L2Tx{},
FeeIdxCoordinator: make([]common.Idx, eth.FeeIdxCoordinatorLen),
VerifierIdx: 0,
L1Batch: true,
})
c.CtlMineBlock()
blockNumA, err := c.EthCurrentBlock()
require.Nil(t, err)
rollupEvents, hashA, err := c.RollupEventsByBlock(blockNumA)
require.Nil(t, err)
assert.Equal(t, 0, len(rollupEvents.L1UserTx))
assert.Equal(t, 0, len(rollupEvents.AddToken))
assert.Equal(t, 1, len(rollupEvents.ForgeBatch))
// Simulate reorg discarding last mined block
c.CtlRollback()
c.CtlMineBlock()
blockNumB, err := c.EthCurrentBlock()
require.Nil(t, err)
rollupEvents, hashB, err := c.RollupEventsByBlock(blockNumA)
require.Nil(t, err)
assert.Equal(t, 0, len(rollupEvents.L1UserTx))
assert.Equal(t, 0, len(rollupEvents.AddToken))
assert.Equal(t, 0, len(rollupEvents.ForgeBatch))
assert.Equal(t, blockNumA, blockNumB)
assert.NotEqual(t, hashA, hashB)
// Forge again
rollupForgeBatchArgs0 := &eth.RollupForgeBatchArgs{
NewLastIdx: 0,
NewStRoot: big.NewInt(1),
NewExitRoot: big.NewInt(100),
L1CoordinatorTxs: []*common.L1Tx{},
L2Txs: []*common.L2Tx{},
FeeIdxCoordinator: make([]common.Idx, eth.FeeIdxCoordinatorLen),
VerifierIdx: 0,
L1Batch: true,
}
c.CtlAddBatch(rollupForgeBatchArgs0)
c.CtlMineBlock()
// Retrieve ForgeBatchArguments starting from the events
blockNum, err = c.EthCurrentBlock()
require.Nil(t, err)
rollupEvents, _, err = c.RollupEventsByBlock(blockNum)
require.Nil(t, err)
rollupForgeBatchArgs1, err := c.RollupForgeBatchArgs(rollupEvents.ForgeBatch[0].EthTxHash)
require.Nil(t, err)
assert.Equal(t, rollupForgeBatchArgs0, rollupForgeBatchArgs1)
}
type keys struct {
BJJSecretKey *babyjub.PrivateKey
BJJPublicKey *babyjub.PublicKey
Addr ethCommon.Address
}
func genKeys(i int64) *keys {
i++ // i = 0 doesn't work for the ecdsa key generation
var sk babyjub.PrivateKey
binary.LittleEndian.PutUint64(sk[:], uint64(i))
// eth address
var key ecdsa.PrivateKey
key.D = big.NewInt(i) // only for testing
key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
key.Curve = ethCrypto.S256()
addr := ethCrypto.PubkeyToAddress(key.PublicKey)
return &keys{
BJJSecretKey: &sk,
BJJPublicKey: sk.Public(),
Addr: addr,
}
}

Loading…
Cancel
Save