Browse Source

Merge pull request #69 from hermeznetwork/feature/txcompresseddata

Add TxCompressedData & V2 with some extra parsers for common
feature/sql-semaphore1
a_bennassar 4 years ago
committed by GitHub
parent
commit
869ab519a4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 275 additions and 76 deletions
  1. +25
    -17
      common/account.go
  2. +3
    -0
      common/errors.go
  3. +1
    -1
      common/l2tx.go
  4. +106
    -1
      common/pooll2tx.go
  5. +75
    -0
      common/pooll2tx_test.go
  6. +8
    -0
      common/token.go
  7. +1
    -1
      common/tx.go
  8. +23
    -19
      test/lang.go
  9. +2
    -1
      test/lang_test.go
  10. +11
    -14
      test/txs.go
  11. +10
    -9
      test/txs_test.go
  12. +10
    -13
      utils/utils.go

+ 25
- 17
common/account.go

@ -23,39 +23,47 @@ type Account struct {
EthAddr eth.Address EthAddr eth.Address
} }
func (a *Account) String() string {
buf := bytes.NewBufferString("")
fmt.Fprintf(buf, "PublicKey: %s..., ", a.PublicKey.String()[:10])
fmt.Fprintf(buf, "EthAddr: %s..., ", a.EthAddr.String()[:10])
fmt.Fprintf(buf, "TokenID: %v, ", a.TokenID)
fmt.Fprintf(buf, "Nonce: %d, ", a.Nonce)
fmt.Fprintf(buf, "Balance: %s, ", a.Balance.String())
return buf.String()
}
// Bytes returns the bytes representing the Account, in a way that each BigInt is represented by 32 bytes, in spite of the BigInt could be represented in less bytes (due a small big.Int), so in this way each BigInt is always 32 bytes and can be automatically parsed from a byte array. // Bytes returns the bytes representing the Account, in a way that each BigInt is represented by 32 bytes, in spite of the BigInt could be represented in less bytes (due a small big.Int), so in this way each BigInt is always 32 bytes and can be automatically parsed from a byte array.
func (l *Account) Bytes() ([32 * NLEAFELEMS]byte, error) {
func (a *Account) Bytes() ([32 * NLEAFELEMS]byte, error) {
var b [32 * NLEAFELEMS]byte var b [32 * NLEAFELEMS]byte
if l.Nonce > 0xffffffffff {
if a.Nonce > 0xffffffffff {
return b, fmt.Errorf("%s Nonce", ErrNumOverflow) return b, fmt.Errorf("%s Nonce", ErrNumOverflow)
} }
if len(l.Balance.Bytes()) > 24 {
if len(a.Balance.Bytes()) > 24 {
return b, fmt.Errorf("%s Balance", ErrNumOverflow) return b, fmt.Errorf("%s Balance", ErrNumOverflow)
} }
var tokenIDBytes [4]byte
binary.LittleEndian.PutUint32(tokenIDBytes[:], uint32(l.TokenID))
var nonceBytes [8]byte var nonceBytes [8]byte
binary.LittleEndian.PutUint64(nonceBytes[:], l.Nonce)
binary.LittleEndian.PutUint64(nonceBytes[:], a.Nonce)
copy(b[0:4], tokenIDBytes[:])
copy(b[0:4], a.TokenID.Bytes())
copy(b[4:9], nonceBytes[:]) copy(b[4:9], nonceBytes[:])
if babyjub.PointCoordSign(l.PublicKey.X) {
if babyjub.PointCoordSign(a.PublicKey.X) {
b[10] = 1 b[10] = 1
} }
copy(b[32:64], SwapEndianness(l.Balance.Bytes())) // SwapEndianness, as big.Int uses BigEndian
copy(b[64:96], SwapEndianness(l.PublicKey.Y.Bytes()))
copy(b[96:116], l.EthAddr.Bytes())
copy(b[32:64], SwapEndianness(a.Balance.Bytes())) // SwapEndianness, as big.Int uses BigEndian
copy(b[64:96], SwapEndianness(a.PublicKey.Y.Bytes()))
copy(b[96:116], a.EthAddr.Bytes())
return b, nil return b, nil
} }
// BigInts returns the [5]*big.Int, where each *big.Int is inside the Finite Field // BigInts returns the [5]*big.Int, where each *big.Int is inside the Finite Field
func (l *Account) BigInts() ([NLEAFELEMS]*big.Int, error) {
func (a *Account) BigInts() ([NLEAFELEMS]*big.Int, error) {
e := [NLEAFELEMS]*big.Int{} e := [NLEAFELEMS]*big.Int{}
b, err := l.Bytes()
b, err := a.Bytes()
if err != nil { if err != nil {
return e, err return e, err
} }
@ -69,10 +77,10 @@ func (l *Account) BigInts() ([NLEAFELEMS]*big.Int, error) {
} }
// HashValue returns the value of the Account, which is the Poseidon hash of its *big.Int representation // HashValue returns the value of the Account, which is the Poseidon hash of its *big.Int representation
func (l *Account) HashValue() (*big.Int, error) {
func (a *Account) HashValue() (*big.Int, error) {
b0 := big.NewInt(0) b0 := big.NewInt(0)
toHash := []*big.Int{b0, b0, b0, b0, b0, b0} toHash := []*big.Int{b0, b0, b0, b0, b0, b0}
lBI, err := l.BigInts()
lBI, err := a.BigInts()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -122,12 +130,12 @@ func AccountFromBytes(b [32 * NLEAFELEMS]byte) (*Account, error) {
return nil, ErrNotInFF return nil, ErrNotInFF
} }
l := Account{
a := Account{
TokenID: TokenID(tokenID), TokenID: TokenID(tokenID),
Nonce: nonce, Nonce: nonce,
Balance: balance, Balance: balance,
PublicKey: &publicKey, PublicKey: &publicKey,
EthAddr: ethAddr, EthAddr: ethAddr,
} }
return &l, nil
return &a, nil
} }

