499c6b5c36
* 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
399 lines
14 KiB
Go
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 ¶ms.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{})
|
|
}
|