diff --git a/common/tx.go b/common/tx.go index 744849b..368e0e6 100644 --- a/common/tx.go +++ b/common/tx.go @@ -45,10 +45,10 @@ const ( TxTypeDeposit TxType = "Deposit" // TxTypeCreateAccountDeposit represents creation of a new leaf in the state tree (newAcconut) + L1->L2 transfer TxTypeCreateAccountDeposit TxType = "CreateAccountDeposit" - // TxTypeCreateAccountDepositAndTransfer represents L1->L2 transfer + L2->L2 transfer - TxTypeCreateAccountDepositAndTransfer TxType = "CreateAccountDepositAndTransfer" - // TxTypeDepositAndTransfer TBD - TxTypeDepositAndTransfer TxType = "TxTypeDepositAndTransfer" + // TxTypeCreateAccountDepositTransfer represents L1->L2 transfer + L2->L2 transfer + TxTypeCreateAccountDepositTransfer TxType = "CreateAccountDepositTransfer" + // TxTypeDepositTransfer TBD + TxTypeDepositTransfer TxType = "TxTypeDepositTransfer" // TxTypeForceTransfer TBD TxTypeForceTransfer TxType = "TxTypeForceTransfer" // TxTypeForceExit TBD @@ -63,7 +63,7 @@ const ( type Tx struct { TxID TxID FromIdx Idx // FromIdx is used by L1Tx/Deposit to indicate the Idx receiver of the L1Tx.LoadAmount (deposit) - ToIdx Idx // ToIdx is ignored in L1Tx/Deposit, but used in the L1Tx/DepositAndTransfer + ToIdx Idx // ToIdx is ignored in L1Tx/Deposit, but used in the L1Tx/DepositTransfer TokenID TokenID Amount *big.Int Nonce uint64 // effective 48 bits used diff --git a/coordinator/coordinator.go b/coordinator/coordinator.go index 2f361d0..5da9d02 100644 --- a/coordinator/coordinator.go +++ b/coordinator/coordinator.go @@ -133,7 +133,7 @@ func (c *Coordinator) forgeSequence() error { batchInfo.SetTxsInfo(l1UserTxsExtra, l1OperatorTxs, l2Txs) // TODO feesInfo // 4. Call BatchBuilder with TxSelector output - configBatch := batchbuilder.ConfigBatch{ + configBatch := &batchbuilder.ConfigBatch{ ForgerAddress: c.config.ForgerAddress, } zkInputs, err := c.batchBuilder.BuildBatch(configBatch, l1UserTxsExtra, l1OperatorTxs, l2Txs, nil) // TODO []common.TokenID --> feesInfo diff --git a/test/lang.go b/test/lang.go index 444c793..51f4348 100644 --- a/test/lang.go +++ b/test/lang.go @@ -13,6 +13,7 @@ import ( var eof = rune(0) var errof = fmt.Errorf("eof in parseline") +var ecomment = fmt.Errorf("comment in parseline") const ( ILLEGAL Token = iota @@ -27,8 +28,9 @@ type Instruction struct { From string To string Amount uint64 + Fee uint8 TokenID common.TokenID - Type int // 0: Deposit, 1: Transfer + Type common.TxType // D: Deposit, T: Transfer, E: ForceExit } type Instructions struct { @@ -40,17 +42,22 @@ type Instructions struct { func (i Instruction) String() string { buf := bytes.NewBufferString("") switch i.Type { - case 0: - fmt.Fprintf(buf, "Type: Deposit, ") - case 1: + case common.TxTypeCreateAccountDeposit: + fmt.Fprintf(buf, "Type: Create&Deposit, ") + case common.TxTypeTransfer: fmt.Fprintf(buf, "Type: Transfer, ") + case common.TxTypeForceExit: + fmt.Fprintf(buf, "Type: ForceExit, ") default: } fmt.Fprintf(buf, "From: %s, ", i.From) - if i.Type == 1 { + if i.Type == common.TxTypeTransfer { fmt.Fprintf(buf, "To: %s, ", i.To) } fmt.Fprintf(buf, "Amount: %d, ", i.Amount) + if i.Type == common.TxTypeTransfer { + fmt.Fprintf(buf, "Fee: %d, ", i.Fee) + } fmt.Fprintf(buf, "TokenID: %d,\n", i.TokenID) return buf.String() } @@ -58,11 +65,18 @@ func (i Instruction) String() string { func (i Instruction) Raw() string { buf := bytes.NewBufferString("") fmt.Fprintf(buf, "%s", i.From) - if i.Type == 1 { + if i.Type == common.TxTypeTransfer { fmt.Fprintf(buf, "-%s", i.To) } - fmt.Fprintf(buf, " (%d):", i.TokenID) + fmt.Fprintf(buf, " (%d)", i.TokenID) + if i.Type == common.TxTypeForceExit { + fmt.Fprintf(buf, "E") + } + fmt.Fprintf(buf, ":") fmt.Fprintf(buf, " %d", i.Amount) + if i.Type == common.TxTypeTransfer { + fmt.Fprintf(buf, " %d", i.Fee) + } return buf.String() } @@ -80,6 +94,10 @@ func isLetter(ch rune) bool { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') } +func isComment(ch rune) bool { + return ch == '/' +} + func isDigit(ch rune) bool { return (ch >= '0' && ch <= '9') } @@ -113,6 +131,10 @@ func (s *Scanner) scan() (tok Token, lit string) { // letter/digit s.unread() return s.scanIndent() + } else if isComment(ch) { + // comment + s.unread() + return s.scanIndent() } if ch == eof { @@ -198,18 +220,22 @@ func (p *Parser) scanIgnoreWhitespace() (tok Token, lit string) { // parseLine parses the current line func (p *Parser) parseLine() (*Instruction, error) { - /* - line can be Deposit: - A (1): 10 - or Transfer: - A-B (1): 6 - */ + // line can be Deposit: + // A (1): 10 + // or Transfer: + // A-B (1): 6 + // or Withdraw: + // A (1) E: 4 c := &Instruction{} tok, lit := p.scanIgnoreWhitespace() if tok == EOF { return nil, errof } c.Literal += lit + if lit == "/" { + _, _ = p.s.r.ReadString('\n') + return nil, ecomment + } c.From = lit _, lit = p.scanIgnoreWhitespace() @@ -219,7 +245,7 @@ func (p *Parser) parseLine() (*Instruction, error) { _, lit = p.scanIgnoreWhitespace() c.Literal += lit c.To = lit - c.Type = 1 + c.Type = common.TxTypeTransfer _, lit = p.scanIgnoreWhitespace() // expect ( c.Literal += lit if lit != "(" { @@ -228,7 +254,7 @@ func (p *Parser) parseLine() (*Instruction, error) { return c, fmt.Errorf("Expected '(', found '%s'", lit) } } else { - c.Type = 0 + c.Type = common.TxTypeCreateAccountDeposit } _, lit = p.scanIgnoreWhitespace() @@ -248,8 +274,13 @@ func (p *Parser) parseLine() (*Instruction, error) { return c, fmt.Errorf("Expected ')', found '%s'", lit) } - _, lit = p.scanIgnoreWhitespace() // expect : + _, lit = p.scanIgnoreWhitespace() // expect ':' or 'E' (Exit type) c.Literal += lit + if lit == "E" { + c.Type = common.TxTypeForceExit + _, lit = p.scanIgnoreWhitespace() // expect ':' + c.Literal += lit + } if lit != ":" { line, _ := p.s.r.ReadString('\n') c.Literal += line @@ -265,6 +296,23 @@ func (p *Parser) parseLine() (*Instruction, error) { } c.Amount = uint64(amount) + if c.Type == common.TxTypeTransfer { + tok, lit = p.scanIgnoreWhitespace() + c.Literal += lit + fee, err := strconv.Atoi(lit) + if err != nil { + line, _ := p.s.r.ReadString('\n') + c.Literal += line + return c, err + } + if fee > 255 { + line, _ := p.s.r.ReadString('\n') + c.Literal += line + return c, fmt.Errorf("Fee %d can not be bigger than 255", fee) + } + c.Fee = uint8(fee) + } + if tok == EOF { return nil, errof } @@ -282,12 +330,16 @@ func (p *Parser) Parse() (Instructions, error) { if err == errof { break } + if err == ecomment { + i++ + continue + } if err != nil { return instructions, fmt.Errorf("error parsing line %d: %s, err: %s", i, instruction.Literal, err.Error()) } instructions.Instructions = append(instructions.Instructions, instruction) accounts[instruction.From] = true - if instruction.Type == 1 { // type: Transfer + if instruction.Type == common.TxTypeTransfer { // type: Transfer accounts[instruction.To] = true } tokenids[instruction.TokenID] = true diff --git a/test/lang_test.go b/test/lang_test.go index 56528ca..deb4522 100644 --- a/test/lang_test.go +++ b/test/lang_test.go @@ -12,22 +12,29 @@ var debug = false func TestParse(t *testing.T) { s := ` + // deposits A (1): 10 A (2): 20 B (1): 5 - A-B (1): 6 - B-C (1): 3 - C-A (1): 3 - A-B (2): 15 + + // L2 transactions + A-B (1): 6 1 + B-C (1): 3 1 + C-A (1): 3 1 + A-B (2): 15 1 + User0 (1): 20 User1 (3) : 20 - User0-User1 (1): 15 - User1-User0 (3): 15 + User0-User1 (1): 15 1 + User1-User0 (3): 15 1 + + // Exits + A (1) E: 5 ` parser := NewParser(strings.NewReader(s)) instructions, err := parser.Parse() assert.Nil(t, err) - assert.Equal(t, 11, len(instructions.Instructions)) + assert.Equal(t, 12, len(instructions.Instructions)) assert.Equal(t, 5, len(instructions.Accounts)) assert.Equal(t, 3, len(instructions.TokenIDs)) @@ -39,9 +46,11 @@ func TestParse(t *testing.T) { } assert.Equal(t, "User0 (1): 20", instructions.Instructions[7].Raw()) - assert.Equal(t, "Type: Deposit, From: User0, Amount: 20, TokenID: 1,\n", instructions.Instructions[7].String()) - assert.Equal(t, "User0-User1 (1): 15", instructions.Instructions[9].Raw()) - assert.Equal(t, "Type: Transfer, From: User0, To: User1, Amount: 15, TokenID: 1,\n", instructions.Instructions[9].String()) + assert.Equal(t, "Type: Create&Deposit, From: User0, Amount: 20, TokenID: 1,\n", instructions.Instructions[7].String()) + assert.Equal(t, "User0-User1 (1): 15 1", instructions.Instructions[9].Raw()) + assert.Equal(t, "Type: Transfer, From: User0, To: User1, Amount: 15, Fee: 1, TokenID: 1,\n", instructions.Instructions[9].String()) + assert.Equal(t, "A (1)E: 5", instructions.Instructions[11].Raw()) + assert.Equal(t, "Type: ForceExit, From: A, Amount: 5, TokenID: 1,\n", instructions.Instructions[11].String()) } func TestParseErrors(t *testing.T) { @@ -59,4 +68,13 @@ func TestParseErrors(t *testing.T) { parser = NewParser(strings.NewReader(s)) _, err = parser.Parse() assert.Equal(t, "error parsing line 0: AB(1): 10, err: strconv.Atoi: parsing \"(\": invalid syntax", err.Error()) + + s = "A-B (1): 10 255" + parser = NewParser(strings.NewReader(s)) + _, err = parser.Parse() + assert.Nil(t, err) + s = "A-B (1): 10 256" + parser = NewParser(strings.NewReader(s)) + _, err = parser.Parse() + assert.Equal(t, "error parsing line 0: A-B(1):10256, err: Fee 256 can not be bigger than 255", err.Error()) } diff --git a/test/txs.go b/test/txs.go new file mode 100644 index 0000000..17d151c --- /dev/null +++ b/test/txs.go @@ -0,0 +1,111 @@ +package test + +import ( + "crypto/ecdsa" + "math/big" + "strconv" + "testing" + "time" + + ethCommon "github.com/ethereum/go-ethereum/common" + ethCrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/hermeznetwork/hermez-node/common" + "github.com/iden3/go-iden3-crypto/babyjub" +) + +type Account struct { + BJJ *babyjub.PrivateKey + Addr ethCommon.Address + Idx common.Idx + Nonce uint64 +} + +// GenerateKeys generates BabyJubJub & Address keys for the given list of +// account names in a deterministic way. This means, that for the same given +// 'accNames' the keys will be always the same. +func GenerateKeys(t *testing.T, accNames []string) map[string]*Account { + acc := make(map[string]*Account) + for i := 1; i < len(accNames)+1; i++ { + // babyjubjub key + var sk babyjub.PrivateKey + copy(sk[:], []byte(strconv.Itoa(i))) // only for testing + + // eth address + var key ecdsa.PrivateKey + key.D = big.NewInt(int64(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) + + a := Account{ + BJJ: &sk, + Addr: addr, + Nonce: 0, + } + acc[accNames[i-1]] = &a + } + return acc +} + +// GenerateTestTxs generates L1Tx & PoolL2Tx in a deterministic way for the +// given Instructions. +func GenerateTestTxs(t *testing.T, instructions Instructions) ([]*common.L1Tx, []*common.PoolL2Tx) { + accounts := GenerateKeys(t, instructions.Accounts) + + // debug + // fmt.Println("accounts:") + // for n, a := range accounts { + // fmt.Printf(" %s: bjj:%s - addr:%s\n", n, a.BJJ.Public().String()[:10], a.Addr.Hex()[:10]) + // } + + var l1txs []*common.L1Tx + var l2txs []*common.PoolL2Tx + idx := 1 + for _, inst := range instructions.Instructions { + switch inst.Type { + case common.TxTypeCreateAccountDeposit: + tx := common.L1Tx{ + // TxID + FromEthAddr: accounts[inst.From].Addr, + FromBJJ: accounts[inst.From].BJJ.Public(), + TokenID: inst.TokenID, + LoadAmount: big.NewInt(int64(inst.Amount)), + Type: common.TxTypeCreateAccountDeposit, + } + l1txs = append(l1txs, &tx) + if accounts[inst.From].Idx == common.Idx(0) { // if account.Idx is not set yet, set it and increment idx + accounts[inst.From].Idx = common.Idx(idx) + idx++ + } + case common.TxTypeTransfer: + tx := common.PoolL2Tx{ + // TxID: nil, + FromIdx: accounts[inst.From].Idx, + ToIdx: accounts[inst.To].Idx, + ToEthAddr: accounts[inst.To].Addr, + ToBJJ: accounts[inst.To].BJJ.Public(), + TokenID: inst.TokenID, + Amount: big.NewInt(int64(inst.Amount)), + Fee: common.FeeSelector(inst.Fee), + Nonce: accounts[inst.From].Nonce, + State: common.PoolL2TxStatePending, + Timestamp: time.Now(), + BatchNum: 0, + Type: common.TxTypeTransfer, + } + // if inst.Fee == 0 { + // tx.Fee = common.FeeSelector(i % 255) + // } + // TODO once signature function is ready, perform + // signature and set it to tx.Signature + + accounts[inst.From].Nonce++ + l2txs = append(l2txs, &tx) + default: + continue + } + + } + + return l1txs, l2txs +} diff --git a/test/txs_test.go b/test/txs_test.go new file mode 100644 index 0000000..9a3d2b0 --- /dev/null +++ b/test/txs_test.go @@ -0,0 +1,50 @@ +package test + +import ( + "strings" + "testing" + + "github.com/hermeznetwork/hermez-node/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenerateTestL2Txs(t *testing.T) { + s := ` + A (1): 10 + A (2): 20 + B (1): 5 + A-B (1): 6 1 + B-C (1): 3 1 + C-A (1): 3 1 + A-B (2): 15 1 + User0 (1): 20 + User1 (3) : 20 + User0-User1 (1): 15 1 + User1-User0 (3): 15 1 + ` + parser := NewParser(strings.NewReader(s)) + instructions, err := parser.Parse() + assert.Nil(t, err) + + l1txs, l2txs := GenerateTestTxs(t, instructions) + require.Equal(t, 5, len(l1txs)) + require.Equal(t, 6, len(l2txs)) + + // l1txs + assert.Equal(t, common.TxTypeCreateAccountDeposit, l1txs[0].Type) + assert.Equal(t, "5bac784d938067d980a9d39bdd79bf84a0cbb296977c47cc30de2d5ce9229d2f", l1txs[0].FromBJJ.String()) + assert.Equal(t, "5bac784d938067d980a9d39bdd79bf84a0cbb296977c47cc30de2d5ce9229d2f", l1txs[1].FromBJJ.String()) + assert.Equal(t, "323ff10c28df37ecb787fe216e111db64aa7cfa2c517509fe0057ff08a10b30c", l1txs[2].FromBJJ.String()) + assert.Equal(t, "a25c7150609ecfcf90fc3f419474e8bc28ea5978df1b0a68339bff884c117e19", l1txs[4].FromBJJ.String()) + + // l2txs + assert.Equal(t, common.TxTypeTransfer, l2txs[0].Type) + assert.Equal(t, common.Idx(1), l2txs[0].FromIdx) + assert.Equal(t, common.Idx(2), l2txs[0].ToIdx) + assert.Equal(t, "323ff10c28df37ecb787fe216e111db64aa7cfa2c517509fe0057ff08a10b30c", l2txs[0].ToBJJ.String()) + assert.Equal(t, "0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF", l2txs[0].ToEthAddr.Hex()) + assert.Equal(t, uint64(0), l2txs[0].Nonce) + assert.Equal(t, uint64(1), l2txs[3].Nonce) + assert.Equal(t, common.FeeSelector(1), l2txs[0].Fee) +} diff --git a/txselector/txselector_test.go b/txselector/txselector_test.go index 03ac53f..dffdaf3 100644 --- a/txselector/txselector_test.go +++ b/txselector/txselector_test.go @@ -12,95 +12,6 @@ import ( "github.com/stretchr/testify/require" ) -/* -func initTestDB(l2 *l2db.L2DB, sdb *statedb.StateDB) *mock.MockDB { - txs := []common.Tx{ - { - FromIdx: common.Idx(0), - ToIdx: common.Idx(1), - TokenID: 1, - Nonce: 1, - AbsoluteFee: 1, - }, - { - FromIdx: common.Idx(0), - ToIdx: common.Idx(1), - TokenID: 1, - Nonce: 2, - AbsoluteFee: 3, - }, - { - FromIdx: common.Idx(0), - ToIdx: common.Idx(1), - TokenID: 1, - Nonce: 4, - AbsoluteFee: 6, - }, - { - FromIdx: common.Idx(0), - ToIdx: common.Idx(1), - TokenID: 1, - Nonce: 4, - AbsoluteFee: 4, - }, - { - ToIdx: common.Idx(1), - FromIdx: common.Idx(0), - TokenID: 1, - Nonce: 1, - AbsoluteFee: 4, - }, - { - ToIdx: common.Idx(1), - FromIdx: common.Idx(0), - TokenID: 1, - Nonce: 2, - AbsoluteFee: 3, - }, - { - ToIdx: common.Idx(1), - FromIdx: common.Idx(0), - TokenID: 1, - Nonce: 3, - AbsoluteFee: 5, - }, - { - // this tx will not be selected, as the ToEthAddr does not have an account - FromIdx: common.Idx(1), - ToIdx: common.Idx(2), - TokenID: 1, - Nonce: 4, - AbsoluteFee: 5, - }, - } - - // n := 0 - nBatch := 0 - for i := 0; i < len(txs); i++ { - // for i := 0; i < nBatch; i++ { - // for j := 0; j < len(txs)/nBatch; j++ { - // store tx - l2db.AddTx(uint64(nBatch), txs[i]) - - // store account if not yet - accountID := getAccountID(txs[i].FromEthAddr, txs[i].TokenID) - if _, ok := m.AccountDB[accountID]; !ok { - account := common.Account{ - // EthAddr: txs[i].FromEthAddr, - TokenID: txs[i].TokenID, - Nonce: 0, - Balance: big.NewInt(0), - } - m.AccountDB[accountID] = account - } - // n++ - // } - } - - return m -} -*/ - func TestGetL2TxSelection(t *testing.T) { dir, err := ioutil.TempDir("", "tmpdb") require.Nil(t, err)