go-ethereum/internal/ethapi/api_test.go
Will Meister 499c6b5c36
Adds L1RollupTxId on Transaction and Transaction-related objects (#10)
* Adding L1RollupTxId field to Transactions
* Adding rollup transactions signing key config and bug fixing within api.go. Signing key and endpoint will be removed when go handles batch fetching
2020-07-31 08:55:48 -05:00

399 lines
14 KiB
Go

package ethapi
import (
"context"
"crypto/ecdsa"
"encoding/json"
"fmt"
"math/big"
"math/rand"
"testing"
"github.com/aws/aws-sdk-go/awstesting"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
var (
internalTxNonce = hexutil.Uint64(uint64(rand.Int()))
internalTxCalldata = hexutil.Bytes{0, 1, 2, 3, 4, 5, 6, 7}
internalTxSender = common.Address{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
internalTxTarget = common.Address{9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
backendTimestamp = int64(0)
)
type testCase struct {
backendContext backendContext
inputCtx context.Context
inputMessageAndSig []hexutil.Bytes
hasErrors bool
resultingTimestamp int64
multipleBatches bool
}
func getTestCases(pk *ecdsa.PrivateKey) []testCase {
return []testCase{
// Bad input -- message and sig not of length 2
{inputCtx: getFakeContext(), inputMessageAndSig: []hexutil.Bytes{}, hasErrors: true},
{inputCtx: getFakeContext(), inputMessageAndSig: []hexutil.Bytes{[]byte{1, 2, 3}}, hasErrors: true},
{inputCtx: getFakeContext(), inputMessageAndSig: []hexutil.Bytes{[]byte{1}, []byte{2}, []byte{3}}, hasErrors: true},
// Bad input -- message not signed
{inputCtx: getFakeContext(), inputMessageAndSig: []hexutil.Bytes{[]byte{1}, []byte{2}}, hasErrors: true},
// Bad input -- message is signed but incorrect format
{inputCtx: getFakeContext(), inputMessageAndSig: getInputMessageAndSignature([]byte{1}, pk), hasErrors: true},
// Returns 0 errors if no transactions but timestamp updated
{inputCtx: getFakeContext(), inputMessageAndSig: getRollupTransactionsInputAndSignature(pk, 0, 1, 0)},
{inputCtx: getFakeContext(), inputMessageAndSig: getRollupTransactionsInputAndSignature(pk, 1, 1, 0), resultingTimestamp: 1},
// Handles one transaction and updates timestamp
{inputCtx: getFakeContext(), inputMessageAndSig: getRollupTransactionsInputAndSignature(pk, 1, 1, 1), resultingTimestamp: 1},
{backendContext: backendContext{sendTxsErrors: getDummyErrors([]int{0}, 1)}, inputCtx: getFakeContext(), inputMessageAndSig: getRollupTransactionsInputAndSignature(pk, 1, 1, 1), hasErrors: true, resultingTimestamp: 1},
// Handles one batch of multiple transaction and updates timestamp
{inputCtx: getFakeContext(), inputMessageAndSig: getRollupTransactionsInputAndSignature(pk, 1, 1, 2), resultingTimestamp: 1},
{backendContext: backendContext{sendTxsErrors: getDummyErrors([]int{1}, 2)}, inputCtx: getFakeContext(), inputMessageAndSig: getRollupTransactionsInputAndSignature(pk, 1, 2, 2), hasErrors: true, resultingTimestamp: 1},
// Handles multiple transactions and updates timestamp
{inputCtx: getFakeContext(), inputMessageAndSig: getRollupTransactionsInputAndSignature(pk, 2, 1, 3), resultingTimestamp: 2},
{backendContext: backendContext{sendTxsErrors: getDummyErrors([]int{0, 2}, 3)}, inputCtx: getFakeContext(), inputMessageAndSig: getRollupTransactionsInputAndSignature(pk, 1, 1, 3), hasErrors: true, resultingTimestamp: 1, multipleBatches: true},
}
}
func TestSendRollupTransactions(t *testing.T) {
rollupTransactionsSender, _ := crypto.GenerateKey()
txSignerPrivKey, _ := crypto.GenerateKey()
for testNum, testCase := range getTestCases(rollupTransactionsSender) {
backendTimestamp = 0
api := getTestPublicTransactionPoolAPI(txSignerPrivKey, rollupTransactionsSender, testCase.backendContext)
res := api.SendRollupTransactions(testCase.inputCtx, testCase.inputMessageAndSig)
h := func(r []error) bool {
for _, e := range r {
if e != nil {
return true
}
}
return false
}
hasErrors := h(res)
// For debugging and verification:
fmt.Printf("test case %d had output errors: %v\n", testNum, res)
if testCase.hasErrors && !hasErrors {
t.Fatalf("test case %d expected output errors but did not result in any. Errors: %v", testNum, res)
}
if !testCase.hasErrors && hasErrors {
t.Fatalf("test case %d did not expect output errors but resulted in %d. Errors: %v", testNum, len(res), res)
}
if hasErrors && len(testCase.backendContext.sendTxsErrors) > 0 {
// Note: Cannot handle test cases with multiple batches the same way because errors are aggregated from the endpoint and not from sendTxsErrors
if testCase.multipleBatches {
errorCount := func(r []error) int {
c := 0
for _, e := range r {
if e != nil {
c++
}
}
return c
}
if errorCount(res) != errorCount(testCase.backendContext.sendTxsErrors) {
t.Fatalf("test case %d expected %d errors but resulted in %d", testNum, errorCount(res), errorCount(testCase.backendContext.sendTxsErrors))
}
} else {
if len(res) != len(testCase.backendContext.sendTxsErrors) {
t.Fatalf("test case %d expected %d output errors but received %d. Errors: %v", testNum, len(testCase.backendContext.sendTxsErrors), len(res), res)
}
for i, err := range res {
if err != nil && testCase.backendContext.sendTxsErrors[i] == nil {
t.Fatalf("test case %d had an error output mismatch. Received error at index %d when one wasn't expected. Expected output: %v, output: %v", testNum, i, testCase.backendContext.sendTxsErrors, res)
}
if err == nil && testCase.backendContext.sendTxsErrors[i] != nil {
t.Fatalf("test case %d had an error output mismatch. Did not receive an error at index %d when one was expected. Expected output: %v, output: %v", testNum, i, testCase.backendContext.sendTxsErrors, res)
}
}
}
}
if backendTimestamp != testCase.resultingTimestamp {
t.Fatalf("test case %d should have updated timestamp to %d but it was %d after execution.", testNum, testCase.resultingTimestamp, backendTimestamp)
}
}
}
func getDummyErrors(errorIndicies []int, outputSize int) []error {
errs := make([]error, outputSize)
for _, i := range errorIndicies {
errs[i] = fmt.Errorf("error %d", i)
}
return errs
}
func getRandomRollupTransaction() *RollupTransaction {
gasLimit := hexutil.Uint64(uint64(0))
l1RollupTxId := hexutil.Uint64(uint64(0))
return &RollupTransaction{
L1RollupTxId: &l1RollupTxId,
Nonce: &internalTxNonce,
GasLimit: &gasLimit,
Sender: &internalTxSender,
Target: &internalTxTarget,
Calldata: &internalTxCalldata,
}
}
func getRollupTransactionsInputAndSignature(privKey *ecdsa.PrivateKey, timestamp int64, blockNumber int, batchSize int) []hexutil.Bytes {
ts := hexutil.Uint64(uint64(timestamp))
blockNum := hexutil.Uint64(uint64(blockNumber))
rollupTransactions := make([]*RollupTransaction, batchSize)
for index := 0; index < batchSize; index++ {
rollupTransactions[index] = getRandomRollupTransaction()
}
bb := &GethSubmission{
Timestamp: &ts,
SubmissionNumber: &blockNum,
RollupTransactions: rollupTransactions,
}
message, _ := json.Marshal(bb)
return getInputMessageAndSignature(message, privKey)
}
func getInputMessageAndSignature(message []byte, privKey *ecdsa.PrivateKey) []hexutil.Bytes {
sig, _ := crypto.Sign(crypto.Keccak256(message), privKey)
return []hexutil.Bytes{message, sig}
}
func getFakeContext() context.Context {
return &awstesting.FakeContext{
Error: fmt.Errorf("fake error%s", "!"),
DoneCh: make(chan struct{}, 1),
}
}
func getTestPublicTransactionPoolAPI(txSignerPrivKey *ecdsa.PrivateKey, rollupTransactionsSender *ecdsa.PrivateKey, backendContext backendContext) *PublicTransactionPoolAPI {
address := crypto.PubkeyToAddress(rollupTransactionsSender.PublicKey)
backend := newMockBackend(&address, backendContext)
return NewPublicTransactionPoolAPI(backend, nil, txSignerPrivKey)
}
type backendContext struct {
currentBlockNumber int64
signerNonce uint64
sendTxsErrors []error
}
type mockBackend struct {
rollupTransactionSender *common.Address
testContext backendContext
timestamp int64
}
func newMockBackend(rollupTransactionSender *common.Address, backendContext backendContext) mockBackend {
return mockBackend{
rollupTransactionSender: rollupTransactionSender,
testContext: backendContext,
}
}
func (m mockBackend) Downloader() *downloader.Downloader {
panic("not implemented")
}
func (m mockBackend) ProtocolVersion() int {
panic("not implemented")
}
func (m mockBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
panic("not implemented")
}
func (m mockBackend) ChainDb() ethdb.Database {
panic("not implemented")
}
func (m mockBackend) AccountManager() *accounts.Manager {
panic("not implemented")
}
func (m mockBackend) ExtRPCEnabled() bool {
panic("not implemented")
}
func (m mockBackend) RPCGasCap() *big.Int {
panic("not implemented")
}
func (m mockBackend) SetHead(number uint64) {
panic("not implemented")
}
func (m mockBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
panic("not implemented")
}
func (m mockBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
panic("not implemented")
}
func (m mockBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
panic("not implemented")
}
func (m mockBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
panic("not implemented")
}
func (m mockBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
panic("not implemented")
}
func (m mockBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
panic("not implemented")
}
func (m mockBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
panic("not implemented")
}
func (m mockBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
panic("not implemented")
}
func (m mockBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
panic("not implemented")
}
func (m mockBackend) GetTd(hash common.Hash) *big.Int {
panic("not implemented")
}
func (m mockBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) {
panic("not implemented")
}
func (m mockBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
panic("not implemented")
}
func (m mockBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
panic("not implemented")
}
func (m mockBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
panic("not implemented")
}
func (m mockBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
panic("not implemented")
}
func (m mockBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
panic("not implemented")
}
func (m mockBackend) GetPoolTransactions() (types.Transactions, error) {
panic("not implemented")
}
func (m mockBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction {
panic("not implemented")
}
func (m mockBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
return m.testContext.signerNonce, nil
}
func (m mockBackend) Stats() (pending int, queued int) {
panic("not implemented")
}
func (m mockBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) {
panic("not implemented")
}
func (m mockBackend) SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription {
panic("not implemented")
}
func (m mockBackend) BloomStatus() (uint64, uint64) {
panic("not implemented")
}
func (m mockBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) {
panic("not implemented")
}
func (m mockBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
panic("not implemented")
}
func (m mockBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
panic("not implemented")
}
func (m mockBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
panic("not implemented")
}
func (m mockBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
panic("not implemented")
}
func (m mockBackend) SendTxs(ctx context.Context, signedTxs []*types.Transaction) []error {
if len(m.testContext.sendTxsErrors) == 0 || len(m.testContext.sendTxsErrors) != len(signedTxs) {
return make([]error, len(signedTxs))
}
return m.testContext.sendTxsErrors
}
func (m mockBackend) SetTimestamp(timestamp int64) {
backendTimestamp = timestamp
}
func (m mockBackend) ChainConfig() *params.ChainConfig {
return &params.ChainConfig{}
}
func (m mockBackend) RollupTransactionSender() *common.Address {
return m.rollupTransactionSender
}
func (m mockBackend) CurrentBlock() *types.Block {
header := &types.Header{
ParentHash: common.Hash{},
UncleHash: common.Hash{},
Coinbase: common.Address{},
Root: common.Hash{},
TxHash: common.Hash{},
ReceiptHash: common.Hash{},
Bloom: types.Bloom{},
Difficulty: nil,
Number: big.NewInt(m.testContext.currentBlockNumber),
GasLimit: 0,
GasUsed: 0,
Time: 0,
Extra: nil,
MixDigest: common.Hash{},
Nonce: types.BlockNonce{},
}
return types.NewBlock(header, []*types.Transaction{}, []*types.Header{}, []*types.Receipt{})
}