Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: tx fuzz testing #311

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions module/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
PACKAGES=$(shell go list ./... | grep -v '/simulation')
Copy link
Author

@ChristianBorst ChristianBorst Aug 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation mentions that in order to get access to the beta fuzz feature you need to install gotip and use that in place of the go command:

$ go get golang.org/dl/gotip
$ gotip download dev.fuzz

PACKAGES=$(shell gotip list ./... | grep -v '/simulation')
VERSION := $(shell git describe --abbrev=6 --dirty --always --tags)
COMMIT := $(shell git log -1 --format='%H')
DOCKER := $(shell which docker)
Expand Down Expand Up @@ -50,20 +50,20 @@ BUILD_FLAGS := -ldflags '$(ldflags)' -gcflags="all=-N -l"
all: install

install: go.sum
go install $(BUILD_FLAGS) ./cmd/gravity
gotip install $(BUILD_FLAGS) ./cmd/gravity

go.sum: go.mod
@echo "--> Ensure dependencies have not been modified"
GO111MODULE=on go mod verify
GO111MODULE=on gotip mod verify

test:
@go test -mod=readonly $(PACKAGES)
@gotip test -mod=readonly $(PACKAGES)

# look into .golangci.yml for enabling / disabling linters
lint:
@echo "--> Running linter"
@golangci-lint run
@go mod verify
@gotip mod verify

###############################################################################
### Protobuf ###
Expand Down Expand Up @@ -161,7 +161,7 @@ proto-tools-stamp:
rm -f ${PROTOC_ZIP})

echo "Installing protoc-gen-gocosmos..."
go install github.com/regen-network/cosmos-proto/protoc-gen-gocosmos
gotip install github.com/regen-network/cosmos-proto/protoc-gen-gocosmos

# Create dummy file to satisfy dependency and avoid
# rebuilding when this Makefile target is hit twice
Expand Down
3 changes: 3 additions & 0 deletions module/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ go 1.15