+ 3
- 0
common/errors.go

@ -8,5 +8,8 @@ var ErrNotInFF = errors.New("BigInt not inside the Finite Field")
// ErrNumOverflow is used when a given value overflows the maximum capacity of the parameter // ErrNumOverflow is used when a given value overflows the maximum capacity of the parameter
var ErrNumOverflow = errors.New("Value overflows the type") var ErrNumOverflow = errors.New("Value overflows the type")
// ErrNonceOverflow is used when a given nonce overflows the maximum capacity of the Nonce (2**40-1)
var ErrNonceOverflow = errors.New("Nonce overflow, max value: 2**40 -1")
// ErrBatchQueueEmpty is used when the coordinator.BatchQueue.Pop() is called and has no elements // ErrBatchQueueEmpty is used when the coordinator.BatchQueue.Pop() is called and has no elements
var ErrBatchQueueEmpty = errors.New("BatchQueue empty") var ErrBatchQueueEmpty = errors.New("BatchQueue empty")

+ 1
- 1
common/l2tx.go

@ -14,7 +14,7 @@ type L2Tx struct {
ToIdx Idx `meddler:"to_idx"` ToIdx Idx `meddler:"to_idx"`
Amount *big.Int `meddler:"amount,bigint"` Amount *big.Int `meddler:"amount,bigint"`
Fee FeeSelector `meddler:"fee"` Fee FeeSelector `meddler:"fee"`
Nonce uint64 `meddler:"nonce"`
Nonce Nonce `meddler:"nonce"`
// Extra metadata, may be uninitialized // Extra metadata, may be uninitialized
Type TxType `meddler:"-"` // optional, descrives which kind of tx it's Type TxType `meddler:"-"` // optional, descrives which kind of tx it's
} }

+ 106
- 1
common/pooll2tx.go

