mirror of
https://github.com/arnaucube/hermez-node.git
synced 2026-02-07 03:16:45 +01:00
Add methods for ZKInputs IntermStates generation
- Add L1Tx TxCompressedData method - Add PoolL2Tx TxCompressedDataV2 method - Update ProcessTxs logic - Add ZKInputs Intermediate States & Fee parameters calculation
This commit is contained in:
@@ -6,6 +6,16 @@ import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// MaxFeePlan is the maximum value of the FeePlan
|
||||
const MaxFeePlan = 256
|
||||
|
||||
// FeePlan represents the fee model, a position in the array indicates the
|
||||
// percentage of tokens paid in concept of fee for a transaction
|
||||
var FeePlan = [MaxFeePlan]float64{}
|
||||
|
||||
// FeeFactorLsh60 is the feeFactor << 60
|
||||
var FeeFactorLsh60 [256]*big.Int
|
||||
|
||||
// RecommendedFee is the recommended fee to pay in USD per transaction set by
|
||||
// the coordinator according to the tx type (if the tx requires to create an
|
||||
// account and register, only register or he account already esists)
|
||||
@@ -31,13 +41,6 @@ func (f FeeSelector) Percentage() float64 {
|
||||
}
|
||||
}
|
||||
|
||||
// MaxFeePlan is the maximum value of the FeePlan
|
||||
const MaxFeePlan = 256
|
||||
|
||||
// FeePlan represents the fee model, a position in the array indicates the
|
||||
// percentage of tokens paid in concept of fee for a transaction
|
||||
var FeePlan = [MaxFeePlan]float64{}
|
||||
|
||||
// CalcFeeAmount calculates the fee amount in tokens from an amount and
|
||||
// feeSelector (fee index).
|
||||
func CalcFeeAmount(amount *big.Int, feeSel FeeSelector) (*big.Int, error) {
|
||||
@@ -55,9 +58,6 @@ func init() {
|
||||
setFeeFactorLsh60(&FeeFactorLsh60)
|
||||
}
|
||||
|
||||
// FeeFactorLsh60 is the feeFactor << 60
|
||||
var FeeFactorLsh60 [256]*big.Int
|
||||
|
||||
func setFeeFactorLsh60(feeFactorLsh60 *[256]*big.Int) {
|
||||
feeFactorLsh60[0], _ = new(big.Int).SetString("0", 10)
|
||||
feeFactorLsh60[1], _ = new(big.Int).SetString("3", 10)
|
||||
|
||||
@@ -146,6 +146,43 @@ func (tx L1Tx) Tx() Tx {
|
||||
return genericTx
|
||||
}
|
||||
|
||||
// TxCompressedData spec:
|
||||
// [ 1 bits ] empty (toBJJSign) // 1 byte
|
||||
// [ 8 bits ] empty (userFee) // 1 byte
|
||||
// [ 40 bits ] empty (nonce) // 5 bytes
|
||||
// [ 32 bits ] tokenID // 4 bytes
|
||||
// [ 16 bits ] amountFloat16 // 2 bytes
|
||||
// [ 48 bits ] toIdx // 6 bytes
|
||||
// [ 48 bits ] fromIdx // 6 bytes
|
||||
// [ 16 bits ] chainId // 2 bytes
|
||||
// [ 32 bits ] empty (signatureConstant) // 4 bytes
|
||||
// Total bits compressed data: 241 bits // 31 bytes in *big.Int representation
|
||||
func (tx L1Tx) TxCompressedData() (*big.Int, error) {
|
||||
amountFloat16, err := NewFloat16(tx.Amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var b [31]byte
|
||||
// b[0:7] empty: no fee neither nonce
|
||||
copy(b[7:11], tx.TokenID.Bytes())
|
||||
copy(b[11:13], amountFloat16.Bytes())
|
||||
toIdxBytes, err := tx.ToIdx.Bytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(b[13:19], toIdxBytes[:])
|
||||
fromIdxBytes, err := tx.FromIdx.Bytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(b[19:25], fromIdxBytes[:])
|
||||
copy(b[25:27], []byte{0, 1}) // TODO this will be generated by the ChainID config parameter
|
||||
// b[27:] empty: no signature
|
||||
|
||||
bi := new(big.Int).SetBytes(b[:])
|
||||
return bi, nil
|
||||
}
|
||||
|
||||
// BytesGeneric returns the generic representation of a L1Tx. This method is
|
||||
// used to compute the []byte representation of a L1UserTx, and also to compute
|
||||
// the L1TxData for the ZKInputs (at the HashGlobalInputs), using this method
|
||||
|
||||
@@ -49,6 +49,22 @@ func TestNewL1CoordinatorTx(t *testing.T) {
|
||||
assert.Equal(t, "0x01000000000000cafe005800", l1Tx.TxID.String())
|
||||
}
|
||||
|
||||
func TestL1TxCompressedData(t *testing.T) {
|
||||
tx := L1Tx{
|
||||
FromIdx: 2,
|
||||
ToIdx: 3,
|
||||
Amount: big.NewInt(4),
|
||||
TokenID: 5,
|
||||
}
|
||||
txCompressedData, err := tx.TxCompressedData()
|
||||
assert.Nil(t, err)
|
||||
|
||||
// test vector value generated from javascript implementation
|
||||
expectedStr := "7307597389635308713748674793997299267460566876160"
|
||||
assert.Equal(t, expectedStr, txCompressedData.String())
|
||||
assert.Equal(t, "050004000000000003000000000002000100000000", hex.EncodeToString(txCompressedData.Bytes()))
|
||||
}
|
||||
|
||||
func TestL1userTxByteParsers(t *testing.T) {
|
||||
var pkComp babyjub.PublicKeyComp
|
||||
pkCompL := []byte("0x56ca90f80d7c374ae7485e9bcc47d4ac399460948da6aeeb899311097925a72c")
|
||||
|
||||
@@ -140,13 +140,64 @@ func (tx *PoolL2Tx) TxCompressedData() (*big.Int, error) {
|
||||
return nil, err
|
||||
}
|
||||
copy(b[19:25], fromIdxBytes[:])
|
||||
copy(b[25:27], []byte{0, 1, 0, 0}) // TODO check js implementation (unexpected behaviour from test vector generated from js)
|
||||
copy(b[25:27], []byte{0, 1}) // TODO this will be generated by the ChainID config parameter
|
||||
copy(b[27:31], sc.Bytes())
|
||||
|
||||
bi := new(big.Int).SetBytes(b[:])
|
||||
return bi, nil
|
||||
}
|
||||
|
||||
// TxCompressedDataV2 spec:
|
||||
// [ 1 bits ] toBJJSign // 1 byte
|
||||
// [ 8 bits ] userFee // 1 byte
|
||||
// [ 40 bits ] nonce // 5 bytes
|
||||
// [ 32 bits ] tokenID // 4 bytes
|
||||
// [ 16 bits ] amountFloat16 // 2 bytes
|
||||
// [ 48 bits ] toIdx // 6 bytes
|
||||
// [ 48 bits ] fromIdx // 6 bytes
|
||||
// Total bits compressed data: 193 bits // 25 bytes in *big.Int representation
|
||||
func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) {
|
||||
if tx.Amount == nil {
|
||||
tx.Amount = big.NewInt(0)
|
||||
}
|
||||
amountFloat16, err := NewFloat16(tx.Amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var b [25]byte
|
||||
toBJJSign := byte(0)
|
||||
if tx.ToBJJ != nil && babyjub.PointCoordSign(tx.ToBJJ.X) {
|
||||
toBJJSign = byte(1)
|
||||
}
|
||||
b[0] = toBJJSign
|
||||
b[1] = byte(tx.Fee)
|
||||
nonceBytes, err := tx.Nonce.Bytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(b[2:7], nonceBytes[:])
|
||||
copy(b[7:11], tx.TokenID.Bytes())
|
||||
copy(b[11:13], amountFloat16.Bytes())
|
||||
toIdxBytes, err := tx.ToIdx.Bytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(b[13:19], toIdxBytes[:])
|
||||
fromIdxBytes, err := tx.FromIdx.Bytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(b[19:25], fromIdxBytes[:])
|
||||
|
||||
bi := new(big.Int).SetBytes(b[:])
|
||||
return bi, nil
|
||||
}
|
||||
|
||||
// RqTxCompressedDataV2 is like the TxCompressedDataV2 but using the 'Rq'
|
||||
// parameters. In a future iteration of the hermez-node, the 'Rq' parameters
|
||||
// can be inside a struct, which contains the 'Rq' transaction grouped inside,
|
||||
// so then computing the 'RqTxCompressedDataV2' would be just calling
|
||||
// 'tx.Rq.TxCompressedDataV2()'.
|
||||
// RqTxCompressedDataV2 spec:
|
||||
// [ 1 bits ] rqToBJJSign // 1 byte
|
||||
// [ 8 bits ] rqUserFee // 1 byte
|
||||
|
||||
@@ -40,10 +40,7 @@ func TestTxCompressedData(t *testing.T) {
|
||||
// test vector value generated from javascript implementation
|
||||
expectedStr := "1766847064778421992193717128424891165872736891548909569553540449389241871"
|
||||
assert.Equal(t, expectedStr, txCompressedData.String())
|
||||
expected, ok := new(big.Int).SetString(expectedStr, 10)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, expected.Bytes(), txCompressedData.Bytes())
|
||||
assert.Equal(t, "10000000000060000000500040000000000030000000000020001c60be60f", hex.EncodeToString(txCompressedData.Bytes())[1:])
|
||||
assert.Equal(t, "010000000000060000000500040000000000030000000000020001c60be60f", hex.EncodeToString(txCompressedData.Bytes()))
|
||||
tx = PoolL2Tx{
|
||||
RqFromIdx: 7,
|
||||
RqToIdx: 8,
|
||||
@@ -58,10 +55,56 @@ func TestTxCompressedData(t *testing.T) {
|
||||
// test vector value generated from javascript implementation
|
||||
expectedStr = "6571340879233176732837827812956721483162819083004853354503"
|
||||
assert.Equal(t, expectedStr, txCompressedData.String())
|
||||
expected, ok = new(big.Int).SetString(expectedStr, 10)
|
||||
assert.Equal(t, "010c000000000b0000000a0009000000000008000000000007", hex.EncodeToString(txCompressedData.Bytes()))
|
||||
}
|
||||
|
||||
func TestTxCompressedDataV2(t *testing.T) {
|
||||
var sk babyjub.PrivateKey
|
||||
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
|
||||
assert.Nil(t, err)
|
||||
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
|
||||
expectedStr := "6571340879233176732837827812956721483162819083004853354503"
|
||||
assert.Equal(t, expectedStr, txCompressedData.String())
|
||||
expected, ok := new(big.Int).SetString(expectedStr, 10)
|
||||
assert.True(t, ok)
|
||||
|
||||
assert.Equal(t, expected.Bytes(), txCompressedData.Bytes())
|
||||
assert.Equal(t, "010c000000000b0000000a0009000000000008000000000007", hex.EncodeToString(txCompressedData.Bytes()))
|
||||
}
|
||||
|
||||
func TestRqTxCompressedDataV2(t *testing.T) {
|
||||
var sk babyjub.PrivateKey
|
||||
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
|
||||
assert.Nil(t, err)
|
||||
tx := PoolL2Tx{
|
||||
RqFromIdx: 7,
|
||||
RqToIdx: 8,
|
||||
RqAmount: big.NewInt(9),
|
||||
RqTokenID: 10,
|
||||
RqNonce: 11,
|
||||
RqFee: 12,
|
||||
RqToBJJ: sk.Public(),
|
||||
}
|
||||
txCompressedData, err := tx.RqTxCompressedDataV2()
|
||||
assert.Nil(t, err)
|
||||
// test vector value generated from javascript implementation
|
||||
expectedStr := "6571340879233176732837827812956721483162819083004853354503"
|
||||
assert.Equal(t, expectedStr, txCompressedData.String())
|
||||
expected, ok := new(big.Int).SetString(expectedStr, 10)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, expected.Bytes(), txCompressedData.Bytes())
|
||||
assert.Equal(t, "10c000000000b0000000a0009000000000008000000000007", hex.EncodeToString(txCompressedData.Bytes())[1:])
|
||||
assert.Equal(t, "010c000000000b0000000a0009000000000008000000000007", hex.EncodeToString(txCompressedData.Bytes()))
|
||||
}
|
||||
|
||||
func TestHashToSign(t *testing.T) {
|
||||
|
||||
@@ -52,3 +52,8 @@ func TokenIDFromBytes(b []byte) (TokenID, error) {
|
||||
tid := binary.BigEndian.Uint32(b[:4])
|
||||
return TokenID(tid), nil
|
||||
}
|
||||
|
||||
// TokenIDFromBigInt returns a TokenID with the value of the given *big.Int
|
||||
func TokenIDFromBigInt(b *big.Int) TokenID {
|
||||
return TokenID(b.Int64())
|
||||
}
|
||||
|
||||
19
common/zk.go
19
common/zk.go
@@ -43,7 +43,7 @@ type ZKMetadata struct {
|
||||
|
||||
// ZKInputs represents the inputs that will be used to generate the zkSNARK proof
|
||||
type ZKInputs struct {
|
||||
Metadata ZKMetadata
|
||||
Metadata ZKMetadata `json:"-"`
|
||||
|
||||
//
|
||||
// General
|
||||
@@ -183,23 +183,28 @@ type ZKInputs struct {
|
||||
// will not be used.
|
||||
|
||||
// decode-tx
|
||||
// ISOnChain indicates if tx is L1 (true) or L2 (false)
|
||||
// ISOnChain indicates if tx is L1 (true (1)) or L2 (false (0))
|
||||
ISOnChain []*big.Int `json:"imOnChain"` // bool, len: [nTx - 1]
|
||||
// ISOutIdx current index account for each Tx
|
||||
// Contains the index of the created account in case that the tx is of
|
||||
// account creation type.
|
||||
ISOutIdx []*big.Int `json:"imOutIdx"` // uint64 (max nLevels bits), len: [nTx - 1]
|
||||
// rollup-tx
|
||||
// ISStateRoot root at the moment of the Tx, the state root value once the Tx is processed into the state tree
|
||||
// ISStateRoot root at the moment of the Tx (once processed), the state root value once the Tx is processed into the state tree
|
||||
ISStateRoot []*big.Int `json:"imStateRoot"` // Hash, len: [nTx - 1]
|
||||
// ISExitTree root at the moment of the Tx the value once the Tx is processed into the exit tree
|
||||
// ISExitTree root at the moment (once processed) of the Tx the value
|
||||
// once the Tx is processed into the exit tree
|
||||
ISExitRoot []*big.Int `json:"imExitRoot"` // Hash, len: [nTx - 1]
|
||||
// ISAccFeeOut accumulated fees once the Tx is processed
|
||||
// ISAccFeeOut accumulated fees once the Tx is processed. Contains the
|
||||
// array of FeeAccount Balances at each moment of each Tx processed.
|
||||
ISAccFeeOut [][]*big.Int `json:"imAccFeeOut"` // big.Int, len: [nTx - 1][maxFeeIdxs]
|
||||
// fee-tx
|
||||
// ISStateRootFee root at the moment of the Tx, the state root value once the Tx is processed into the state tree
|
||||
// ISStateRootFee root at the moment of the Tx (once processed), the state root value once the Tx is processed into the state tree
|
||||
ISStateRootFee []*big.Int `json:"imStateRootFee"` // Hash, len: [maxFeeIdxs - 1]
|
||||
// ISInitStateRootFee state root once all L1-L2 tx are processed (before computing the fees-tx)
|
||||
ISInitStateRootFee *big.Int `json:"imInitStateRootFee"` // Hash
|
||||
// ISFinalAccFee final accumulated fees (before computing the fees-tx)
|
||||
// ISFinalAccFee final accumulated fees (before computing the fees-tx).
|
||||
// Contains the final values of the ISAccFeeOut parameter
|
||||
ISFinalAccFee []*big.Int `json:"imFinalAccFee"` // big.Int, len: [maxFeeIdxs - 1]
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package statedb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
@@ -72,6 +73,10 @@ func (s *StateDB) ProcessTxs(ptc ProcessTxsConfig, coordIdxs []common.Idx, l1use
|
||||
}
|
||||
defer s.resetZKInputs()
|
||||
|
||||
if len(coordIdxs) >= int(ptc.MaxFeeTx) {
|
||||
return nil, fmt.Errorf("CoordIdxs (%d) length must be smaller than MaxFeeTx (%d)", len(coordIdxs), ptc.MaxFeeTx)
|
||||
}
|
||||
|
||||
s.accumulatedFees = make(map[common.Idx]*big.Int)
|
||||
|
||||
nTx := len(l1usertxs) + len(l1coordinatortxs) + len(l2txs)
|
||||
@@ -94,7 +99,7 @@ func (s *StateDB) ProcessTxs(ptc ProcessTxsConfig, coordIdxs []common.Idx, l1use
|
||||
}
|
||||
|
||||
// TBD if ExitTree is only in memory or stored in disk, for the moment
|
||||
// only needed in memory
|
||||
// is only needed in memory
|
||||
if s.typ == TypeSynchronizer || s.typ == TypeBatchBuilder {
|
||||
tmpDir, err := ioutil.TempDir("", "hermez-statedb-exittree")
|
||||
if err != nil {
|
||||
@@ -122,17 +127,6 @@ func (s *StateDB) ProcessTxs(ptc ProcessTxsConfig, coordIdxs []common.Idx, l1use
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.typ == TypeSynchronizer || s.typ == TypeBatchBuilder {
|
||||
if exitIdx != nil && exitTree != nil {
|
||||
exits[s.i] = processedExit{
|
||||
exit: true,
|
||||
newExit: newExit,
|
||||
idx: *exitIdx,
|
||||
acc: *exitAccount,
|
||||
}
|
||||
}
|
||||
s.i++
|
||||
}
|
||||
if s.typ == TypeSynchronizer && createdAccount != nil {
|
||||
createdAccounts = append(createdAccounts, *createdAccount)
|
||||
}
|
||||
@@ -143,6 +137,22 @@ func (s *StateDB) ProcessTxs(ptc ProcessTxsConfig, coordIdxs []common.Idx, l1use
|
||||
return nil, err
|
||||
}
|
||||
s.zki.Metadata.L1TxsData = append(s.zki.Metadata.L1TxsData, l1TxData)
|
||||
|
||||
if s.i < nTx-1 {
|
||||
s.zki.ISOutIdx[s.i] = s.idx.BigInt()
|
||||
s.zki.ISStateRoot[s.i] = s.mt.Root().BigInt()
|
||||
}
|
||||
}
|
||||
if s.typ == TypeSynchronizer || s.typ == TypeBatchBuilder {
|
||||
if exitIdx != nil && exitTree != nil {
|
||||
exits[s.i] = processedExit{
|
||||
exit: true,
|
||||
newExit: newExit,
|
||||
idx: *exitIdx,
|
||||
acc: *exitAccount,
|
||||
}
|
||||
}
|
||||
s.i++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +174,12 @@ func (s *StateDB) ProcessTxs(ptc ProcessTxsConfig, coordIdxs []common.Idx, l1use
|
||||
return nil, err
|
||||
}
|
||||
s.zki.Metadata.L1TxsData = append(s.zki.Metadata.L1TxsData, l1TxData)
|
||||
|
||||
if s.i < nTx-1 {
|
||||
s.zki.ISOutIdx[s.i] = s.idx.BigInt()
|
||||
s.zki.ISStateRoot[s.i] = s.mt.Root().BigInt()
|
||||
}
|
||||
s.i++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,20 +195,49 @@ func (s *StateDB) ProcessTxs(ptc ProcessTxsConfig, coordIdxs []common.Idx, l1use
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// collectedFees will contain the amount of fee collected for each
|
||||
// TokenID
|
||||
var collectedFees map[common.TokenID]*big.Int
|
||||
if s.typ == TypeSynchronizer {
|
||||
if s.typ == TypeSynchronizer || s.typ == TypeBatchBuilder {
|
||||
collectedFees = make(map[common.TokenID]*big.Int)
|
||||
for tokenID := range coordIdxsMap {
|
||||
collectedFees[tokenID] = big.NewInt(0)
|
||||
}
|
||||
}
|
||||
|
||||
if s.zki != nil {
|
||||
// get the feePlanTokens
|
||||
feePlanTokens, err := s.getFeePlanTokens(coordIdxs, l2txs)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
copy(s.zki.FeePlanTokens, feePlanTokens)
|
||||
}
|
||||
|
||||
// Process L2Txs
|
||||
for i := 0; i < len(l2txs); i++ {
|
||||
exitIdx, exitAccount, newExit, err := s.processL2Tx(coordIdxsMap, collectedFees, exitTree, &l2txs[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.zki != nil {
|
||||
l2TxData, err := l2txs[i].L2Tx().Bytes(s.zki.Metadata.NLevels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.zki.Metadata.L2TxsData = append(s.zki.Metadata.L2TxsData, l2TxData)
|
||||
|
||||
if s.i < nTx-1 {
|
||||
// Intermediate States
|
||||
s.zki.ISOutIdx[s.i] = s.idx.BigInt()
|
||||
s.zki.ISStateRoot[s.i] = s.mt.Root().BigInt()
|
||||
s.zki.ISAccFeeOut[s.i] = formatAccumulatedFees(collectedFees, s.zki.FeePlanTokens)
|
||||
}
|
||||
if s.i == nTx-1 {
|
||||
s.zki.ISFinalAccFee = formatAccumulatedFees(collectedFees, s.zki.FeePlanTokens)
|
||||
}
|
||||
}
|
||||
if s.typ == TypeSynchronizer || s.typ == TypeBatchBuilder {
|
||||
if exitIdx != nil && exitTree != nil {
|
||||
exits[s.i] = processedExit{
|
||||
@@ -204,13 +249,11 @@ func (s *StateDB) ProcessTxs(ptc ProcessTxsConfig, coordIdxs []common.Idx, l1use
|
||||
}
|
||||
s.i++
|
||||
}
|
||||
}
|
||||
|
||||
if s.zki != nil {
|
||||
l2TxData, err := l2txs[i].L2Tx().Bytes(s.zki.Metadata.NLevels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.zki.Metadata.L2TxsData = append(s.zki.Metadata.L2TxsData, l2TxData)
|
||||
}
|
||||
// before computing the Fees txs, set the ISInitStateRootFee
|
||||
s.zki.ISInitStateRootFee = s.mt.Root().BigInt()
|
||||
}
|
||||
|
||||
// distribute the AccumulatedFees from the processed L2Txs into the
|
||||
@@ -242,6 +285,8 @@ func (s *StateDB) ProcessTxs(ptc ProcessTxsConfig, coordIdxs []common.Idx, l1use
|
||||
|
||||
// add Coord Idx to ZKInputs.FeeTxsData
|
||||
s.zki.FeeIdxs[iFee] = idx.BigInt()
|
||||
|
||||
s.zki.ISStateRootFee[iFee] = s.mt.Root().BigInt()
|
||||
}
|
||||
iFee++
|
||||
}
|
||||
@@ -293,6 +338,10 @@ func (s *StateDB) ProcessTxs(ptc ProcessTxsConfig, coordIdxs []common.Idx, l1use
|
||||
}
|
||||
s.zki.OldKey2[i] = p.OldKey.BigInt()
|
||||
s.zki.OldValue2[i] = p.OldValue.BigInt()
|
||||
|
||||
if i < nTx-1 {
|
||||
s.zki.ISExitRoot[i] = exitTree.Root().BigInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.typ == TypeSynchronizer {
|
||||
@@ -310,17 +359,9 @@ func (s *StateDB) ProcessTxs(ptc ProcessTxsConfig, coordIdxs []common.Idx, l1use
|
||||
// compute last ZKInputs parameters
|
||||
s.zki.GlobalChainID = big.NewInt(0) // TODO, 0: ethereum, this will be get from config file
|
||||
// zki.FeeIdxs = ? // TODO, this will be get from the config file
|
||||
tokenIDs, err := s.getTokenIDsBigInt(l1usertxs, l1coordinatortxs, l2txs)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
s.zki.FeePlanTokens = tokenIDs
|
||||
s.zki.Metadata.NewStateRootRaw = s.mt.Root()
|
||||
s.zki.Metadata.NewExitRootRaw = exitTree.Root()
|
||||
|
||||
// s.zki.ISInitStateRootFee = s.mt.Root().BigInt()
|
||||
|
||||
// return ZKInputs as the BatchBuilder will return it to forge the Batch
|
||||
return &ProcessTxOutput{
|
||||
ZKInputs: s.zki,
|
||||
@@ -331,15 +372,22 @@ func (s *StateDB) ProcessTxs(ptc ProcessTxsConfig, coordIdxs []common.Idx, l1use
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getTokenIDsBigInt returns the list of TokenIDs in *big.Int format
|
||||
func (s *StateDB) getTokenIDsBigInt(l1usertxs, l1coordinatortxs []common.L1Tx, l2txs []common.PoolL2Tx) ([]*big.Int, error) {
|
||||
// getFeePlanTokens returns an array of *big.Int containing a list of tokenIDs
|
||||
// corresponding to the given CoordIdxs and the processed L2Txs
|
||||
func (s *StateDB) getFeePlanTokens(coordIdxs []common.Idx, l2txs []common.PoolL2Tx) ([]*big.Int, error) {
|
||||
// get Coordinator TokenIDs corresponding to the Idxs where the Fees
|
||||
// will be sent
|
||||
coordTokenIDs := make(map[common.TokenID]bool)
|
||||
for i := 0; i < len(coordIdxs); i++ {
|
||||
acc, err := s.GetAccount(coordIdxs[i])
|
||||
if err != nil {
|
||||
log.Errorf("could not get account to determine TokenID of CoordIdx %d not found: %s", coordIdxs[i], err.Error())
|
||||
return nil, err
|
||||
}
|
||||
coordTokenIDs[acc.TokenID] = true
|
||||
}
|
||||
|
||||
tokenIDs := make(map[common.TokenID]bool)
|
||||
for i := 0; i < len(l1usertxs); i++ {
|
||||
tokenIDs[l1usertxs[i].TokenID] = true
|
||||
}
|
||||
for i := 0; i < len(l1coordinatortxs); i++ {
|
||||
tokenIDs[l1coordinatortxs[i].TokenID] = true
|
||||
}
|
||||
for i := 0; i < len(l2txs); i++ {
|
||||
// as L2Tx does not have parameter TokenID, get it from the
|
||||
// AccountsDB (in the StateDB)
|
||||
@@ -348,8 +396,10 @@ func (s *StateDB) getTokenIDsBigInt(l1usertxs, l1coordinatortxs []common.L1Tx, l
|
||||
log.Errorf("could not get account to determine TokenID of L2Tx: FromIdx %d not found: %s", l2txs[i].FromIdx, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := coordTokenIDs[acc.TokenID]; ok {
|
||||
tokenIDs[acc.TokenID] = true
|
||||
}
|
||||
}
|
||||
var tBI []*big.Int
|
||||
for t := range tokenIDs {
|
||||
tBI = append(tBI, t.BigInt())
|
||||
@@ -368,7 +418,12 @@ func (s *StateDB) processL1Tx(exitTree *merkletree.MerkleTree, tx *common.L1Tx)
|
||||
// ZKInputs
|
||||
if s.zki != nil {
|
||||
// Txs
|
||||
// s.zki.TxCompressedData[s.i] = tx.TxCompressedData() // uncomment once L1Tx.TxCompressedData is ready
|
||||
var err error
|
||||
s.zki.TxCompressedData[s.i], err = tx.TxCompressedData()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, nil, false, nil, err
|
||||
}
|
||||
s.zki.FromIdx[s.i] = tx.FromIdx.BigInt()
|
||||
s.zki.ToIdx[s.i] = tx.ToIdx.BigInt()
|
||||
s.zki.OnChain[s.i] = big.NewInt(1)
|
||||
@@ -408,11 +463,6 @@ func (s *StateDB) processL1Tx(exitTree *merkletree.MerkleTree, tx *common.L1Tx)
|
||||
// TODO applyCreateAccount will return the created account,
|
||||
// which in the case type==TypeSynchronizer will be added to an
|
||||
// array of created accounts that will be returned
|
||||
|
||||
if s.zki != nil {
|
||||
s.zki.AuxFromIdx[s.i] = s.idx.BigInt() // last s.idx is the one used for creating the new account
|
||||
s.zki.NewAccount[s.i] = big.NewInt(1)
|
||||
}
|
||||
case common.TxTypeDeposit:
|
||||
// update balance of the MT account
|
||||
err := s.applyDeposit(tx, false)
|
||||
@@ -436,11 +486,6 @@ func (s *StateDB) processL1Tx(exitTree *merkletree.MerkleTree, tx *common.L1Tx)
|
||||
log.Error(err)
|
||||
return nil, nil, false, nil, err
|
||||
}
|
||||
|
||||
if s.zki != nil {
|
||||
s.zki.AuxFromIdx[s.i] = s.idx.BigInt() // last s.idx is the one used for creating the new account
|
||||
s.zki.NewAccount[s.i] = big.NewInt(1)
|
||||
}
|
||||
case common.TxTypeForceExit:
|
||||
// execute exit flow
|
||||
// coordIdxsMap is 'nil', as at L1Txs there is no L2 fees
|
||||
@@ -485,8 +530,14 @@ func (s *StateDB) processL2Tx(coordIdxsMap map[common.TokenID]common.Idx, collec
|
||||
// ZKInputs
|
||||
if s.zki != nil {
|
||||
// Txs
|
||||
// s.zki.TxCompressedData[s.i] = tx.TxCompressedData() // uncomment once L1Tx.TxCompressedData is ready
|
||||
// s.zki.TxCompressedDataV2[s.i] = tx.TxCompressedDataV2() // uncomment once L2Tx.TxCompressedDataV2 is ready
|
||||
s.zki.TxCompressedData[s.i], err = tx.TxCompressedData()
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
s.zki.TxCompressedDataV2[s.i], err = tx.TxCompressedDataV2()
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
s.zki.FromIdx[s.i] = tx.FromIdx.BigInt()
|
||||
s.zki.ToIdx[s.i] = tx.ToIdx.BigInt()
|
||||
|
||||
@@ -587,6 +638,14 @@ func (s *StateDB) applyCreateAccount(tx *common.L1Tx) error {
|
||||
s.zki.OldValue1[s.i] = p.OldValue.BigInt()
|
||||
|
||||
s.zki.Metadata.NewLastIdxRaw = s.idx + 1
|
||||
|
||||
s.zki.AuxFromIdx[s.i] = common.Idx(s.idx + 1).BigInt()
|
||||
s.zki.NewAccount[s.i] = big.NewInt(1)
|
||||
|
||||
if s.i < len(s.zki.ISOnChain) { // len(s.zki.ISOnChain) == nTx
|
||||
// intermediate states
|
||||
s.zki.ISOnChain[s.i] = big.NewInt(1)
|
||||
}
|
||||
}
|
||||
|
||||
s.idx = s.idx + 1
|
||||
@@ -695,7 +754,7 @@ func (s *StateDB) applyTransfer(coordIdxsMap map[common.TokenID]common.Idx, coll
|
||||
accumulated := s.accumulatedFees[accCoord.Idx]
|
||||
accumulated.Add(accumulated, fee)
|
||||
|
||||
if s.typ == TypeSynchronizer {
|
||||
if s.typ == TypeSynchronizer || s.typ == TypeBatchBuilder {
|
||||
collected := collectedFees[accCoord.TokenID]
|
||||
collected.Add(collected, fee)
|
||||
}
|
||||
@@ -800,6 +859,12 @@ func (s *StateDB) applyCreateAccountDepositTransfer(tx *common.L1Tx) error {
|
||||
s.zki.OldValue1[s.i] = p.OldValue.BigInt()
|
||||
|
||||
s.zki.Metadata.NewLastIdxRaw = s.idx + 1
|
||||
|
||||
s.zki.AuxFromIdx[s.i] = common.Idx(s.idx + 1).BigInt()
|
||||
s.zki.NewAccount[s.i] = big.NewInt(1)
|
||||
|
||||
// intermediate states
|
||||
s.zki.ISOnChain[s.i] = big.NewInt(1)
|
||||
}
|
||||
|
||||
// update receiver account in localStateDB
|
||||
@@ -854,7 +919,7 @@ func (s *StateDB) applyExit(coordIdxsMap map[common.TokenID]common.Idx, collecte
|
||||
accumulated := s.accumulatedFees[accCoord.Idx]
|
||||
accumulated.Add(accumulated, fee)
|
||||
|
||||
if s.typ == TypeSynchronizer {
|
||||
if s.typ == TypeSynchronizer || s.typ == TypeBatchBuilder {
|
||||
collected := collectedFees[accCoord.TokenID]
|
||||
collected.Add(collected, fee)
|
||||
}
|
||||
|
||||
@@ -477,6 +477,80 @@ func TestProcessTxsRootTestVectors(t *testing.T) {
|
||||
assert.Equal(t, "9827704113668630072730115158977131501210702363656902211840117643154933433410", sdb.mt.Root().BigInt().String())
|
||||
}
|
||||
|
||||
func TestCircomTest(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "tmpdb")
|
||||
require.Nil(t, err)
|
||||
defer assert.Nil(t, os.RemoveAll(dir))
|
||||
|
||||
sdb, err := NewStateDB(dir, TypeBatchBuilder, 8)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// same values than in the js test
|
||||
bjj0, err := common.BJJFromStringWithChecksum("21b0a1688b37f77b1d1d5539ec3b826db5ac78b2513f574a04c50a7d4f8246d7")
|
||||
assert.Nil(t, err)
|
||||
l1Txs := []common.L1Tx{
|
||||
{
|
||||
FromIdx: 0,
|
||||
// LoadAmount: big.NewInt(10400),
|
||||
LoadAmount: big.NewInt(16000000),
|
||||
Amount: big.NewInt(0),
|
||||
TokenID: 1,
|
||||
FromBJJ: bjj0,
|
||||
FromEthAddr: ethCommon.HexToAddress("0x7e5f4552091a69125d5dfcb7b8c2659029395bdf"),
|
||||
ToIdx: 0,
|
||||
Type: common.TxTypeCreateAccountDeposit,
|
||||
},
|
||||
}
|
||||
l2Txs := []common.PoolL2Tx{
|
||||
{
|
||||
FromIdx: 256,
|
||||
ToIdx: 256,
|
||||
TokenID: 1,
|
||||
Amount: big.NewInt(1000),
|
||||
Nonce: 0,
|
||||
Fee: 126,
|
||||
Type: common.TxTypeTransfer,
|
||||
},
|
||||
}
|
||||
|
||||
ptc := ProcessTxsConfig{
|
||||
NLevels: 8,
|
||||
MaxFeeTx: 2,
|
||||
MaxTx: 5,
|
||||
MaxL1Tx: 2,
|
||||
}
|
||||
ptOut, err := sdb.ProcessTxs(ptc, nil, l1Txs, nil, l2Txs)
|
||||
require.Nil(t, err)
|
||||
|
||||
// check expected account keys values from tx inputs
|
||||
acc, err := sdb.GetAccount(common.Idx(256))
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "d746824f7d0ac5044a573f51b278acb56d823bec39551d1d7bf7378b68a1b021", acc.PublicKey.Compress().String())
|
||||
assert.Equal(t, "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf", acc.EthAddr.Hex())
|
||||
|
||||
// check that there no exist more accounts
|
||||
_, err = sdb.GetAccount(common.Idx(257))
|
||||
require.NotNil(t, err)
|
||||
ptOut.ZKInputs.FeeIdxs[0] = common.Idx(256).BigInt()
|
||||
|
||||
s, err := json.Marshal(ptOut.ZKInputs)
|
||||
require.Nil(t, err)
|
||||
debug := false
|
||||
if debug {
|
||||
fmt.Println("\nCopy&Paste into js circom test:\n let zkInput = JSON.parse(`" + string(s) + "`);")
|
||||
|
||||
h, err := ptOut.ZKInputs.HashGlobalData()
|
||||
require.Nil(t, err)
|
||||
fmt.Printf(`
|
||||
const output={
|
||||
hashGlobalInputs: "%s",
|
||||
};
|
||||
await circuit.assertOut(w, output);
|
||||
`, h.String())
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
|
||||
func TestZKInputsHashTestVector0(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "tmpdb")
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -160,3 +160,19 @@ func BJJCompressedTo256BigInts(pkComp babyjub.PublicKeyComp) [256]*big.Int {
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// formatAccumulatedFees returns an array of [nFeeAccounts]*big.Int containing
|
||||
// the balance of each FeeAccount, taken from the 'collectedFees' map, in the
|
||||
// order of the 'orderTokenIDs'
|
||||
func formatAccumulatedFees(collectedFees map[common.TokenID]*big.Int, orderTokenIDs []*big.Int) []*big.Int {
|
||||
accFeeOut := make([]*big.Int, len(orderTokenIDs))
|
||||
for i := 0; i < len(orderTokenIDs); i++ {
|
||||
tokenID := common.TokenIDFromBigInt(orderTokenIDs[i])
|
||||
if _, ok := collectedFees[tokenID]; ok {
|
||||
accFeeOut[i] = collectedFees[tokenID]
|
||||
} else {
|
||||
accFeeOut[i] = big.NewInt(0)
|
||||
}
|
||||
}
|
||||
return accFeeOut
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user