require (
github.com/cosmos/cosmos-sdk v0.42.1
github.com/cosmos/iavl v0.15.3
github.com/ethereum/go-ethereum v1.10.3
github.com/gogo/protobuf v1.3.3
github.com/golang/protobuf v1.4.3
github.com/google/gofuzz v1.2.0 // indirect
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
github.com/pkg/errors v0.9.1
github.com/rakyll/statik v0.1.7
github.com/spf13/cast v1.3.1
Expand Down
3 changes: 2 additions & 1 deletion module/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,9 @@ github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64=
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
Expand Down
101 changes: 101 additions & 0 deletions module/x/gravity/keeper/pool_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// +build gofuzzbeta

package keeper

import (
"encoding/binary"
"fmt"
"math"
"math/big"
"testing"

Expand Down Expand Up @@ -79,6 +84,102 @@ func TestAddToOutgoingPool(t *testing.T) {
assert.Equal(t, exp, got)
}

func createSeedUint64ByteArrayWithValue(numElts int, value uint64) []byte {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fuzzing currently doesn't support typed arrays, only byte arrays. These methods make it easy to "seed" the fuzzer with example data to work off of

numBytes := numElts * 64
arr := make([]byte, numBytes)
for i := 0; i < numElts; i++ {
binary.PutUvarint(arr[64*i:64*(i+1)], value)
}
return arr
}

func deserializeByteArrayToUint64Array(bytes []byte, numElts int) []uint64 {
uints := make([]uint64, numElts)

for j := 0; j < numElts; j++ {
uints[j] = binary.BigEndian.Uint64(bytes[64*j : 64*(j+1)])
}
return uints
}

func FuzzAddToOutgoingPool(f *testing.F) {
numInputs := 6
ones := createSeedUint64ByteArrayWithValue(numInputs, uint64(1))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create some example uint64 arrays. I wanted to start out with fees all equal to 1, and amounts all equal to 100

oneHundreds := createSeedUint64ByteArrayWithValue(numInputs, uint64(100))

f.Add(ones, oneHundreds, "0000000000000000000000000000000000000000")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Add function creates a single point of seed input into the fuzzer. I don't fully understand how it will use the seed data, but I'm guessing it just speeds up the time it takes to randomly generate acceptable inputs

f.Fuzz(func(t *testing.T, feez []byte, amountz []byte, contractAddr string) {
if types.ValidateEthAddress(contractAddr) != nil {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plenty of the input strings were too short, if the fuzzer generates a bad string then we just skip with t.Skip()

t.Skip()
}
fees := deserializeByteArrayToUint64Array(feez, numInputs)
amounts := deserializeByteArrayToUint64Array(amountz, numInputs)
for j := 0; j < numInputs; j++ {
fees[j] = binary.BigEndian.Uint64(feez[64*j : 64*(j+1)])
amounts[j] = binary.BigEndian.Uint64(amountz[64*j : 64*(j+1)])
}

input := CreateTestEnv(t)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From this point onward this test is basically a copy of the TestAddToOutgoingPool test above it

ctx := input.Context
var (
mySender, _ = sdk.AccAddressFromBech32("cosmos1ahx7f8wyertuus9r20284ej0asrs085case3kn")
myReceiver = "0xd041c41EA1bf0F006ADBb6d2c9ef9D425dE5eaD7"
myTokenContractAddr = "0x" + contractAddr
)

// mint some voucher first
var balance uint64 = math.MaxUint64
allVouchers := sdk.Coins{types.NewERC20Token(balance, myTokenContractAddr).GravityCoin()}
err := input.BankKeeper.MintCoins(ctx, types.ModuleName, allVouchers)
if err != nil {
t.Skip()
}

// set senders balance
input.AccountKeeper.NewAccountWithAddress(ctx, mySender)
err = input.BankKeeper.SetBalances(ctx, mySender, allVouchers)
if err != nil {
t.Skip()
}

transacts := make([]*types.OutgoingTransferTx, len(fees))
// create transactions
for i, _ := range fees {
txAmt := amounts[i]
txFee := fees[i]
if uint64(txAmt+txFee) >= balance {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the account doesn't have enough balance (starts with uint64 max) to cover this transaction, we modify the amount and fee to make the transaction work out. Later on we check that there are a correct number of transactions created so I didn't want to just skip.

txAmt = uint64(balance / 2)
txFee = uint64(txAmt / 2)
}
amount := types.NewERC20Token(txAmt, myTokenContractAddr).GravityCoin()
fee := types.NewERC20Token(txFee, myTokenContractAddr).GravityCoin()
transacts[i] = &types.OutgoingTransferTx{
Id: uint64(i + 1),
Sender: mySender.String(),
DestAddress: myReceiver,
Erc20Token: types.NewSDKIntERC20Token(amount.Amount, amount.Denom),
Erc20Fee: types.NewSDKIntERC20Token(fee.Amount, fee.Denom),
}
r, err := input.GravityKeeper.AddToOutgoingPool(ctx, mySender, myReceiver, amount, fee)
balance = balance - uint64(txAmt+txFee)
require.NoError(t, err)
t.Logf("___ response: %#v", r)
// Should create:
// 1: amount 100, fee 2
// 2: amount 101, fee 3
// 3: amount 102, fee 2
// 4: amount 103, fee 1

}

got := input.GravityKeeper.GetUnbatchedTransactionsByContract(ctx, myTokenContractAddr)
if len(got) != len(fees) {
t.Fatal(fmt.Errorf("generated transactions do not match ones received\nexpected: %v\nreceived: %v", transacts, got))
}
})

}

func TestTotalBatchFeeInPool(t *testing.T) {
input := CreateTestEnv(t)
ctx := input.Context
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
go test fuzz v1
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file was generated automatically when I ran gotip test -fuzz=FuzzAddToOutgoingPool in the terminal. This is somehow related to the fuzzer's seed method f.Add I mentioned above, but I'm not sure how it gets generated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably be git-ignored

[]byte("\x01")
[]byte("d")
string("0")