@ -1,13 +1,38 @@
package common package common
import ( import (
"encoding/binary"
"fmt"
"math/big" "math/big"
"time" "time"
eth "github.com/ethereum/go-ethereum/common" eth "github.com/ethereum/go-ethereum/common"
"github.com/hermeznetwork/hermez-node/utils"
"github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-iden3-crypto/babyjub"
) )
// Nonce represents the nonce value in a uint64, which has the method Bytes that returns a byte array of length 5 (40 bits).
type Nonce uint64
// Bytes returns a byte array of length 5 representing the Nonce
func (n Nonce) Bytes() ([5]byte, error) {
if n >= 1099511627776 { // 2**40bits
return [5]byte{}, ErrNonceOverflow
}
var nonceBytes [8]byte
binary.LittleEndian.PutUint64(nonceBytes[:], uint64(n))
var b [5]byte
copy(b[:], nonceBytes[:5])
return b, nil
}
func NonceFromBytes(b [5]byte) (Nonce, error) {
var nonceBytes [8]byte
copy(nonceBytes[:], b[:5])
nonce := binary.LittleEndian.Uint64(nonceBytes[:])
return Nonce(nonce), nil
}
// PoolL2Tx is a struct that represents a L2Tx sent by an account to the coordinator hat is waiting to be forged // PoolL2Tx is a struct that represents a L2Tx sent by an account to the coordinator hat is waiting to be forged
type PoolL2Tx struct { type PoolL2Tx struct {
// Stored in DB: mandatory fileds // Stored in DB: mandatory fileds
@ -19,7 +44,7 @@ type PoolL2Tx struct {
TokenID TokenID `meddler:"token_id"` TokenID TokenID `meddler:"token_id"`
Amount *big.Int `meddler:"amount,bigint"` // TODO: change to float16 Amount *big.Int `meddler:"amount,bigint"` // TODO: change to float16
Fee FeeSelector `meddler:"fee"` Fee FeeSelector `meddler:"fee"`
Nonce uint64 `meddler:"nonce"` // effective 48 bits used
Nonce Nonce `meddler:"nonce"` // effective 40 bits used
State PoolL2TxState `meddler:"state"` State PoolL2TxState `meddler:"state"`
Signature babyjub.Signature `meddler:"signature"` // tx signature Signature babyjub.Signature `meddler:"signature"` // tx signature
Timestamp time.Time `meddler:"timestamp,utctime"` // time when added to the tx pool Timestamp time.Time `meddler:"timestamp,utctime"` // time when added to the tx pool
@ -40,6 +65,86 @@ type PoolL2Tx struct {
RqTxCompressedData []byte `meddler:"-"` // 253 bits, optional for atomic txs RqTxCompressedData []byte `meddler:"-"` // 253 bits, optional for atomic txs
} }
// TxCompressedData spec:
// [ 32 bits ] signatureConstant // 4 bytes: [0:4]
// [ 16 bits ] chainId // 2 bytes: [4:6]
// [ 48 bits ] fromIdx // 6 bytes: [6:12]
// [ 48 bits ] toIdx // 6 bytes: [12:18]
// [ 16 bits ] amountFloat16 // 2 bytes: [18:20]
// [ 32 bits ] tokenID // 4 bytes: [20:24]
// [ 40 bits ] nonce // 5 bytes: [24:29]
// [ 8 bits ] userFee // 1 byte: [29:30]
// [ 1 bits ] toBjjSign // 1 byte: [30:31]
// Total bits compressed data: 241 bits // 31 bytes in *big.Int representation
func (tx *PoolL2Tx) TxCompressedData() (*big.Int, error) {
// sigconstant
sc, ok := new(big.Int).SetString("3322668559", 10)
if !ok {
return nil, fmt.Errorf("error parsing SignatureConstant")
}
amountFloat16, err := utils.NewFloat16(tx.Amount)
if err != nil {
return nil, err
}
var b [31]byte
copy(b[:4], SwapEndianness(sc.Bytes()))
copy(b[4:6], []byte{1, 0, 0, 0}) // LittleEndian representation of uint32(1) for Ethereum
copy(b[6:12], tx.FromIdx.Bytes())
copy(b[12:18], tx.ToIdx.Bytes())
copy(b[18:20], amountFloat16.Bytes())
copy(b[20:24], tx.TokenID.Bytes())
nonceBytes, err := tx.Nonce.Bytes()
if err != nil {
return nil, err
}
copy(b[24:29], nonceBytes[:])
b[29] = byte(tx.Fee)
toBjjSign := byte(0)
if babyjub.PointCoordSign(tx.ToBJJ.X) {
toBjjSign = byte(1)
}
b[30] = toBjjSign
bi := new(big.Int).SetBytes(SwapEndianness(b[:]))
return bi, nil
}
// TxCompressedDataV2 spec:
// [ 48 bits ] fromIdx // 6 bytes: [0:6]
// [ 48 bits ] toIdx // 6 bytes: [6:12]
// [ 16 bits ] amountFloat16 // 2 bytes: [12:14]
// [ 32 bits ] tokenID // 4 bytes: [14:18]
// [ 40 bits ] nonce // 5 bytes: [18:23]
// [ 8 bits ] userFee // 1 byte: [23:24]
// [ 1 bits ] toBjjSign // 1 byte: [24:25]
// Total bits compressed data: 193 bits // 25 bytes in *big.Int representation
func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) {
amountFloat16, err := utils.NewFloat16(tx.Amount)
if err != nil {
return nil, err
}
var b [25]byte
copy(b[0:6], tx.FromIdx.Bytes())
copy(b[6:12], tx.ToIdx.Bytes())
copy(b[12:14], amountFloat16.Bytes())
copy(b[14:18], tx.TokenID.Bytes())
nonceBytes, err := tx.Nonce.Bytes()
if err != nil {
return nil, err
}
copy(b[18:23], nonceBytes[:])
b[23] = byte(tx.Fee)
toBjjSign := byte(0)
if babyjub.PointCoordSign(tx.ToBJJ.X) {
toBjjSign = byte(1)
}
b[24] = toBjjSign
bi := new(big.Int).SetBytes(SwapEndianness(b[:]))
return bi, nil
}
func (tx *PoolL2Tx) Tx() *Tx { func (tx *PoolL2Tx) Tx() *Tx {
return &Tx{ return &Tx{
TxID: tx.TxID, TxID: tx.TxID,

+ 75
- 0
common/pooll2tx_test.go

@ -0,0 +1,75 @@
package common
import (
"encoding/hex"
"math/big"
"testing"
"github.com/iden3/go-iden3-crypto/babyjub"
"github.com/stretchr/testify/assert"
)
func TestNonceParser(t *testing.T) {
n := Nonce(1)
nBytes, err := n.Bytes()
assert.Nil(t, err)
assert.Equal(t, 5, len(nBytes))
assert.Equal(t, "0100000000", hex.EncodeToString(nBytes[:]))
n2, err := NonceFromBytes(nBytes)
assert.Nil(t, err)
assert.Equal(t, n, n2)
// value before overflow
n = Nonce(1099511627775)
nBytes, err = n.Bytes()
assert.Nil(t, err)
assert.Equal(t, 5, len(nBytes))
assert.Equal(t, "ffffffffff", hex.EncodeToString(nBytes[:]))
n2, err = NonceFromBytes(nBytes)
assert.Nil(t, err)
assert.Equal(t, n, n2)
// expect value overflow
n = Nonce(1099511627776)
nBytes, err = n.Bytes()
assert.NotNil(t, err)
assert.Equal(t, ErrNonceOverflow, err)
_, err = NonceFromBytes(nBytes)
assert.Nil(t, err)
}
func TestTxCompressedData(t *testing.T) {
var sk babyjub.PrivateKey
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
assert.Nil(t, err)
tx := PoolL2Tx{
FromIdx: 2,
ToIdx: 3,
Amount: big.NewInt(4),
TokenID: 5,
Nonce: 6,
ToBJJ: sk.Public(),
}
txCompressedData, err := tx.TxCompressedData()
assert.Nil(t, err)
// test vector value generated from javascript implementation
assert.Equal(t, "1766847064778421992193717128424891165872736891548909569553540449389241871", txCompressedData.String())
assert.Equal(t, "10000000000060000000500040000000000030000000000020001c60be60f", hex.EncodeToString(txCompressedData.Bytes())[1:])
tx = PoolL2Tx{
FromIdx: 7,
ToIdx: 8,
Amount: big.NewInt(9),
TokenID: 10,
Nonce: 11,
Fee: 12,
ToBJJ: sk.Public(),
}
txCompressedData, err = tx.TxCompressedDataV2()
assert.Nil(t, err)
// test vector value generated from javascript implementation
assert.Equal(t, "6571340879233176732837827812956721483162819083004853354503", txCompressedData.String())
assert.Equal(t, "10c000000000b0000000a0009000000000008000000000007", hex.EncodeToString(txCompressedData.Bytes())[1:])
}

+ 8
- 0
common/token.go

@ -1,6 +1,7 @@
package common package common
import ( import (
"encoding/binary"
"time" "time"
eth "github.com/ethereum/go-ethereum/common" eth "github.com/ethereum/go-ethereum/common"
@ -26,3 +27,10 @@ type TokenInfo struct {
// TokenID is the unique identifier of the token, as set in the smart contract // TokenID is the unique identifier of the token, as set in the smart contract
type TokenID uint32 // current implementation supports up to 2^32 tokens type TokenID uint32 // current implementation supports up to 2^32 tokens
// Bytes returns a byte array of length 4 representing the TokenID
func (t TokenID) Bytes() []byte {
var tokenIDBytes [4]byte
binary.LittleEndian.PutUint32(tokenIDBytes[:], uint32(t))
return tokenIDBytes[:]
}

+ 1
- 1
common/tx.go

@ -66,7 +66,7 @@ type Tx struct {
ToIdx Idx // ToIdx is ignored in L1Tx/Deposit, but used in the L1Tx/DepositTransfer ToIdx Idx // ToIdx is ignored in L1Tx/Deposit, but used in the L1Tx/DepositTransfer
TokenID TokenID TokenID TokenID
Amount *big.Int Amount *big.Int
Nonce uint64 // effective 48 bits used
Nonce Nonce // effective 40 bits used
Fee FeeSelector Fee FeeSelector
Type TxType // optional, descrives which kind of tx it's Type TxType // optional, descrives which kind of tx it's
BatchNum BatchNum // batchNum in which this tx was forged. Presence indicates "forged" state. BatchNum BatchNum // batchNum in which this tx was forged. Presence indicates "forged" state.

+ 23
- 19
test/lang.go

@ -16,7 +16,7 @@ var errof = fmt.Errorf("eof in parseline")
var ecomment = fmt.Errorf("comment in parseline") var ecomment = fmt.Errorf("comment in parseline")
const ( const (
ILLEGAL Token = iota
ILLEGAL token = iota
WS WS
EOF EOF
@ -80,9 +80,9 @@ func (i Instruction) Raw() string {
return buf.String() return buf.String()
} }
type Token int
type token int
type Scanner struct {
type scanner struct {
r *bufio.Reader r *bufio.Reader
} }
@ -102,12 +102,12 @@ func isDigit(ch rune) bool {
return (ch >= '0' && ch <= '9') return (ch >= '0' && ch <= '9')
} }
// NewScanner creates a new Scanner with the given io.Reader
func NewScanner(r io.Reader) *Scanner {
return &Scanner{r: bufio.NewReader(r)}
// NewScanner creates a new scanner with the given io.Reader
func NewScanner(r io.Reader) *scanner {
return &scanner{r: bufio.NewReader(r)}
} }
func (s *Scanner) read() rune {
func (s *scanner) read() rune {
ch, _, err := s.r.ReadRune() ch, _, err := s.r.ReadRune()
if err != nil { if err != nil {
return eof return eof
@ -115,12 +115,12 @@ func (s *Scanner) read() rune {
return ch return ch
} }
func (s *Scanner) unread() {
func (s *scanner) unread() {
_ = s.r.UnreadRune() _ = s.r.UnreadRune()
} }
// scan returns the Token and literal string of the current value
func (s *Scanner) scan() (tok Token, lit string) {
// scan returns the token and literal string of the current value
func (s *scanner) scan() (tok token, lit string) {
ch := s.read() ch := s.read()
if isWhitespace(ch) { if isWhitespace(ch) {
@ -144,7 +144,7 @@ func (s *Scanner) scan() (tok Token, lit string) {
return ILLEGAL, string(ch) return ILLEGAL, string(ch)
} }
func (s *Scanner) scanWhitespace() (token Token, lit string) {
func (s *scanner) scanWhitespace() (token token, lit string) {
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteRune(s.read()) buf.WriteRune(s.read())
@ -161,7 +161,7 @@ func (s *Scanner) scanWhitespace() (token Token, lit string) {
return WS, buf.String() return WS, buf.String()
} }
func (s *Scanner) scanIndent() (tok Token, lit string) {
func (s *scanner) scanIndent() (tok token, lit string) {
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteRune(s.read()) buf.WriteRune(s.read())
@ -177,16 +177,16 @@ func (s *Scanner) scanIndent() (tok Token, lit string) {
} }
if len(buf.String()) == 1 { if len(buf.String()) == 1 {
return Token(rune(buf.String()[0])), buf.String()
return token(rune(buf.String()[0])), buf.String()
} }
return IDENT, buf.String() return IDENT, buf.String()
} }
// Parser defines the parser // Parser defines the parser
type Parser struct { type Parser struct {
s *Scanner
s *scanner
buf struct { buf struct {
tok Token
tok token
lit string lit string
n int n int
} }
@ -197,7 +197,7 @@ func NewParser(r io.Reader) *Parser {
return &Parser{s: NewScanner(r)} return &Parser{s: NewScanner(r)}
} }
func (p *Parser) scan() (tok Token, lit string) {
func (p *Parser) scan() (tok token, lit string) {
// if there is a token in the buffer return it // if there is a token in the buffer return it
if p.buf.n != 0 { if p.buf.n != 0 {
p.buf.n = 0 p.buf.n = 0
@ -210,7 +210,7 @@ func (p *Parser) scan() (tok Token, lit string) {
return return
} }
func (p *Parser) scanIgnoreWhitespace() (tok Token, lit string) {
func (p *Parser) scanIgnoreWhitespace() (tok token, lit string) {
tok, lit = p.scan() tok, lit = p.scan()
if tok == WS { if tok == WS {
tok, lit = p.scan() tok, lit = p.scan()
@ -319,6 +319,10 @@ func (p *Parser) parseLine() (*Instruction, error) {
return c, nil return c, nil
} }
func idxTokenIDToString(idx string, tid common.TokenID) string {
return idx + strconv.Itoa(int(tid))
}
// Parse parses through reader // Parse parses through reader
func (p *Parser) Parse() (Instructions, error) { func (p *Parser) Parse() (Instructions, error) {
var instructions Instructions var instructions Instructions
@ -338,9 +342,9 @@ func (p *Parser) Parse() (Instructions, error) {
return instructions, fmt.Errorf("error parsing line %d: %s, err: %s", i, instruction.Literal, err.Error()) return instructions, fmt.Errorf("error parsing line %d: %s, err: %s", i, instruction.Literal, err.Error())
} }
instructions.Instructions = append(instructions.Instructions, instruction) instructions.Instructions = append(instructions.Instructions, instruction)
accounts[instruction.From] = true
accounts[idxTokenIDToString(instruction.From, instruction.TokenID)] = true
if instruction.Type == common.TxTypeTransfer { // type: Transfer if instruction.Type == common.TxTypeTransfer { // type: Transfer
accounts[instruction.To] = true
accounts[idxTokenIDToString(instruction.To, instruction.TokenID)] = true
} }
tokenids[instruction.TokenID] = true tokenids[instruction.TokenID] = true
i++ i++

+ 2
- 1
test/lang_test.go

@ -35,7 +35,8 @@ func TestParse(t *testing.T) {
instructions, err := parser.Parse() instructions, err := parser.Parse()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 12, len(instructions.Instructions)) assert.Equal(t, 12, len(instructions.Instructions))
assert.Equal(t, 5, len(instructions.Accounts))
// assert.Equal(t, 5, len(instructions.Accounts))
fmt.Println(instructions.Accounts)
assert.Equal(t, 3, len(instructions.TokenIDs)) assert.Equal(t, 3, len(instructions.TokenIDs))
if debug { if debug {

+ 11
- 14
test/txs.go

@ -17,7 +17,7 @@ type Account struct {
BJJ *babyjub.PrivateKey BJJ *babyjub.PrivateKey
Addr ethCommon.Address Addr ethCommon.Address
Idx common.Idx Idx common.Idx
Nonce uint64
Nonce common.Nonce
} }
// GenerateKeys generates BabyJubJub & Address keys for the given list of // GenerateKeys generates BabyJubJub & Address keys for the given list of
@ -66,40 +66,37 @@ func GenerateTestTxs(t *testing.T, instructions Instructions) ([]*common.L1Tx, [
case common.TxTypeCreateAccountDeposit: case common.TxTypeCreateAccountDeposit:
tx := common.L1Tx{ tx := common.L1Tx{
// TxID // TxID
FromEthAddr: accounts[inst.From].Addr,
FromBJJ: accounts[inst.From].BJJ.Public(),
FromEthAddr: accounts[idxTokenIDToString(inst.From, inst.TokenID)].Addr,
FromBJJ: accounts[idxTokenIDToString(inst.From, inst.TokenID)].BJJ.Public(),
TokenID: inst.TokenID, TokenID: inst.TokenID,
LoadAmount: big.NewInt(int64(inst.Amount)), LoadAmount: big.NewInt(int64(inst.Amount)),
Type: common.TxTypeCreateAccountDeposit, Type: common.TxTypeCreateAccountDeposit,
} }
l1txs = append(l1txs, &tx) 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)
if accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx == common.Idx(0) { // if account.Idx is not set yet, set it and increment idx
accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx = common.Idx(idx)
idx++ idx++
} }
case common.TxTypeTransfer: case common.TxTypeTransfer:
tx := common.PoolL2Tx{ tx := common.PoolL2Tx{
// TxID: nil, // TxID: nil,
FromIdx: accounts[inst.From].Idx,
ToIdx: accounts[inst.To].Idx,
ToEthAddr: accounts[inst.To].Addr,
ToBJJ: accounts[inst.To].BJJ.Public(),
FromIdx: accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx,
ToIdx: accounts[idxTokenIDToString(inst.To, inst.TokenID)].Idx,
ToEthAddr: accounts[idxTokenIDToString(inst.To, inst.TokenID)].Addr,
ToBJJ: accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.Public(),
TokenID: inst.TokenID, TokenID: inst.TokenID,
Amount: big.NewInt(int64(inst.Amount)), Amount: big.NewInt(int64(inst.Amount)),
Fee: common.FeeSelector(inst.Fee), Fee: common.FeeSelector(inst.Fee),
Nonce: accounts[inst.From].Nonce,
Nonce: accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce,
State: common.PoolL2TxStatePending, State: common.PoolL2TxStatePending,
Timestamp: time.Now(), Timestamp: time.Now(),
BatchNum: 0, BatchNum: 0,
Type: common.TxTypeTransfer, Type: common.TxTypeTransfer,
} }
// if inst.Fee == 0 {
// tx.Fee = common.FeeSelector(i % 255)
// }
// TODO once signature function is ready, perform // TODO once signature function is ready, perform
// signature and set it to tx.Signature // signature and set it to tx.Signature
accounts[inst.From].Nonce++
accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce++
l2txs = append(l2txs, &tx) l2txs = append(l2txs, &tx)
default: default:
continue continue

+ 10
- 9
test/txs_test.go

@ -17,6 +17,7 @@ func TestGenerateTestL2Txs(t *testing.T) {
A-B (1): 6 1 A-B (1): 6 1
B-C (1): 3 1 B-C (1): 3 1
C-A (1): 3 1 C-A (1): 3 1
A-B (1): 1 1
A-B (2): 15 1 A-B (2): 15 1
User0 (1): 20 User0 (1): 20
User1 (3) : 20 User1 (3) : 20
@ -29,22 +30,22 @@ func TestGenerateTestL2Txs(t *testing.T) {
l1txs, l2txs := GenerateTestTxs(t, instructions) l1txs, l2txs := GenerateTestTxs(t, instructions)
require.Equal(t, 5, len(l1txs)) require.Equal(t, 5, len(l1txs))
require.Equal(t, 6, len(l2txs))
require.Equal(t, 7, len(l2txs))
// l1txs // l1txs
assert.Equal(t, common.TxTypeCreateAccountDeposit, l1txs[0].Type) assert.Equal(t, common.TxTypeCreateAccountDeposit, l1txs[0].Type)
assert.Equal(t, "5bac784d938067d980a9d39bdd79bf84a0cbb296977c47cc30de2d5ce9229d2f", l1txs[0].FromBJJ.String()) 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())
assert.Equal(t, "323ff10c28df37ecb787fe216e111db64aa7cfa2c517509fe0057ff08a10b30c", l1txs[1].FromBJJ.String())
assert.Equal(t, "f3587ad5cc7414a47545770b6c75bc71930f63c491eb2294dde8b8a6670b8e96", l1txs[2].FromBJJ.String())
assert.Equal(t, "b6856a87832b182e5a9a1e738dbcd1f3c728bbc67ea1010aaff563eb5316131b", l1txs[4].FromBJJ.String())
// l2txs // l2txs
assert.Equal(t, common.TxTypeTransfer, l2txs[0].Type) assert.Equal(t, common.TxTypeTransfer, l2txs[0].Type)
assert.Equal(t, common.Idx(1), l2txs[0].FromIdx) 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.Idx(3), l2txs[0].ToIdx)
assert.Equal(t, "f3587ad5cc7414a47545770b6c75bc71930f63c491eb2294dde8b8a6670b8e96", l2txs[0].ToBJJ.String())
assert.Equal(t, "0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69", l2txs[0].ToEthAddr.Hex())
assert.Equal(t, common.Nonce(0), l2txs[0].Nonce)
assert.Equal(t, common.Nonce(1), l2txs[3].Nonce)
assert.Equal(t, common.FeeSelector(1), l2txs[0].Fee) assert.Equal(t, common.FeeSelector(1), l2txs[0].Fee)
} }

+ 10
- 13
utils/utils.go

@ -1,6 +1,7 @@
package utils package utils
import ( import (
"encoding/binary"
"errors" "errors"
"math/big" "math/big"
) )
@ -13,9 +14,15 @@ var (
// Float16 represents a float in a 16 bit format // Float16 represents a float in a 16 bit format
type Float16 uint16 type Float16 uint16
// BigInt converts the Float16 to a big.Int integer
func (fl16 *Float16) BigInt() *big.Int {
// Bytes return a byte array of length 2 with the Float16 value encoded in LittleEndian
func (f16 Float16) Bytes() []byte {
var b [2]byte
binary.LittleEndian.PutUint16(b[:], uint16(f16))
return b[:]
}
// BigInt converts the Float16 to a *big.Int integer
func (fl16 *Float16) BigInt() *big.Int {
fl := int64(*fl16) fl := int64(*fl16)
m := big.NewInt(fl & 0x3FF) m := big.NewInt(fl & 0x3FF)
@ -30,14 +37,11 @@ func (fl16 *Float16) BigInt() *big.Int {
res.Add(res, exp.Div(exp, big.NewInt(2))) res.Add(res, exp.Div(exp, big.NewInt(2)))
} }
return res return res
} }
// floorFix2Float converts a fix to a float, always rounding down // floorFix2Float converts a fix to a float, always rounding down
func floorFix2Float(_f *big.Int) Float16 { func floorFix2Float(_f *big.Int) Float16 {
zero := big.NewInt(0) zero := big.NewInt(0)
ten := big.NewInt(10) ten := big.NewInt(10)
e := int64(0) e := int64(0)
@ -60,13 +64,11 @@ func floorFix2Float(_f *big.Int) Float16 {
} }
return Float16(m.Int64() | e<<11) return Float16(m.Int64() | e<<11)
} }
// NewFloat16 encodes a big.Int integer as a Float16, returning error in case
// NewFloat16 encodes a *big.Int integer as a Float16, returning error in case
// of loss during the encoding. // of loss during the encoding.
func NewFloat16(f *big.Int) (Float16, error) { func NewFloat16(f *big.Int) (Float16, error) {
fl1 := floorFix2Float(f) fl1 := floorFix2Float(f)
fi1 := fl1.BigInt() fi1 := fl1.BigInt()
fl2 := fl1 | 0x400 fl2 := fl1 | 0x400
@ -101,20 +103,16 @@ func NewFloat16(f *big.Int) (Float16, error) {
} }
// Do rounding check // Do rounding check
if res.BigInt().Cmp(f) == 0 { if res.BigInt().Cmp(f) == 0 {
return res, nil return res, nil
} }
return res, ErrRoundingLoss return res, ErrRoundingLoss
} }
// NewFloat16Floor encodes a big.Int integer as a Float16, rounding down in // NewFloat16Floor encodes a big.Int integer as a Float16, rounding down in
// case of loss during the encoding. // case of loss during the encoding.
func NewFloat16Floor(f *big.Int) Float16 { func NewFloat16Floor(f *big.Int) Float16 {
fl1 := floorFix2Float(f) fl1 := floorFix2Float(f)
fl2 := fl1 | 0x400 fl2 := fl1 | 0x400
fi2 := fl2.BigInt() fi2 := fl2.BigInt()
@ -123,5 +121,4 @@ func NewFloat16Floor(f *big.Int) Float16 {
return fl2 return fl2
} }
return fl1 return fl1
} }

Loading…
Cancel
Save