Implement initial version of test.Client

This commit is contained in:
Eduard S
2020-09-07 18:09:52 +02:00
parent 56604ecc69
commit bcd93fee36
9 changed files with 617 additions and 102 deletions

View File

@@ -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...)
}
}
type hasher struct {
counter uint64
}
// 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() (*big.Int, error) {
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()),
}
}
// 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
}
//