Browse Source

Add StateDB & LocalStateDB

feature/sql-semaphore1
arnaucube 3 years ago
parent
commit
d8cb7298e0
10 changed files with 715 additions and 324 deletions
  1. +21
    -23
      batchbuilder/batchbuilder.go
  2. +7
    -7
      batchbuilder/state.go
  3. +121
    -6
      common/account.go
  4. +196
    -0
      common/account_test.go
  5. +0
    -128
      common/leaf.go
  6. +0
    -158
      common/leaf_test.go
  7. +215
    -0
      db/statedb/statedb.go
  8. +144
    -0
      db/statedb/statedb_test.go
  9. +2
    -2
      go.mod
  10. +9
    -0
      go.sum

+ 21
- 23
batchbuilder/batchbuilder.go

@ -3,7 +3,6 @@ package batchbuilder
import (
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/hermeznetwork/hermez-node/common"
"github.com/iden3/go-iden3-crypto/babyjub"
"github.com/iden3/go-merkletree"
"github.com/iden3/go-merkletree/db"
"github.com/iden3/go-merkletree/db/memory"
@ -73,7 +72,7 @@ func (bb *BatchBuilder) BuildBatch(configBatch ConfigBatch, l1usertxs, l1coordin
for _, tx := range l2txs {
switch tx.Type {
case common.TxTypeTransfer:
// go to the MT leaf of sender and receiver, and update
// go to the MT account of sender and receiver, and update
// balance & nonce
err := bb.applyTransfer(tx.Tx)
if err != nil {
@ -91,33 +90,33 @@ func (bb *BatchBuilder) BuildBatch(configBatch ConfigBatch, l1usertxs, l1coordin
func (bb *BatchBuilder) processL1Tx(tx common.L1Tx) error {
switch tx.Type {
case common.TxTypeForceTransfer, common.TxTypeTransfer:
// go to the MT leaf of sender and receiver, and update balance
// go to the MT account of sender and receiver, and update balance
// & nonce
err := bb.applyTransfer(tx.Tx)
if err != nil {
return err
}
case common.TxTypeCreateAccountDeposit:
// add new leaf to the MT, update balance of the MT leaf
// add new account to the MT, update balance of the MT account
err := bb.applyCreateLeaf(tx)
if err != nil {
return err
}
case common.TxTypeDeposit:
// update balance of the MT leaf
case common.TxTypeDeposit: // TODO check if this type will ever exist, or will be TxTypeDepositAndTransfer with transfer 0 value
// update balance of the MT account
err := bb.applyDeposit(tx, false)
if err != nil {
return err
}
case common.TxTypeDepositAndTransfer:
// update balance in MT leaf, update balance & nonce of sender
// update balance in MT account, update balance & nonce of sender
// & receiver
err := bb.applyDeposit(tx, true)
if err != nil {
return err
}
case common.TxTypeCreateAccountDepositAndTransfer:
// add new leaf to the merkletree, update balance in MT leaf,
// add new account to the merkletree, update balance in MT account,
// update balance & nonce of sender & receiver
err := bb.applyCreateLeaf(tx)
if err != nil {
@ -135,19 +134,18 @@ func (bb *BatchBuilder) processL1Tx(tx common.L1Tx) error {
return nil
}
// applyCreateLeaf creates a new leaf in the leaf of the depositer, it stores
// applyCreateLeaf creates a new account in the account of the depositer, it stores
// the deposit value
func (bb *BatchBuilder) applyCreateLeaf(tx common.L1Tx) error {
leaf := common.Leaf{
TokenID: tx.TokenID,
Nonce: 0, // TODO check w spec: always that a new leaf is created nonce is at 0
Balance: tx.LoadAmount,
Sign: babyjub.PointCoordSign(tx.FromBJJ.X),
Ay: tx.FromBJJ.Y,
EthAddr: tx.FromEthAddr,
account := common.Account{
TokenID: tx.TokenID,
Nonce: 0,
Balance: tx.LoadAmount,
PublicKey: &tx.FromBJJ,
EthAddr: tx.FromEthAddr,
}
v, err := leaf.HashValue()
v, err := account.HashValue()
if err != nil {
return err
}
@ -156,15 +154,15 @@ func (bb *BatchBuilder) applyCreateLeaf(tx common.L1Tx) error {
return err
}
err = bb.CreateBalance(dbTx, common.Idx(bb.idx+1), leaf)
err = bb.CreateBalance(dbTx, common.Idx(bb.idx+1), account)
if err != nil {
return err
}
leafBytes, err := leaf.Bytes()
accountBytes, err := account.Bytes()
if err != nil {
return err
}
dbTx.Put(v.Bytes(), leafBytes[:])
dbTx.Put(v.Bytes(), accountBytes[:])
// if everything is fine, do dbTx & increment idx
if err := dbTx.Commit(); err != nil {
@ -174,7 +172,7 @@ func (bb *BatchBuilder) applyCreateLeaf(tx common.L1Tx) error {
return nil
}
// applyDeposit updates the balance in the leaf of the depositer, if andTransfer parameter is set to true, the method will also apply the Transfer of the L1Tx/DepositAndTransfer
// applyDeposit updates the balance in the account of the depositer, if andTransfer parameter is set to true, the method will also apply the Transfer of the L1Tx/DepositAndTransfer
func (bb *BatchBuilder) applyDeposit(tx common.L1Tx, andTransfer bool) error {
dbTx, err := bb.mt.DB().NewTx()
if err != nil {
@ -206,8 +204,8 @@ func (bb *BatchBuilder) applyDeposit(tx common.L1Tx, andTransfer bool) error {
return nil
}
// applyTransfer updates the balance & nonce in the leaf of the sender, and the
// balance in the leaf of the receiver
// applyTransfer updates the balance & nonce in the account of the sender, and the
// balance in the account of the receiver
func (bb *BatchBuilder) applyTransfer(tx common.Tx) error {
dbTx, err := bb.mt.DB().NewTx()
if err != nil {

+ 7
- 7
batchbuilder/state.go

@ -10,7 +10,7 @@ import (
// TODO next iteration move the methods of this file into StateDB, which Synchronizer will use in the disk DB, and BatchBuilder will use with the MemoryDB
// GetBalance returns the balance for a given Idx from the DB
func (bb *BatchBuilder) GetBalance(tx db.Tx, idx common.Idx) (*common.Leaf, error) {
func (bb *BatchBuilder) GetBalance(tx db.Tx, idx common.Idx) (*common.Account, error) {
idxBytes := idx.Bytes()
vBytes, err := tx.Get(idxBytes[:])
if err != nil {
@ -18,15 +18,15 @@ func (bb *BatchBuilder) GetBalance(tx db.Tx, idx common.Idx) (*common.Leaf, erro
}
var b [32 * common.NLEAFELEMS]byte
copy(b[:], vBytes)
leaf, err := common.LeafFromBytes(b)
leaf, err := common.AccountFromBytes(b)
if err != nil {
return nil, err
}
return leaf, nil
}
// CreateBalance stores the Leaf into the Idx position in the MerkleTree, also adds db entry for the Leaf value
func (bb *BatchBuilder) CreateBalance(tx db.Tx, idx common.Idx, leaf common.Leaf) error {
// CreateBalance stores the Account into the Idx position in the MerkleTree, also adds db entry for the Account value
func (bb *BatchBuilder) CreateBalance(tx db.Tx, idx common.Idx, leaf common.Account) error {
// store at the DB the key: v, and value: leaf.Bytes()
v, err := leaf.HashValue()
if err != nil {
@ -37,7 +37,7 @@ func (bb *BatchBuilder) CreateBalance(tx db.Tx, idx common.Idx, leaf common.Leaf
return err
}
// store the Leaf value
// store the Account value
tx.Put(v.Bytes(), leafBytes[:])
// Add k & v into the MT
err = bb.mt.Add(idx.BigInt(), v)
@ -73,10 +73,10 @@ func (bb *BatchBuilder) UpdateBalance(tx db.Tx, idx common.Idx, amount *big.Int,
return err
}
// store the Leaf value
// store the Account value
tx.Put(v.Bytes(), leafBytes[:])
// Add k & v into the MT
err = bb.mt.Update(idx.BigInt(), v)
_, err = bb.mt.Update(idx.BigInt(), v)
if err != nil {
return err
}

+ 121
- 6
common/account.go

@ -1,18 +1,133 @@
package common
import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
eth "github.com/ethereum/go-ethereum/common"
"github.com/iden3/go-iden3-crypto/babyjub"
"github.com/iden3/go-iden3-crypto/poseidon"
cryptoUtils "github.com/iden3/go-iden3-crypto/utils"
)
// Account is a struct that gives information of the holdings of an address for a specific token
const NLEAFELEMS = 4
// Account is a struct that gives information of the holdings of an address for a specific token. Is the data structure that generates the Value stored in the leaf of the MerkleTree
type Account struct {
TokenID TokenID
Nonce uint64 // max of 40 bits used
Balance *big.Int // max of 192 bits used
PublicKey *babyjub.PublicKey
EthAddr eth.Address
TokenID TokenID // effective 32 bits
Idx uint32 // bits = SMT levels (SMT levels needs to be decided)
Nonce uint64 // effective 48 bits
Balance *big.Int // Up to 192 bits
PublicKey babyjub.PublicKey
}
// 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) {
var b [32 * NLEAFELEMS]byte
if l.Nonce > 0xffffffffff {
return b, fmt.Errorf("%s Nonce", ErrNumOverflow)
}
if len(l.Balance.Bytes()) > 24 {
return b, fmt.Errorf("%s Balance", ErrNumOverflow)
}
var tokenIDBytes [4]byte
binary.LittleEndian.PutUint32(tokenIDBytes[:], uint32(l.TokenID))
var nonceBytes [8]byte
binary.LittleEndian.PutUint64(nonceBytes[:], l.Nonce)
copy(b[0:4], tokenIDBytes[:])
copy(b[4:9], nonceBytes[:])
if babyjub.PointCoordSign(l.PublicKey.X) {
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())
return b, nil
}
// BigInts returns the [5]*big.Int, where each *big.Int is inside the Finite Field
func (l *Account) BigInts() ([NLEAFELEMS]*big.Int, error) {
e := [NLEAFELEMS]*big.Int{}
b, err := l.Bytes()
if err != nil {
return e, err
}
e[0] = new(big.Int).SetBytes(SwapEndianness(b[0:32]))
e[1] = new(big.Int).SetBytes(SwapEndianness(b[32:64]))
e[2] = new(big.Int).SetBytes(SwapEndianness(b[64:96]))
e[3] = new(big.Int).SetBytes(SwapEndianness(b[96:128]))
return e, nil
}
// HashValue returns the value of the Account, which is the Poseidon hash of its *big.Int representation
func (l *Account) HashValue() (*big.Int, error) {
b0 := big.NewInt(0)
toHash := [poseidon.T]*big.Int{b0, b0, b0, b0, b0, b0}
lBI, err := l.BigInts()
if err != nil {
return nil, err
}
copy(toHash[:], lBI[:])
v, err := poseidon.Hash(toHash)
return v, err
}
// AccountFromBigInts returns a Account from a [5]*big.Int
func AccountFromBigInts(e [NLEAFELEMS]*big.Int) (*Account, error) {
if !cryptoUtils.CheckBigIntArrayInField(e[:]) {
return nil, ErrNotInFF
}
var b [32 * NLEAFELEMS]byte
copy(b[0:32], SwapEndianness(e[0].Bytes())) // SwapEndianness, as big.Int uses BigEndian
copy(b[32:64], SwapEndianness(e[1].Bytes()))
copy(b[64:96], SwapEndianness(e[2].Bytes()))
copy(b[96:128], SwapEndianness(e[3].Bytes()))
return AccountFromBytes(b)
}
// AccountFromBytes returns a Account from a byte array
func AccountFromBytes(b [32 * NLEAFELEMS]byte) (*Account, error) {
tokenID := binary.LittleEndian.Uint32(b[0:4])
var nonceBytes [8]byte
copy(nonceBytes[:], b[4:9])
nonce := binary.LittleEndian.Uint64(nonceBytes[:])
sign := b[10] == 1
balance := new(big.Int).SetBytes(SwapEndianness(b[32:56])) // b[32:56], as Balance is 192 bits (24 bytes)
if !bytes.Equal(b[56:64], []byte{0, 0, 0, 0, 0, 0, 0, 0}) {
return nil, fmt.Errorf("%s Balance", ErrNumOverflow)
}
ay := new(big.Int).SetBytes(SwapEndianness(b[64:96]))
pkPoint, err := babyjub.PointFromSignAndY(sign, ay)
if err != nil {
return nil, err
}
publicKey := babyjub.PublicKey(*pkPoint)
ethAddr := eth.BytesToAddress(b[96:116])
if !cryptoUtils.CheckBigIntInField(balance) {
return nil, ErrNotInFF
}
if !cryptoUtils.CheckBigIntInField(ay) {
return nil, ErrNotInFF
}
l := Account{
TokenID: TokenID(tokenID),
Nonce: nonce,
Balance: balance,
PublicKey: &publicKey,
EthAddr: ethAddr,
}
return &l, nil
}

+ 196
- 0
common/account_test.go

@ -0,0 +1,196 @@
package common
import (
"encoding/hex"
"fmt"
"math"
"math/big"
"testing"
ethCommon "github.com/ethereum/go-ethereum/common"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/iden3/go-iden3-crypto/babyjub"
cryptoConstants "github.com/iden3/go-iden3-crypto/constants"
cryptoUtils "github.com/iden3/go-iden3-crypto/utils"
"github.com/stretchr/testify/assert"
)
func TestAccount(t *testing.T) {
var sk babyjub.PrivateKey
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
assert.Nil(t, err)
pk := sk.Public()
account := &Account{
TokenID: TokenID(1),
Nonce: uint64(1234),
Balance: big.NewInt(1000),
PublicKey: pk,
EthAddr: ethCommon.HexToAddress("0xc58d29fA6e86E4FAe04DDcEd660d45BCf3Cb2370"),
}
b, err := account.Bytes()
assert.Nil(t, err)
assert.Equal(t, byte(1), b[10])
a1, err := AccountFromBytes(b)
assert.Nil(t, err)
assert.Equal(t, account, a1)
e, err := account.BigInts()
assert.Nil(t, err)
assert.True(t, cryptoUtils.CheckBigIntInField(e[0]))
assert.True(t, cryptoUtils.CheckBigIntInField(e[1]))
assert.True(t, cryptoUtils.CheckBigIntInField(e[2]))
assert.True(t, cryptoUtils.CheckBigIntInField(e[3]))
assert.Equal(t, "1000", e[1].String())
assert.Equal(t, pk.Y.String(), e[2].String())
assert.Equal(t, new(big.Int).SetBytes(SwapEndianness(account.EthAddr.Bytes())).String(), e[3].String())
a2, err := AccountFromBigInts(e)
assert.Nil(t, err)
assert.Equal(t, account, a2)
assert.Equal(t, a1, a2)
}
func TestAccountLoop(t *testing.T) {
// check that for different Address there is no problem
for i := 0; i < 256; i++ {
var sk babyjub.PrivateKey
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
assert.Nil(t, err)
pk := sk.Public()
key, err := ethCrypto.GenerateKey()
assert.Nil(t, err)
address := ethCrypto.PubkeyToAddress(key.PublicKey)
account := &Account{
TokenID: TokenID(i),
Nonce: uint64(i),
Balance: big.NewInt(1000),
PublicKey: pk,
EthAddr: address,
}
b, err := account.Bytes()
assert.Nil(t, err)
a1, err := AccountFromBytes(b)
assert.Nil(t, err)
assert.Equal(t, account, a1)
e, err := account.BigInts()
assert.Nil(t, err)
assert.True(t, cryptoUtils.CheckBigIntInField(e[0]))
assert.True(t, cryptoUtils.CheckBigIntInField(e[1]))
assert.True(t, cryptoUtils.CheckBigIntInField(e[2]))
assert.True(t, cryptoUtils.CheckBigIntInField(e[3]))
a2, err := AccountFromBigInts(e)
assert.Nil(t, err)
assert.Equal(t, account, a2)
}
}
func TestAccountHashValue(t *testing.T) {
var sk babyjub.PrivateKey
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
assert.Nil(t, err)
pk := sk.Public()
account := &Account{
TokenID: TokenID(1),
Nonce: uint64(1234),
Balance: big.NewInt(1000),
PublicKey: pk,
EthAddr: ethCommon.HexToAddress("0xc58d29fA6e86E4FAe04DDcEd660d45BCf3Cb2370"),
}
v, err := account.HashValue()
assert.Nil(t, err)
assert.Equal(t, "6335844662301214382338419199835935731871537354006112711277201708185593574314", v.String())
}
func TestAccountErrNotInFF(t *testing.T) {
z := big.NewInt(0)
// Q-1 should not give error
r := new(big.Int).Sub(cryptoConstants.Q, big.NewInt(1))
e := [NLEAFELEMS]*big.Int{z, z, r, r}
_, err := AccountFromBigInts(e)
assert.Nil(t, err)
// Q should give error
r = cryptoConstants.Q
e = [NLEAFELEMS]*big.Int{z, z, r, r}
_, err = AccountFromBigInts(e)
assert.NotNil(t, err)
assert.Equal(t, ErrNotInFF, err)
// Q+1 should give error
r = new(big.Int).Add(cryptoConstants.Q, big.NewInt(1))
e = [NLEAFELEMS]*big.Int{z, z, r, r}
_, err = AccountFromBigInts(e)
assert.NotNil(t, err)
assert.Equal(t, ErrNotInFF, err)
}
func TestAccountErrNumOverflowNonce(t *testing.T) {
var sk babyjub.PrivateKey
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
assert.Nil(t, err)
pk := sk.Public()
// check limit
account := &Account{
TokenID: TokenID(1),
Nonce: uint64(math.Pow(2, 40) - 1),
Balance: big.NewInt(1000),
PublicKey: pk,
EthAddr: ethCommon.HexToAddress("0xc58d29fA6e86E4FAe04DDcEd660d45BCf3Cb2370"),
}
_, err = account.Bytes()
assert.Nil(t, err)
// force value overflow
account.Nonce = uint64(math.Pow(2, 40))
b, err := account.Bytes()
assert.NotNil(t, err)
assert.Equal(t, fmt.Errorf("%s Nonce", ErrNumOverflow), err)
_, err = AccountFromBytes(b)
assert.Nil(t, err)
}
func TestAccountErrNumOverflowBalance(t *testing.T) {
var sk babyjub.PrivateKey
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
assert.Nil(t, err)
pk := sk.Public()
// check limit
account := &Account{
TokenID: TokenID(1),
Nonce: uint64(math.Pow(2, 40) - 1),
Balance: new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(192), nil), big.NewInt(1)),
PublicKey: pk,
EthAddr: ethCommon.HexToAddress("0xc58d29fA6e86E4FAe04DDcEd660d45BCf3Cb2370"),
}
assert.Equal(t, "6277101735386680763835789423207666416102355444464034512895", account.Balance.String())
_, err = account.Bytes()
assert.Nil(t, err)
// force value overflow
account.Balance = new(big.Int).Exp(big.NewInt(2), big.NewInt(192), nil)
assert.Equal(t, "6277101735386680763835789423207666416102355444464034512896", account.Balance.String())
b, err := account.Bytes()
assert.NotNil(t, err)
assert.Equal(t, fmt.Errorf("%s Balance", ErrNumOverflow), err)
_, err = AccountFromBytes(b)
assert.Nil(t, err)
b[56] = 1
_, err = AccountFromBytes(b)
assert.NotNil(t, err)
assert.Equal(t, fmt.Errorf("%s Balance", ErrNumOverflow), err)
}

+ 0
- 128
common/leaf.go

@ -1,128 +0,0 @@
package common
import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
eth "github.com/ethereum/go-ethereum/common"
"github.com/iden3/go-iden3-crypto/poseidon"
cryptoUtils "github.com/iden3/go-iden3-crypto/utils"
)
const NLEAFELEMS = 4
// Leaf is the data structure stored in the Leaf of the MerkleTree
type Leaf struct {
TokenID TokenID
Nonce uint64 // max of 40 bits used
Balance *big.Int // max of 192 bits used
Sign bool
Ay *big.Int
EthAddr eth.Address
}
// Bytes returns the bytes representing the Leaf, 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 *Leaf) Bytes() ([32 * NLEAFELEMS]byte, error) {
var b [32 * NLEAFELEMS]byte
if l.Nonce > 0xffffffffff {
return b, fmt.Errorf("%s Nonce", ErrNumOverflow)
}
if len(l.Balance.Bytes()) > 24 {
return b, fmt.Errorf("%s Balance", ErrNumOverflow)
}
var tokenIDBytes [4]byte
binary.LittleEndian.PutUint32(tokenIDBytes[:], uint32(l.TokenID))
var nonceBytes [8]byte
binary.LittleEndian.PutUint64(nonceBytes[:], l.Nonce)
copy(b[0:4], tokenIDBytes[:])
copy(b[4:9], nonceBytes[:])
if l.Sign {
b[10] = 1
}
copy(b[32:64], SwapEndianness(l.Balance.Bytes())) // SwapEndianness, as big.Int uses BigEndian
copy(b[64:96], SwapEndianness(l.Ay.Bytes()))
copy(b[96:116], l.EthAddr.Bytes())
return b, nil
}
// BigInts returns the [5]*big.Int, where each *big.Int is inside the Finite Field
func (l *Leaf) BigInts() ([NLEAFELEMS]*big.Int, error) {
e := [NLEAFELEMS]*big.Int{}
b, err := l.Bytes()
if err != nil {
return e, err
}
e[0] = new(big.Int).SetBytes(SwapEndianness(b[0:32]))
e[1] = new(big.Int).SetBytes(SwapEndianness(b[32:64]))
e[2] = new(big.Int).SetBytes(SwapEndianness(b[64:96]))
e[3] = new(big.Int).SetBytes(SwapEndianness(b[96:128]))
return e, nil
}
// HashValue returns the value of the Leaf, which is the Poseidon hash of its *big.Int representation
func (l *Leaf) HashValue() (*big.Int, error) {
toHash := [poseidon.T]*big.Int{}
lBI, err := l.BigInts()
if err != nil {
return nil, err
}
copy(toHash[:], lBI[:])
v, err := poseidon.Hash(toHash)
return v, err
}
// LeafFromBigInts returns a Leaf from a [5]*big.Int
func LeafFromBigInts(e [NLEAFELEMS]*big.Int) (*Leaf, error) {
if !cryptoUtils.CheckBigIntArrayInField(e[:]) {
return nil, ErrNotInFF
}
var b [32 * NLEAFELEMS]byte
copy(b[0:32], SwapEndianness(e[0].Bytes())) // SwapEndianness, as big.Int uses BigEndian
copy(b[32:64], SwapEndianness(e[1].Bytes()))
copy(b[64:96], SwapEndianness(e[2].Bytes()))
copy(b[96:128], SwapEndianness(e[3].Bytes()))
return LeafFromBytes(b)
}
// LeafFromBytes returns a Leaf from a byte array
func LeafFromBytes(b [32 * NLEAFELEMS]byte) (*Leaf, error) {
tokenID := binary.LittleEndian.Uint32(b[0:4])
var nonceBytes [8]byte
copy(nonceBytes[:], b[4:9])
nonce := binary.LittleEndian.Uint64(nonceBytes[:])
sign := b[10] == 1
balance := new(big.Int).SetBytes(SwapEndianness(b[32:56])) // b[32:56], as Balance is 192 bits (24 bytes)
if !bytes.Equal(b[56:64], []byte{0, 0, 0, 0, 0, 0, 0, 0}) {
return nil, fmt.Errorf("%s Balance", ErrNumOverflow)
}
ay := new(big.Int).SetBytes(SwapEndianness(b[64:96]))
ethAddr := eth.BytesToAddress(b[96:116])
if !cryptoUtils.CheckBigIntInField(balance) {
return nil, ErrNotInFF
}
if !cryptoUtils.CheckBigIntInField(ay) {
return nil, ErrNotInFF
}
l := Leaf{
TokenID: TokenID(tokenID),
Nonce: nonce,
Balance: balance,
Sign: sign,
Ay: ay,
EthAddr: ethAddr,
}
return &l, nil
}

+ 0
- 158
common/leaf_test.go

@ -1,158 +0,0 @@
package common
import (
"fmt"
"math"
"math/big"
"testing"
ethCommon "github.com/ethereum/go-ethereum/common"
cryptoConstants "github.com/iden3/go-iden3-crypto/constants"
cryptoUtils "github.com/iden3/go-iden3-crypto/utils"
"github.com/stretchr/testify/assert"
)
func TestLeaf(t *testing.T) {
leaf := &Leaf{
TokenID: TokenID(1),
Nonce: uint64(1234),
Balance: big.NewInt(1000),
Sign: true,
Ay: big.NewInt(6789),
EthAddr: ethCommon.HexToAddress("0xc58d29fA6e86E4FAe04DDcEd660d45BCf3Cb2370"),
}
b, err := leaf.Bytes()
assert.Nil(t, err)
assert.Equal(t, byte(1), b[10])
l1, err := LeafFromBytes(b)
assert.Nil(t, err)
assert.Equal(t, leaf, l1)
e, err := leaf.BigInts()
assert.Nil(t, err)
assert.True(t, cryptoUtils.CheckBigIntInField(e[0]))
assert.True(t, cryptoUtils.CheckBigIntInField(e[1]))
assert.True(t, cryptoUtils.CheckBigIntInField(e[2]))
assert.True(t, cryptoUtils.CheckBigIntInField(e[3]))
assert.Equal(t, "1000", e[1].String())
assert.Equal(t, "6789", e[2].String())
assert.Equal(t, new(big.Int).SetBytes(SwapEndianness(leaf.EthAddr.Bytes())).String(), e[3].String())
l2, err := LeafFromBigInts(e)
assert.Nil(t, err)
assert.Equal(t, leaf, l2)
assert.Equal(t, l1, l2)
}
// func TestLeafLoop(t *testing.T) {
// // check that for different Address there is no problem
// for i := 0; i < 256; i++ {
// key, err := ethCrypto.GenerateKey()
// assert.Nil(t, err)
// address := ethCrypto.PubkeyToAddress(key.PublicKey)
//
// leaf := &Leaf{
// TokenID: TokenID(i),
// Nonce: uint64(i),
// Balance: big.NewInt(1000),
// Sign: true,
// Ay: big.NewInt(6789),
// EthAddr: address,
// }
// b, err := leaf.Bytes()
// assert.Nil(t, err)
// l1, err := LeafFromBytes(b)
// assert.Nil(t, err)
// assert.Equal(t, leaf, l1)
//
// e, err := leaf.BigInts()
// assert.Nil(t, err)
// assert.True(t, cryptoUtils.CheckBigIntInField(e[0]))
// assert.True(t, cryptoUtils.CheckBigIntInField(e[1]))
// assert.True(t, cryptoUtils.CheckBigIntInField(e[2]))
// assert.True(t, cryptoUtils.CheckBigIntInField(e[3]))
//
// l2, err := LeafFromBigInts(e)
// assert.Nil(t, err)
// assert.Equal(t, leaf, l2)
// }
// }
func TestLeafErrNotInFF(t *testing.T) {
z := big.NewInt(0)
// Q-1 should not give error
r := new(big.Int).Sub(cryptoConstants.Q, big.NewInt(1))
e := [NLEAFELEMS]*big.Int{z, z, r, r}
_, err := LeafFromBigInts(e)
assert.Nil(t, err)
// Q should give error
r = cryptoConstants.Q
e = [NLEAFELEMS]*big.Int{z, z, r, r}
_, err = LeafFromBigInts(e)
assert.NotNil(t, err)
assert.Equal(t, ErrNotInFF, err)
// Q+1 should give error
r = new(big.Int).Add(cryptoConstants.Q, big.NewInt(1))
e = [NLEAFELEMS]*big.Int{z, z, r, r}
_, err = LeafFromBigInts(e)
assert.NotNil(t, err)
assert.Equal(t, ErrNotInFF, err)
}
func TestLeafErrNumOverflowNonce(t *testing.T) {
// check limit
leaf := &Leaf{
TokenID: TokenID(1),
Nonce: uint64(math.Pow(2, 40) - 1),
Balance: big.NewInt(1000),
Sign: true,
Ay: big.NewInt(6789),
EthAddr: ethCommon.HexToAddress("0xc58d29fA6e86E4FAe04DDcEd660d45BCf3Cb2370"),
}
_, err := leaf.Bytes()
assert.Nil(t, err)
// force value overflow
leaf.Nonce = uint64(math.Pow(2, 40))
b, err := leaf.Bytes()
assert.NotNil(t, err)
assert.Equal(t, fmt.Errorf("%s Nonce", ErrNumOverflow), err)
_, err = LeafFromBytes(b)
assert.Nil(t, err)
}
func TestLeafErrNumOverflowBalance(t *testing.T) {
// check limit
leaf := &Leaf{
TokenID: TokenID(1),
Nonce: uint64(math.Pow(2, 40) - 1),
Balance: new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(192), nil), big.NewInt(1)),
Sign: true,
Ay: big.NewInt(6789),
EthAddr: ethCommon.HexToAddress("0xc58d29fA6e86E4FAe04DDcEd660d45BCf3Cb2370"),
}
assert.Equal(t, "6277101735386680763835789423207666416102355444464034512895", leaf.Balance.String())
_, err := leaf.Bytes()
assert.Nil(t, err)
// force value overflow
leaf.Balance = new(big.Int).Exp(big.NewInt(2), big.NewInt(192), nil)
assert.Equal(t, "6277101735386680763835789423207666416102355444464034512896", leaf.Balance.String())
b, err := leaf.Bytes()
assert.NotNil(t, err)
assert.Equal(t, fmt.Errorf("%s Balance", ErrNumOverflow), err)
_, err = LeafFromBytes(b)
assert.Nil(t, err)
b[56] = 1
_, err = LeafFromBytes(b)
assert.NotNil(t, err)
assert.Equal(t, fmt.Errorf("%s Balance", ErrNumOverflow), err)
}

+ 215
- 0
db/statedb/statedb.go

@ -0,0 +1,215 @@
package statedb
import (
"errors"
"github.com/hermeznetwork/hermez-node/common"
"github.com/iden3/go-merkletree"
"github.com/iden3/go-merkletree/db"
"github.com/iden3/go-merkletree/db/leveldb"
"github.com/iden3/go-merkletree/db/memory"
)
// ErrStateDBWithoutMT is used when a method that requires a MerkleTree is called in a StateDB that does not have a MerkleTree defined
var ErrStateDBWithoutMT = errors.New("Can not call method to use MerkleTree in a StateDB without MerkleTree")
// ErrAccountAlreadyExists is used when CreateAccount is called and the Account already exists
var ErrAccountAlreadyExists = errors.New("Can not CreateAccount because Account already exists")
// StateDB represents the StateDB object
type StateDB struct {
db db.Storage
mt *merkletree.MerkleTree
}
// NewStateDB creates a new StateDB, allowing to use an in-memory or in-disk
// storage
func NewStateDB(path string, inDisk bool, withMT bool, nLevels int) (*StateDB, error) {
var sto db.Storage
var err error
if inDisk {
sto, err = leveldb.NewLevelDbStorage(path, false)
if err != nil {
return nil, err
}
} else {
sto = memory.NewMemoryStorage()
}
var mt *merkletree.MerkleTree = nil
if withMT {
mt, err = merkletree.NewMerkleTree(sto, nLevels)
if err != nil {
return nil, err
}
}
return &StateDB{
db: sto,
mt: mt,
}, nil
}
// CheckPointAt does a checkpoint at the given batchNum in the defined path
func (s *StateDB) CheckPointAt(batchNum int, path string) error {
// TODO
return nil
}
// Reset resets the StateDB to the checkpoint at the given batchNum
func (s *StateDB) Reset(batchNum int) error {
// TODO
return nil
}
// Checkpoints returns a list of the checkpoints (batchNums)
func (s *StateDB) Checkpoints() ([]int, error) {
// TODO
//batchnums, err
return nil, nil
}
// GetAccount returns the account for the given Idx
func (s *StateDB) GetAccount(idx common.Idx) (*common.Account, error) {
vBytes, err := s.db.Get(idx.Bytes())
if err != nil {
return nil, err
}
accBytes, err := s.db.Get(vBytes)
if err != nil {
return nil, err
}
var b [32 * common.NLEAFELEMS]byte
copy(b[:], accBytes)
return common.AccountFromBytes(b)
}
// CreateAccount creates a new Account in the StateDB for the given Idx.
// MerkleTree is not affected.
func (s *StateDB) CreateAccount(idx common.Idx, account *common.Account) error {
// store at the DB the key: v, and value: leaf.Bytes()
v, err := account.HashValue()
if err != nil {
return err
}
accountBytes, err := account.Bytes()
if err != nil {
return err
}
// store the Leaf value
tx, err := s.db.NewTx()
if err != nil {
return err
}
_, err = tx.Get(idx.Bytes())
if err != db.ErrNotFound {
return ErrAccountAlreadyExists
}
tx.Put(v.Bytes(), accountBytes[:])
tx.Put(idx.Bytes(), v.Bytes())
return tx.Commit()
}
// UpdateAccount updates the Account in the StateDB for the given Idx.
// MerkleTree is not affected.
func (s *StateDB) UpdateAccount(idx common.Idx, account *common.Account) error {
// store at the DB the key: v, and value: leaf.Bytes()
v, err := account.HashValue()
if err != nil {
return err
}
accountBytes, err := account.Bytes()
if err != nil {
return err
}
tx, err := s.db.NewTx()
if err != nil {
return err
}
tx.Put(v.Bytes(), accountBytes[:])
tx.Put(idx.Bytes(), v.Bytes())
return tx.Commit()
}
// MTCreateAccount creates a new the Account in the StateDB for the given Idx,
// and updates the MerkleTree, returning a CircomProcessorProof
func (s *StateDB) MTCreateAccount(idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
if s.mt == nil {
return nil, ErrStateDBWithoutMT
}
err := s.CreateAccount(idx, account)
if err != nil {
return nil, err
}
v, err := account.HashValue() // already computed in s.CreateAccount, next iteration reuse first computation
if err != nil {
return nil, err
}
// Add k & v into the MT
return s.mt.AddAndGetCircomProof(idx.BigInt(), v)
}
// MTUpdateAccount updates the Account in the StateDB for the given Idx, and
// updates the MerkleTree, returning a CircomProcessorProof
func (s *StateDB) MTUpdateAccount(idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
if s.mt == nil {
return nil, ErrStateDBWithoutMT
}
err := s.UpdateAccount(idx, account)
if err != nil {
return nil, err
}
v, err := account.HashValue() // already computed in s.CreateAccount, next iteration reuse first computation
if err != nil {
return nil, err
}
// Add k & v into the MT
return s.mt.Update(idx.BigInt(), v)
}
// MTGetProof returns the CircomVerifierProof for a given Idx
func (s *StateDB) MTGetProof(idx common.Idx) (*merkletree.CircomVerifierProof, error) {
if s.mt == nil {
return nil, ErrStateDBWithoutMT
}
return s.mt.GenerateCircomVerifierProof(idx.BigInt(), s.mt.Root())
}
// LocalStateDB represents the local StateDB which allows to make copies from
// the synchronizer StateDB, and is used by the tx-selector and the
// batch-builder. LocalStateDB is an in-memory storage.
type LocalStateDB struct {
*StateDB
synchronizerStateDB *StateDB
}
// NewLocalStateDB returns a new LocalStateDB connected to the given
// synchronizerDB
func NewLocalStateDB(synchronizerDB *StateDB, withMT bool, nLevels int) (*LocalStateDB, error) {
s, err := NewStateDB("", false, withMT, nLevels)
if err != nil {
return nil, err
}
return &LocalStateDB{
s,
synchronizerDB,
}, nil
}
// Reset performs a reset, getting the state from
// LocalStateDB.synchronizerStateDB for the given batchNum
func (l *LocalStateDB) Reset(batchNum int, fromSynchronizer bool) error {
// TODO
return nil
}

+ 144
- 0
db/statedb/statedb_test.go

@ -0,0 +1,144 @@
package statedb
import (
"encoding/hex"
"io/ioutil"
"math/big"
"testing"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/hermeznetwork/hermez-node/common"
"github.com/iden3/go-iden3-crypto/babyjub"
"github.com/iden3/go-merkletree/db"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newAccount(t *testing.T, i int) *common.Account {
var sk babyjub.PrivateKey
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
require.Nil(t, err)
pk := sk.Public()
key, err := ethCrypto.GenerateKey()
require.Nil(t, err)
address := ethCrypto.PubkeyToAddress(key.PublicKey)
return &common.Account{
TokenID: common.TokenID(i),
Nonce: uint64(i),
Balance: big.NewInt(1000),
PublicKey: pk,
EthAddr: address,
}
}
func TestStateDBWithoutMT(t *testing.T) {
dir, err := ioutil.TempDir("", "tmpdb")
require.Nil(t, err)
sdb, err := NewStateDB(dir, false, false, 0)
assert.Nil(t, err)
// create test accounts
var accounts []*common.Account
for i := 0; i < 100; i++ {
accounts = append(accounts, newAccount(t, i))
}
// get non-existing account, expecting an error
_, err = sdb.GetAccount(common.Idx(1))
assert.NotNil(t, err)
assert.Equal(t, db.ErrNotFound, err)
// add test accounts
for i := 0; i < len(accounts); i++ {
err = sdb.CreateAccount(common.Idx(i), accounts[i])
assert.Nil(t, err)
}
for i := 0; i < len(accounts); i++ {
accGetted, err := sdb.GetAccount(common.Idx(i))
assert.Nil(t, err)
assert.Equal(t, accounts[i], accGetted)
}
// try already existing idx and get error
_, err = sdb.GetAccount(common.Idx(1)) // check that exist
assert.Nil(t, err)
err = sdb.CreateAccount(common.Idx(1), accounts[1]) // check that can not be created twice
assert.NotNil(t, err)
assert.Equal(t, ErrAccountAlreadyExists, err)
// update accounts
for i := 0; i < len(accounts); i++ {
accounts[i].Nonce = accounts[i].Nonce + 1
err = sdb.UpdateAccount(common.Idx(i), accounts[i])
assert.Nil(t, err)
}
// check that can not call MerkleTree methods of the StateDB
_, err = sdb.MTCreateAccount(common.Idx(1), accounts[1])
assert.NotNil(t, err)
assert.Equal(t, ErrStateDBWithoutMT, err)
_, err = sdb.MTUpdateAccount(common.Idx(1), accounts[1])
assert.NotNil(t, err)
assert.Equal(t, ErrStateDBWithoutMT, err)
_, err = sdb.MTGetProof(common.Idx(1))
assert.NotNil(t, err)
assert.Equal(t, ErrStateDBWithoutMT, err)
}
func TestStateDBWithMT(t *testing.T) {
dir, err := ioutil.TempDir("", "tmpdb")
require.Nil(t, err)
sdb, err := NewStateDB(dir, false, true, 32)
assert.Nil(t, err)
// create test accounts
var accounts []*common.Account
for i := 0; i < 100; i++ {
accounts = append(accounts, newAccount(t, i))
}
// get non-existing account, expecting an error
_, err = sdb.GetAccount(common.Idx(1))
assert.NotNil(t, err)
assert.Equal(t, db.ErrNotFound, err)
// add test accounts
for i := 0; i < len(accounts); i++ {
_, err = sdb.MTCreateAccount(common.Idx(i), accounts[i])
assert.Nil(t, err)
}
for i := 0; i < len(accounts); i++ {
accGetted, err := sdb.GetAccount(common.Idx(i))
assert.Nil(t, err)
assert.Equal(t, accounts[i], accGetted)
}
// try already existing idx and get error
_, err = sdb.GetAccount(common.Idx(1)) // check that exist
assert.Nil(t, err)
_, err = sdb.MTCreateAccount(common.Idx(1), accounts[1]) // check that can not be created twice
assert.NotNil(t, err)
assert.Equal(t, ErrAccountAlreadyExists, err)
_, err = sdb.MTGetProof(common.Idx(1))
assert.Nil(t, err)
// update accounts
for i := 0; i < len(accounts); i++ {
accounts[i].Nonce = accounts[i].Nonce + 1
_, err = sdb.MTUpdateAccount(common.Idx(i), accounts[i])
assert.Nil(t, err)
}
a, err := sdb.GetAccount(common.Idx(1)) // check that account value has been updated
assert.Nil(t, err)
assert.Equal(t, accounts[1].Nonce, a.Nonce)
}

+ 2
- 2
go.mod

@ -5,8 +5,8 @@ go 1.14
require (
github.com/dghubble/sling v1.3.0
github.com/ethereum/go-ethereum v1.9.17
github.com/iden3/go-iden3-crypto v0.0.6-0.20200723082457-29a66457f0bf
github.com/iden3/go-merkletree v0.0.0-20200723202738-75e24244a1e3
github.com/iden3/go-iden3-crypto v0.0.6-0.20200806115047-327a8175d6eb
github.com/iden3/go-merkletree v0.0.0-20200807083900-f6f82d8375d5
github.com/jinzhu/gorm v1.9.15
github.com/stretchr/testify v1.6.1
)

+ 9
- 0
go.sum

@ -109,6 +109,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 h1:lMm2hD9Fy0ynom5+85/pbdkiYcBqM1JWmhpAXLmy0fw=
github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -135,8 +136,14 @@ github.com/iden3/go-iden3-crypto v0.0.5 h1:inCSm5a+ry+nbpVTL/9+m6UcIwSv6nhUm0tnI
github.com/iden3/go-iden3-crypto v0.0.5/go.mod h1:XKw1oDwYn2CIxKOtr7m/mL5jMn4mLOxAxtZBRxQBev8=
github.com/iden3/go-iden3-crypto v0.0.6-0.20200723082457-29a66457f0bf h1:/7L5dEqctuzJY2g8OEQct+1Y+n2sMKyd4JoYhw2jy1s=
github.com/iden3/go-iden3-crypto v0.0.6-0.20200723082457-29a66457f0bf/go.mod h1:XKw1oDwYn2CIxKOtr7m/mL5jMn4mLOxAxtZBRxQBev8=
github.com/iden3/go-iden3-crypto v0.0.6-0.20200806115047-327a8175d6eb h1:4Vqq5ZoqQd9t3Uj7MUbak7eHmtaYs8mqQoo8T1DS7tA=
github.com/iden3/go-iden3-crypto v0.0.6-0.20200806115047-327a8175d6eb/go.mod h1:XKw1oDwYn2CIxKOtr7m/mL5jMn4mLOxAxtZBRxQBev8=
github.com/iden3/go-merkletree v0.0.0-20200723202738-75e24244a1e3 h1:QR6LqG1HqqPE4myiLR73QFIieAfwODG3bqo1juuaqVI=
github.com/iden3/go-merkletree v0.0.0-20200723202738-75e24244a1e3/go.mod h1:Fc49UeywIsj8nUfb5lxBzmWrMeMmqzTJ5F0OcjdiEME=
github.com/iden3/go-merkletree v0.0.0-20200806171216-dd600560e44c h1:EzVMSVkwKdfcOR1a+rZe9dfbtAj6KdJnBxOEZ5B+gCQ=
github.com/iden3/go-merkletree v0.0.0-20200806171216-dd600560e44c/go.mod h1:Fc49UeywIsj8nUfb5lxBzmWrMeMmqzTJ5F0OcjdiEME=
github.com/iden3/go-merkletree v0.0.0-20200807083900-f6f82d8375d5 h1:qvWSCt3AYxj65uTdW6lLSKlrbckcHghOAW4TwdfJ+N8=
github.com/iden3/go-merkletree v0.0.0-20200807083900-f6f82d8375d5/go.mod h1:Fc49UeywIsj8nUfb5lxBzmWrMeMmqzTJ5F0OcjdiEME=
github.com/iden3/go-wasm3 v0.0.1/go.mod h1:j+TcAB94Dfrjlu5kJt83h2OqAU+oyNUTwNZnQyII1sI=
github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
github.com/influxdata/influxdb v1.7.8/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
@ -225,6 +232,7 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@ -241,6 +249,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs=
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=

Loading…
Cancel
Save