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

core: add new sponsored transaction type #372

Merged
merged 3 commits into from
Nov 14, 2023
Merged
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
4 changes: 4 additions & 0 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,10 @@ func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
func (m callMsg) Data() []byte { return m.CallMsg.Data }
func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }

// FIXME: support sponsored transaction in callMsg
func (m callMsg) Payer() common.Address { return m.CallMsg.From }
func (m callMsg) ExpiredTime() uint64 { return 0 }

// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
type filterBackend struct {
Expand Down
269 changes: 268 additions & 1 deletion core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package core
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"io/ioutil"
"math/big"
"math/rand"
Expand All @@ -28,6 +27,9 @@ import (
"testing"
"time"

"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/eth/tracers/logger"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/ethash"
Expand Down Expand Up @@ -3337,3 +3339,268 @@ func TestEIP1559Transition(t *testing.T) {
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
}
}

func TestSponsoredTxTransitionBeforeMiko(t *testing.T) {
var chainConfig params.ChainConfig

chainConfig.HomesteadBlock = common.Big0
chainConfig.EIP150Block = common.Big0
chainConfig.EIP155Block = common.Big0
chainConfig.ChainID = big.NewInt(2020)

engine := ethash.NewFaker()
db := rawdb.NewMemoryDatabase()

recipient := common.HexToAddress("1000000000000000000000000000000000000001")
senderKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
payerKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}

gspec := &Genesis{
Config: &chainConfig,
}
genesis := gspec.MustCommit(db)
chain, err := NewBlockChain(db, nil, &chainConfig, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to create blockchain, err %s", err)
}
defer chain.Stop()
mikoSigner := types.NewMikoSigner(big.NewInt(2020))

innerTx := types.SponsoredTx{
ChainID: big.NewInt(2020),
Nonce: 1,
GasTipCap: big.NewInt(100000),
GasFeeCap: big.NewInt(100000),
Gas: 1000,
To: &recipient,
Value: big.NewInt(10),
ExpiredTime: 1000,
}

innerTx.PayerR, innerTx.PayerS, innerTx.PayerV, err = types.PayerSign(
payerKey,
mikoSigner,
crypto.PubkeyToAddress(senderKey.PublicKey),
&innerTx,
)
if err != nil {
t.Fatalf("Payer fails to sign transaction, err %s", err)
}

tx, err := types.SignNewTx(senderKey, mikoSigner, &innerTx)
if err != nil {
t.Fatalf("Fail to sign transaction, err %s", err)
}

block := GenerateBadBlock(genesis, engine, types.Transactions{tx}, &chainConfig)
_, err = chain.InsertChain(types.Blocks{block})
want := fmt.Errorf("could not apply tx %d [%v]: %w", 0, tx.Hash().String(), ErrTxTypeNotSupported)
if err == nil || err.Error() != want.Error() {
t.Fatalf("Expect error %s, get %s", want, err)
}
}

