|
|
@ -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 |
|
|
|
} |
|
|
|
|
|
|
|
//
|
|
|
|