arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/test/transakcio/lang.go
test/transakcio/lang.goRaw
  1package transakcio
  2
  3import (
  4	"bufio"
  5	"bytes"
  6	"fmt"
  7	"io"
  8	"sort"
  9	"strconv"
 10
 11	"github.com/hermeznetwork/hermez-node/common"
 12	"github.com/hermeznetwork/hermez-node/log"
 13)
 14
 15var eof = rune(0)
 16var errof = fmt.Errorf("eof in parseline")
 17var commentLine = fmt.Errorf("comment in parseline") //nolint:golint
 18var newEventLine = fmt.Errorf("newEventLine")        //nolint:golint
 19var setTypeLine = fmt.Errorf("setTypeLine")          //nolint:golint
 20
 21// setType defines the type of the set
 22type setType string
 23
 24// setTypeBlockchain defines the type 'Blockchain' of the set
 25var setTypeBlockchain = setType("Blockchain")
 26
 27// setTypePoolL2 defines the type 'PoolL2' of the set
 28var setTypePoolL2 = setType("PoolL2")
 29
 30// typeNewBatch is used for testing purposes only, and represents the
 31// common.TxType of a new batch
 32var typeNewBatch common.TxType = "InstrTypeNewBatch"
 33
 34// typeNewBlock is used for testing purposes only, and represents the
 35// common.TxType of a new ethereum block
 36var typeNewBlock common.TxType = "InstrTypeNewBlock"
 37
 38// typeRegisterToken is used for testing purposes only, and represents the
 39// common.TxType of a new Token regsitration
 40// It has 'nolint:gosec' as the string 'Token' triggers gosec as a potential leaked Token (which is not the case)
 41var typeRegisterToken common.TxType = "InstrTypeRegisterToken" //nolint:gosec
 42
 43var txTypeCreateAccountDepositCoordinator common.TxType = "TypeCreateAccountDepositCoordinator"
 44
 45//nolint
 46const (
 47	ILLEGAL token = iota
 48	WS
 49	EOF
 50
 51	IDENT // val
 52)
 53
 54// instruction is the data structure that represents one line of code
 55type instruction struct {
 56	literal    string
 57	from       string
 58	to         string
 59	amount     uint64
 60	loadAmount uint64
 61	fee        uint8
 62	tokenID    common.TokenID
 63	typ        common.TxType // D: Deposit, T: Transfer, E: ForceExit
 64}
 65
 66// parsedSet contains the full Set of Instructions representing a full code
 67type parsedSet struct {
 68	// type         string
 69	instructions []instruction
 70	accounts     []string
 71}
 72
 73func (i instruction) String() string {
 74	buf := bytes.NewBufferString("")
 75	fmt.Fprintf(buf, "Type: %s, ", i.typ)
 76	fmt.Fprintf(buf, "From: %s, ", i.from)
 77	if i.typ == common.TxTypeTransfer ||
 78		i.typ == common.TxTypeDepositTransfer ||
 79		i.typ == common.TxTypeCreateAccountDepositTransfer {
 80		fmt.Fprintf(buf, "To: %s, ", i.to)
 81	}
 82
 83	if i.typ == common.TxTypeDeposit ||
 84		i.typ == common.TxTypeDepositTransfer ||
 85		i.typ == common.TxTypeCreateAccountDepositTransfer {
 86		fmt.Fprintf(buf, "LoadAmount: %d, ", i.loadAmount)
 87	}
 88	if i.typ != common.TxTypeDeposit {
 89		fmt.Fprintf(buf, "Amount: %d, ", i.amount)
 90	}
 91	if i.typ == common.TxTypeTransfer ||
 92		i.typ == common.TxTypeDepositTransfer ||
 93		i.typ == common.TxTypeCreateAccountDepositTransfer {
 94		fmt.Fprintf(buf, "Fee: %d, ", i.fee)
 95	}
 96	fmt.Fprintf(buf, "TokenID: %d\n", i.tokenID)
 97	return buf.String()
 98}
 99