func TestSponsoredTxTransition(t *testing.T) {
var chainConfig params.ChainConfig

chainConfig.HomesteadBlock = common.Big0
chainConfig.EIP150Block = common.Big0
chainConfig.EIP155Block = common.Big0
chainConfig.MikoBlock = common.Big0
chainConfig.ChainID = big.NewInt(2020)

engine := ethash.NewFaker()
db := rawdb.NewMemoryDatabase()

recipient := common.HexToAddress("1000000000000000000000000000000000000001")
senderKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
senderAddr := crypto.PubkeyToAddress(senderKey.PublicKey)
payerKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
payerAddr := crypto.PubkeyToAddress(payerKey.PublicKey)
adminKey, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
adminAddr := crypto.PubkeyToAddress(adminKey.PublicKey)

gspec := &Genesis{
Config: &chainConfig,
Timestamp: 2000,
Alloc: GenesisAlloc{
adminAddr: {Balance: math.BigPow(10, 18)},
},
}
genesis := gspec.MustCommit(db)
chain, err := NewBlockChain(db, nil, &chainConfig, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to create blockchain, err %s", err)
}
defer chain.Stop()
mikoSigner := types.NewMikoSigner(big.NewInt(2020))

// 1. Same payer and sender in sponsored tx
innerTx := types.SponsoredTx{
ChainID: big.NewInt(2020),
Nonce: 0,
GasTipCap: big.NewInt(100000),
GasFeeCap: big.NewInt(100000),
Gas: 30000,
To: &recipient,
Value: big.NewInt(10),
ExpiredTime: 1000,
}

innerTx.PayerR, innerTx.PayerS, innerTx.PayerV, err = types.PayerSign(
payerKey,
mikoSigner,
crypto.PubkeyToAddress(payerKey.PublicKey),
&innerTx,
)
if err != nil {
t.Fatalf("Payer fails to sign transaction, err %s", err)
}

sponsoredTx, err := types.SignNewTx(payerKey, mikoSigner, &innerTx)
if err != nil {
t.Fatalf("Fail to sign transaction, err %s", err)
}

block := GenerateBadBlock(genesis, engine, types.Transactions{sponsoredTx}, &chainConfig)
_, err = chain.InsertChain(types.Blocks{block})
if err == nil || !errors.Is(err, types.ErrSamePayerSenderSponsoredTx) {
t.Fatalf("Expect error %s, get %s", types.ErrSamePayerSenderSponsoredTx, err)
}

// 2. Expired sponsored tx

innerTx.PayerR, innerTx.PayerS, innerTx.PayerV, err = types.PayerSign(
payerKey,
mikoSigner,
crypto.PubkeyToAddress(senderKey.PublicKey),
&innerTx,
)
if err != nil {
t.Fatalf("Payer fails to sign transaction, err %s", err)
}

sponsoredTx, err = types.SignNewTx(senderKey, mikoSigner, &innerTx)
if err != nil {
t.Fatalf("Fail to sign transaction, err %s", err)
}

block = GenerateBadBlock(genesis, engine, types.Transactions{sponsoredTx}, &chainConfig)
_, err = chain.InsertChain(types.Blocks{block})
if err == nil || !errors.Is(err, ErrExpiredSponsoredTx) {
t.Fatalf("Expect error %s, get %s", ErrExpiredSponsoredTx, err)
}

// 3. Gas tip cap and gas fee cap are different
innerTx.ExpiredTime = 3000
innerTx.GasTipCap = new(big.Int).Add(innerTx.GasFeeCap, common.Big1)
innerTx.PayerR, innerTx.PayerS, innerTx.PayerV, err = types.PayerSign(
payerKey,
mikoSigner,
crypto.PubkeyToAddress(senderKey.PublicKey),
&innerTx,
)
if err != nil {
t.Fatalf("Payer fails to sign transaction, err %s", err)
}

sponsoredTx, err = types.SignNewTx(senderKey, mikoSigner, &innerTx)
if err != nil {
t.Fatalf("Fail to sign transaction, err %s", err)
}

block = GenerateBadBlock(genesis, engine, types.Transactions{sponsoredTx}, &chainConfig)
_, err = chain.InsertChain(types.Blocks{block})
if err == nil || !errors.Is(err, ErrDifferentFeeCapTipCap) {
t.Fatalf("Expect error %s, get %s", ErrDifferentFeeCapTipCap, err)
}

// 4. Payer does not have sufficient fund
innerTx.GasTipCap = innerTx.GasFeeCap
innerTx.PayerR, innerTx.PayerS, innerTx.PayerV, err = types.PayerSign(
payerKey,
mikoSigner,
crypto.PubkeyToAddress(senderKey.PublicKey),
&innerTx,
)
if err != nil {
t.Fatalf("Payer fails to sign transaction, err %s", err)
}

sponsoredTx, err = types.SignNewTx(senderKey, mikoSigner, &innerTx)
if err != nil {
t.Fatalf("Fail to sign transaction, err %s", err)
}

block = GenerateBadBlock(genesis, engine, types.Transactions{sponsoredTx}, &chainConfig)
_, err = chain.InsertChain(types.Blocks{block})
if err == nil || !errors.Is(err, ErrInsufficientPayerFunds) {
t.Fatalf("Expect error %s, get %s", ErrInsufficientPayerFunds, err)
}

// 5. Sender does not have sufficient fund
gasFee := new(big.Int).Mul(innerTx.GasFeeCap, new(big.Int).SetUint64(innerTx.Gas))
blocks, _ := GenerateChain(&chainConfig, genesis, engine, db, 1, func(i int, bg *BlockGen) {
tx, err := types.SignTx(types.NewTransaction(0, payerAddr, gasFee, params.TxGas, bg.header.BaseFee, nil), mikoSigner, adminKey)
if err != nil {
t.Fatal(err)
}

bg.AddTx(tx)
}, true)
_, err = chain.InsertChain(blocks)
if err != nil {
t.Fatalf("Failed to insert blocks, err %s", err)
}

block = GenerateBadBlock(blocks[0], engine, types.Transactions{sponsoredTx}, &chainConfig)
_, err = chain.InsertChain(types.Blocks{block})
if err == nil || !errors.Is(err, ErrInsufficientSenderFunds) {
t.Fatalf("Expect error %s, get %s", ErrInsufficientSenderFunds, err)
}

// 5. Successfully add tx
blocks, _ = GenerateChain(&chainConfig, blocks[0], engine, db, 1, func(i int, bg *BlockGen) {
tx, err := types.SignTx(types.NewTransaction(1, senderAddr, innerTx.Value, params.TxGas, bg.header.BaseFee, nil), mikoSigner, adminKey)
if err != nil {
t.Fatal(err)
}

bg.AddTx(tx)
bg.AddTx(sponsoredTx)
}, true)
_, err = chain.InsertChain(blocks)
if err != nil {
t.Fatalf("Failed to insert blocks, err %s", err)
}

statedb, _ := chain.State()
// Check sender's balance after sponsored tx
have := statedb.GetBalance(senderAddr)
want := common.Big0
if have.Cmp(want) != 0 {
t.Fatalf("Expect sender's balance %d, get %d", want.Uint64(), have.Uint64())
}
// Check payer's balance after sponsored tx
// Transfer tx costs 21000 gas so 9000 gas is refunded
want = new(big.Int).Mul(innerTx.GasFeeCap, big.NewInt(9000))
have = statedb.GetBalance(payerAddr)
if have.Cmp(want) != 0 {
t.Fatalf("Expect sender's balance %d, get %d", want.Uint64(), have.Uint64())
}
}
15 changes: 15 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,19 @@ var (

// ErrSenderNoEOA is returned if the sender of a transaction is a contract.
ErrSenderNoEOA = errors.New("sender not an eoa")

// ErrExpiredSponsoredTx is returned if the sponsored transaction is expired.
ErrExpiredSponsoredTx = errors.New("sponsored transaction is expired")

// ErrInsufficientPayerFunds is returned if the gas fee cost of executing a transaction
// is higher than the balance of the payer's account.
ErrInsufficientPayerFunds = errors.New("insufficient payer funds for gas * price")

// ErrInsufficientSenderFunds is returned if the value in transaction
// is higher than the balance of the user's account.
ErrInsufficientSenderFunds = errors.New("insufficient sender funds for value")

// ErrDifferentFeeCapTipCap is returned if fee cap and tip cap are different
// when dynamic gas fee is not supported
ErrDifferentFeeCapTipCap = errors.New("gas fee cap and gas tip cap are different")
)
5 changes: 4 additions & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,12 @@ func applyTransaction(
from := msg.From()

// Check if sender and recipient are blacklisted
payer := msg.Payer()
if config.Consortium != nil && config.IsOdysseus(blockNumber) {
contractAddr := config.BlacklistContractAddress
if state.IsAddressBlacklisted(statedb, contractAddr, &from) || state.IsAddressBlacklisted(statedb, contractAddr, msg.To()) {
if state.IsAddressBlacklisted(statedb, contractAddr, &from) ||
state.IsAddressBlacklisted(statedb, contractAddr, msg.To()) ||
state.IsAddressBlacklisted(statedb, contractAddr, &payer) {
return nil, nil, ErrAddressBlacklisted
}
}
Expand Down
Loading