100// Raw returns a string with the raw representation of the Instruction
101func (i instruction) raw() string {
102	buf := bytes.NewBufferString("")
103	fmt.Fprintf(buf, "%s", i.typ)
104	fmt.Fprintf(buf, "(%d)", i.tokenID)
105	fmt.Fprintf(buf, "%s", i.from)
106	if i.typ == common.TxTypeTransfer ||
107		i.typ == common.TxTypeDepositTransfer ||
108		i.typ == common.TxTypeCreateAccountDepositTransfer {
109		fmt.Fprintf(buf, "-%s", i.to)
110	}
111	fmt.Fprintf(buf, ":")
112	if i.typ == common.TxTypeDeposit ||
113		i.typ == common.TxTypeDepositTransfer ||
114		i.typ == common.TxTypeCreateAccountDepositTransfer {
115		fmt.Fprintf(buf, "%d", i.loadAmount)
116	}
117	if i.typ != common.TxTypeDeposit {
118		fmt.Fprintf(buf, "%d", i.amount)
119	}
120	if i.typ == common.TxTypeTransfer {
121		fmt.Fprintf(buf, "(%d)", i.fee)
122	}
123	return buf.String()
124}
125
126type token int
127
128type scanner struct {
129	r *bufio.Reader
130}
131
132func isWhitespace(ch rune) bool {
133	return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'
134}
135
136func isLetter(ch rune) bool {
137	return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
138}
139
140func isComment(ch rune) bool {
141	return ch == '/'
142}
143
144func isDigit(ch rune) bool {
145	return (ch >= '0' && ch <= '9')
146}
147
148// newScanner creates a new scanner with the given io.Reader
149func newScanner(r io.Reader) *scanner {
150	return &scanner{r: bufio.NewReader(r)}
151}
152
153func (s *scanner) read() rune {
154	ch, _, err := s.r.ReadRune()
155	if err != nil {
156		return eof
157	}
158	return ch
159}
160
161func (s *scanner) unread() {
162	_ = s.r.UnreadRune()
163}
164
165// scan returns the token and literal string of the current value
166func (s *scanner) scan() (tok token, lit string) {
167	ch := s.read()
168
169	if isWhitespace(ch) {
170		// space
171		s.unread()
172		return s.scanWhitespace()
173	} else if isLetter(ch) || isDigit(ch) {
174		// letter/digit
175		s.unread()
176		return s.scanIndent()
177	} else if isComment(ch) {
178		// comment
179		s.unread()
180		return s.scanIndent()
181	}
182
183	if ch == eof {
184		return EOF, ""
185	}
186
187	return ILLEGAL, string(ch)
188}
189
190func (s *scanner) scanWhitespace() (token token, lit string) {
191	var buf bytes.Buffer
192	buf.WriteRune(s.read())
193
194	for {
195		if ch := s.read(); ch == eof {
196			break
197		} else if !isWhitespace(ch) {
198			s.unread()
199			break
200		} else {
201			_, _ = buf.WriteRune(ch)
202		}
203	}
204	return WS, buf.String()
205}
206
207func (s *scanner) scanIndent() (tok token, lit string) {
208	var buf bytes.Buffer
209	buf.WriteRune(s.read())
210
211	for {
212		if ch := s.read(); ch == eof {
213			break
214		} else if !isLetter(ch) && !isDigit(ch) {
215			s.unread()
216			break
217		} else {
218			_, _ = buf.WriteRune(ch)
219		}
220	}
221
222	if len(buf.String()) == 1 {
223		return token(rune(buf.String()[0])), buf.String()
224	}
225	return IDENT, buf.String()
226}
227
228// parser defines the parser
229type parser struct {
230	s   *scanner
231	buf struct {
232		tok token
233		lit string
234		n   int
235	}
236}
237
238// newParser creates a new parser from a io.Reader
239func newParser(r io.Reader) *parser {
240	return &parser{s: newScanner(r)}
241}
242
243func (p *parser) scan() (tok token, lit string) {
244	// if there is a token in the buffer return it
245	if p.buf.n != 0 {
246		p.buf.n = 0
247		return p.buf.tok, p.buf.lit
248	}
249	tok, lit = p.s.scan()
250
251	p.buf.tok, p.buf.lit = tok, lit
252
253	return
254}
255
256func (p *parser) scanIgnoreWhitespace() (tok token, lit string) {
257	tok, lit = p.scan()
258	if tok == WS {
259		tok, lit = p.scan()
260	}
261	return
262}
263
264// parseLine parses the current line
265func (p *parser) parseLine(setType setType) (*instruction, error) {
266	c := &instruction{}
267	tok, lit := p.scanIgnoreWhitespace()
268	if tok == EOF {
269		return nil, errof
270	}
271	c.literal += lit
272	if lit == "/" {
273		_, _ = p.s.r.ReadString('\n')
274		return nil, commentLine
275	} else if lit == ">" {
276		if setType == setTypePoolL2 {
277			return c, fmt.Errorf("Unexpected '>' at PoolL2Txs set")
278		}
279		_, lit = p.scanIgnoreWhitespace()
280		if lit == "batch" {
281			_, _ = p.s.r.ReadString('\n')
282			return &instruction{typ: typeNewBatch}, newEventLine
283		} else if lit == "block" {
284			_, _ = p.s.r.ReadString('\n')
285			return &instruction{typ: typeNewBlock}, newEventLine
286		} else {
287			return c, fmt.Errorf("Unexpected '> %s', expected '> batch' or '> block'", lit)
288		}
289	} else if lit == "Type" {
290		if err := p.expectChar(c, ":"); err != nil {
291			return c, err
292		}
293		_, lit = p.scanIgnoreWhitespace()
294		if lit == "Blockchain" {
295			return &instruction{typ: "Blockchain"}, setTypeLine
296		} else if lit == "PoolL2" {
297			return &instruction{typ: "PoolL2"}, setTypeLine
298		} else {
299			return c, fmt.Errorf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", lit)
300		}
301	} else if lit == "RegisterToken" {
302		if err := p.expectChar(c, "("); err != nil {
303			return c, err
304		}
305		_, lit = p.scanIgnoreWhitespace()
306		c.literal += lit
307		tidI, err := strconv.Atoi(lit)
308		if err != nil {
309			line, _ := p.s.r.ReadString('\n')
310			c.literal += line
311			return c, err
312		}
313		c.tokenID = common.TokenID(tidI)
314		if err := p.expectChar(c, ")"); err != nil {
315			return c, err
316		}
317		c.typ = typeRegisterToken
318		line, _ := p.s.r.ReadString('\n')
319		c.literal += line
320		return c, newEventLine
321	}
322
323	if setType == "" {
324		return c, fmt.Errorf("Set type not defined")
325	}
326	transferring := false
327
328	if setType == setTypeBlockchain {
329		switch lit {
330		case "Deposit":
331			c.typ = common.TxTypeDeposit
332		case "Exit":
333			c.typ = common.TxTypeExit
334		case "Transfer":
335			c.typ = common.TxTypeTransfer
336			transferring = true
337		case "CreateAccountDeposit":
338			c.typ = common.TxTypeCreateAccountDeposit
339		case "CreateAccountDepositTransfer":
340			c.typ = common.TxTypeCreateAccountDepositTransfer
341			transferring = true
342		case "CreateAccountDepositCoordinator":
343			c.typ = txTypeCreateAccountDepositCoordinator
344			// transferring is false, as the Coordinator tx transfer will be 0
345		case "DepositTransfer":
346			c.typ = common.TxTypeDepositTransfer
347			transferring = true
348		case "ForceTransfer":
349			c.typ = common.TxTypeForceTransfer
350		case "ForceExit":
351			c.typ = common.TxTypeForceExit
352		default:
353			return c, fmt.Errorf("Unexpected Blockchain tx type: %s", lit)
354		}
355	} else if setType == setTypePoolL2 {
356		switch lit {
357		case "PoolTransfer":
358			c.typ = common.TxTypeTransfer
359			transferring = true
360		case "PoolExit":
361			c.typ = common.TxTypeExit
362		default:
363			return c, fmt.Errorf("Unexpected PoolL2 tx type: %s", lit)
364		}
365	} else {
366		return c, fmt.Errorf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", setType)
367	}
368
369	if err := p.expectChar(c, "("); err != nil {
370		return c, err
371	}
372	_, lit = p.scanIgnoreWhitespace()
373	c.literal += lit
374	tidI, err := strconv.Atoi(lit)
375	if err != nil {
376		line, _ := p.s.r.ReadString('\n')
377		c.literal += line
378		return c, err
379	}
380	c.tokenID = common.TokenID(tidI)
381	if err := p.expectChar(c, ")"); err != nil {
382		return c, err
383	}
384	_, lit = p.scanIgnoreWhitespace()
385	c.literal += lit
386	c.from = lit
387	if c.typ == txTypeCreateAccountDepositCoordinator {
388		line, _ := p.s.r.ReadString('\n')
389		c.literal += line
390		return c, nil
391	}
392	_, lit = p.scanIgnoreWhitespace()
393	c.literal += lit
394	if transferring {
395		if lit != "-" {
396			return c, fmt.Errorf("Expected '-', found '%s'", lit)
397		}
398		_, lit = p.scanIgnoreWhitespace()
399		c.literal += lit
400		c.to = lit
401		_, lit = p.scanIgnoreWhitespace()
402		c.literal += lit
403	}
404	if lit != ":" {
405		line, _ := p.s.r.ReadString('\n')
406		c.literal += line
407		return c, fmt.Errorf("Expected ':', found '%s'", lit)
408	}
409	if c.typ == common.TxTypeDepositTransfer ||
410		c.typ == common.TxTypeCreateAccountDepositTransfer {
411		// deposit case
412		_, lit = p.scanIgnoreWhitespace()
413		c.literal += lit
414		loadAmount, err := strconv.Atoi(lit)
415		if err != nil {
416			line, _ := p.s.r.ReadString('\n')
417			c.literal += line
418			return c, err
419		}
420		c.loadAmount = uint64(loadAmount)
421		if err := p.expectChar(c, ","); err != nil {
422			return c, err
423		}
424	}
425	_, lit = p.scanIgnoreWhitespace()
426	c.literal += lit
427	amount, err := strconv.Atoi(lit)
428	if err != nil {
429		line, _ := p.s.r.ReadString('\n')
430		c.literal += line
431		return c, err
432	}
433	if c.typ == common.TxTypeDeposit ||
434		c.typ == common.TxTypeCreateAccountDeposit {
435		c.loadAmount = uint64(amount)
436	} else {
437		c.amount = uint64(amount)
438	}
439	if transferring {
440		if err := p.expectChar(c, "("); err != nil {
441			return c, err
442		}
443		_, lit = p.scanIgnoreWhitespace()
444		c.literal += lit
445		fee, err := strconv.Atoi(lit)
446		if err != nil {
447			line, _ := p.s.r.ReadString('\n')
448			c.literal += line
449			return c, err
450		}
451		if fee > common.MaxFeePlan-1 {
452			line, _ := p.s.r.ReadString('\n')
453			c.literal += line
454			return c, fmt.Errorf("Fee %d can not be bigger than 255", fee)
455		}
456		c.fee = uint8(fee)
457
458		if err := p.expectChar(c, ")"); err != nil {
459			return c, err
460		}
461	}
462
463	if tok == EOF {
464		return nil, errof
465	}
466	return c, nil
467}
468
469func (p *parser) expectChar(c *instruction, ch string) error {
470	_, lit := p.scanIgnoreWhitespace()
471	c.literal += lit
472	if lit != ch {
473		line, _ := p.s.r.ReadString('\n')
474		c.literal += line
475		return fmt.Errorf("Expected '%s', found '%s'", ch, lit)
476	}
477	return nil
478}
479
480func idxTokenIDToString(idx string, tid common.TokenID) string {
481	return idx + strconv.Itoa(int(tid))
482}
483
484// parse parses through reader
485func (p *parser) parse() (*parsedSet, error) {
486	ps := &parsedSet{}
487	i := 0
488	accounts := make(map[string]bool)
489	var setTypeOfSet setType
490	for {
491		instruction, err := p.parseLine(setTypeOfSet)
492		if err == errof {
493			break
494		}
495		if err == setTypeLine {
496			if setTypeOfSet != "" {
497				return ps, fmt.Errorf("Instruction of 'Type: %s' when there is already a previous instruction 'Type: %s' defined", instruction.typ, setTypeOfSet)
498			}
499			if instruction.typ == "PoolL2" {
500				setTypeOfSet = setTypePoolL2
501			} else if instruction.typ == "Blockchain" {
502				setTypeOfSet = setTypeBlockchain
503			} else {
504				log.Fatalf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", instruction.typ)
505			}
506			i++
507			continue
508		}
509		if err == commentLine {
510			i++
511			continue
512		}
513		if err == newEventLine {
514			if instruction.typ == typeRegisterToken && instruction.tokenID == common.TokenID(0) {
515				return ps, fmt.Errorf("RegisterToken can not register TokenID 0")
516			}
517			i++
518			ps.instructions = append(ps.instructions, *instruction)
519			continue
520		}
521		if err != nil {
522			return ps, fmt.Errorf("error parsing line %d: %s, err: %s", i, instruction.literal, err.Error())
523		}
524		if setTypeOfSet == "" {
525			return ps, fmt.Errorf("Set type not defined")
526		}
527		ps.instructions = append(ps.instructions, *instruction)
528		accounts[instruction.from] = true
529		if instruction.typ == common.TxTypeTransfer { // type: Transfer
530			accounts[instruction.to] = true
531		}
532		i++
533	}
534	for a := range accounts {
535		ps.accounts = append(ps.accounts, a)
536	}
537	sort.Strings(ps.accounts)
538	return ps, nil
539}