diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 5fd3f4600a..15e865b3cf 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -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 { diff --git a/core/types/access_list_tx.go b/core/types/access_list_tx.go index ee5f194b77..90ea043cb2 100644 --- a/core/types/access_list_tx.go +++ b/core/types/access_list_tx.go @@ -105,6 +105,11 @@ func (tx *AccessListTx) gasFeeCap() *big.Int { return tx.GasPrice } func (tx *AccessListTx) value() *big.Int { return tx.Value } func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } func (tx *AccessListTx) to() *common.Address { return tx.To } +func (tx *AccessListTx) expiredTime() uint64 { return 0 } + +func (tx *AccessListTx) rawPayerSignatureValues() (v, r, s *big.Int) { + return nil, nil, nil +} func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) { return tx.V, tx.R, tx.S diff --git a/core/types/dynamic_fee_tx.go b/core/types/dynamic_fee_tx.go index 585c029d89..ffab45df66 100644 --- a/core/types/dynamic_fee_tx.go +++ b/core/types/dynamic_fee_tx.go @@ -93,6 +93,11 @@ func (tx *DynamicFeeTx) gasPrice() *big.Int { return tx.GasFeeCap } func (tx *DynamicFeeTx) value() *big.Int { return tx.Value } func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce } func (tx *DynamicFeeTx) to() *common.Address { return tx.To } +func (tx *DynamicFeeTx) expiredTime() uint64 { return 0 } + +func (tx *DynamicFeeTx) rawPayerSignatureValues() (v, r, s *big.Int) { + return nil, nil, nil +} func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) { return tx.V, tx.R, tx.S diff --git a/core/types/legacy_tx.go b/core/types/legacy_tx.go index cb86bed772..3c2d007be6 100644 --- a/core/types/legacy_tx.go +++ b/core/types/legacy_tx.go @@ -102,6 +102,11 @@ func (tx *LegacyTx) gasFeeCap() *big.Int { return tx.GasPrice } func (tx *LegacyTx) value() *big.Int { return tx.Value } func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } func (tx *LegacyTx) to() *common.Address { return tx.To } +func (tx *LegacyTx) expiredTime() uint64 { return 0 } + +func (tx *LegacyTx) rawPayerSignatureValues() (v, r, s *big.Int) { + return nil, nil, nil +} func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) { return tx.V, tx.R, tx.S diff --git a/core/types/receipt.go b/core/types/receipt.go index c3588990c0..264ccb4515 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -192,7 +192,7 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { return errEmptyTypedReceipt } r.Type = b[0] - if r.Type == AccessListTxType || r.Type == DynamicFeeTxType { + if r.Type == AccessListTxType || r.Type == DynamicFeeTxType || r.Type == SponsoredTxType { var dec receiptRLP if err := rlp.DecodeBytes(b[1:], &dec); err != nil { return err @@ -228,7 +228,7 @@ func (r *Receipt) decodeTyped(b []byte) error { return errEmptyTypedReceipt } switch b[0] { - case DynamicFeeTxType, AccessListTxType: + case DynamicFeeTxType, AccessListTxType, SponsoredTxType: var data receiptRLP err := rlp.DecodeBytes(b[1:], &data) if err != nil { @@ -391,11 +391,8 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { switch r.Type { case LegacyTxType: rlp.Encode(w, data) - case AccessListTxType: - w.WriteByte(AccessListTxType) - rlp.Encode(w, data) - case DynamicFeeTxType: - w.WriteByte(DynamicFeeTxType) + case AccessListTxType, DynamicFeeTxType, SponsoredTxType: + w.WriteByte(r.Type) rlp.Encode(w, data) default: // For unsupported types, write nothing. Since this is for diff --git a/core/types/sponsored_tx.go b/core/types/sponsored_tx.go new file mode 100644 index 0000000000..8efb46e35b --- /dev/null +++ b/core/types/sponsored_tx.go @@ -0,0 +1,89 @@ +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +type SponsoredTx struct { + Nonce uint64 // nonce of sender account + GasPrice *big.Int // wei per gas + Gas uint64 // gas limit + To *common.Address `rlp:"nil"` // nil means contract creation + Value *big.Int // wei amount + Data []byte // contract invocation input data + ExpiredTime uint64 // the expired time of payer's signature + PayerV, PayerR, PayerS *big.Int // payer's signature values + V, R, S *big.Int // sender's signature values +} + +func (tx *SponsoredTx) copy() TxData { + cpy := &SponsoredTx{ + Nonce: tx.Nonce, + To: copyAddressPtr(tx.To), + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + ExpiredTime: tx.ExpiredTime, + // These are initialized below. + Value: new(big.Int), + GasPrice: new(big.Int), + PayerV: new(big.Int), + PayerR: new(big.Int), + PayerS: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.GasPrice != nil { + cpy.GasPrice.Set(tx.GasPrice) + } + if tx.PayerV != nil { + cpy.PayerV.Set(tx.PayerV) + } + if tx.PayerR != nil { + cpy.PayerR.Set(tx.PayerR) + } + if tx.PayerS != nil { + cpy.PayerS.Set(tx.PayerS) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// accessors for innerTx. +func (tx *SponsoredTx) txType() byte { return SponsoredTxType } +func (tx *SponsoredTx) chainID() *big.Int { return deriveChainId(tx.V) } +func (tx *SponsoredTx) accessList() AccessList { return nil } +func (tx *SponsoredTx) data() []byte { return tx.Data } +func (tx *SponsoredTx) gas() uint64 { return tx.Gas } +func (tx *SponsoredTx) gasPrice() *big.Int { return tx.GasPrice } +func (tx *SponsoredTx) gasTipCap() *big.Int { return tx.GasPrice } +func (tx *SponsoredTx) gasFeeCap() *big.Int { return tx.GasPrice } +func (tx *SponsoredTx) value() *big.Int { return tx.Value } +func (tx *SponsoredTx) nonce() uint64 { return tx.Nonce } +func (tx *SponsoredTx) to() *common.Address { return tx.To } +func (tx *SponsoredTx) expiredTime() uint64 { return tx.ExpiredTime } + +func (tx *SponsoredTx) rawPayerSignatureValues() (v, r, s *big.Int) { + return tx.PayerV, tx.PayerR, tx.PayerS +} + +func (tx *SponsoredTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *SponsoredTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.V, tx.R, tx.S = v, r, s +} diff --git a/core/types/transaction.go b/core/types/transaction.go index 4e3c4fad8a..dd50ba4994 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -45,6 +45,7 @@ const ( LegacyTxType = iota AccessListTxType DynamicFeeTxType + SponsoredTxType = 100 ) // Transaction is an Ethereum transaction. @@ -53,9 +54,10 @@ type Transaction struct { time time.Time // Time first seen locally (spam avoidance) // caches - hash atomic.Value - size atomic.Value - from atomic.Value + hash atomic.Value + size atomic.Value + from atomic.Value + payer atomic.Value } // NewTx creates a new transaction. @@ -82,7 +84,9 @@ type TxData interface { value() *big.Int nonce() uint64 to() *common.Address + expiredTime() uint64 + rawPayerSignatureValues() (v, r, s *big.Int) rawSignatureValues() (v, r, s *big.Int) setSignatureValues(chainID, v, r, s *big.Int) } @@ -287,6 +291,11 @@ func (tx *Transaction) To() *common.Address { return copyAddressPtr(tx.inner.to()) } +// ExpiredTime returns the expired time of the sponsored transaction +func (tx *Transaction) ExpiredTime() uint64 { + return tx.inner.expiredTime() +} + // Cost returns gas * gasPrice + value. func (tx *Transaction) Cost() *big.Int { total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) @@ -294,6 +303,12 @@ func (tx *Transaction) Cost() *big.Int { return total } +// RawSignatureValues returns the V, R, S payer signature values of the transaction. +// The return values should not be modified by the caller. +func (tx *Transaction) RawPayerSignatureValues() (v, r, s *big.Int) { + return tx.inner.rawPayerSignatureValues() +} + // RawSignatureValues returns the V, R, S signature values of the transaction. // The return values should not be modified by the caller. func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { @@ -567,48 +582,64 @@ func (t *TransactionsByPriceAndNonce) Size() int { // // NOTE: In a future PR this will be removed. type Message struct { - to *common.Address - from common.Address - nonce uint64 - amount *big.Int - gasLimit uint64 - gasPrice *big.Int - gasFeeCap *big.Int - gasTipCap *big.Int - data []byte - accessList AccessList - isFake bool -} - -func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, isFake bool) Message { + to *common.Address + from common.Address + nonce uint64 + amount *big.Int + gasLimit uint64 + gasPrice *big.Int + gasFeeCap *big.Int + gasTipCap *big.Int + data []byte + accessList AccessList + isFake bool + payer common.Address + expiredTime uint64 +} + +// Create a new message with payer is the same as from, expired time = 0 +func NewMessage( + from common.Address, + to *common.Address, + nonce uint64, + amount *big.Int, + gasLimit uint64, + gasPrice, gasFeeCap, gasTipCap *big.Int, + data []byte, + accessList AccessList, + isFake bool, +) Message { return Message{ - from: from, - to: to, - nonce: nonce, - amount: amount, - gasLimit: gasLimit, - gasPrice: gasPrice, - gasFeeCap: gasFeeCap, - gasTipCap: gasTipCap, - data: data, - accessList: accessList, - isFake: isFake, + from: from, + to: to, + nonce: nonce, + amount: amount, + gasLimit: gasLimit, + gasPrice: gasPrice, + gasFeeCap: gasFeeCap, + gasTipCap: gasTipCap, + data: data, + accessList: accessList, + isFake: isFake, + payer: from, + expiredTime: 0, } } // AsMessage returns the transaction as a core.Message. func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) { msg := Message{ - nonce: tx.Nonce(), - gasLimit: tx.Gas(), - gasPrice: new(big.Int).Set(tx.GasPrice()), - gasFeeCap: new(big.Int).Set(tx.GasFeeCap()), - gasTipCap: new(big.Int).Set(tx.GasTipCap()), - to: tx.To(), - amount: tx.Value(), - data: tx.Data(), - accessList: tx.AccessList(), - isFake: false, + nonce: tx.Nonce(), + gasLimit: tx.Gas(), + gasPrice: new(big.Int).Set(tx.GasPrice()), + gasFeeCap: new(big.Int).Set(tx.GasFeeCap()), + gasTipCap: new(big.Int).Set(tx.GasTipCap()), + to: tx.To(), + amount: tx.Value(), + data: tx.Data(), + accessList: tx.AccessList(), + isFake: false, + expiredTime: tx.ExpiredTime(), } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { @@ -616,7 +647,25 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) { } var err error msg.from, err = Sender(s, tx) - return msg, err + if err != nil { + return Message{}, err + } + + msg.payer, err = Payer(s, tx) + if err != nil && errors.Is(err, errMissingPayerField) { + if errors.Is(err, errMissingPayerField) { + // This is not a sponsored transaction, the payer is the same as from + msg.payer = msg.from + return msg, nil + } + + return Message{}, err + } else if msg.payer == msg.from { + // Reject sponsored transaction with identical payer and sender + return Message{}, errors.New("payer = sender in sponsored transaction") + } + + return msg, nil } func (m Message) From() common.Address { return m.from } @@ -630,6 +679,8 @@ func (m Message) Nonce() uint64 { return m.nonce } func (m Message) Data() []byte { return m.data } func (m Message) AccessList() AccessList { return m.accessList } func (m Message) IsFake() bool { return m.isFake } +func (m Message) Payer() common.Address { return m.payer } +func (m Message) ExpiredTime() uint64 { return m.expiredTime } // copyAddressPtr copies an address. func copyAddressPtr(a *common.Address) *common.Address { diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 1d0d2a4c75..46a1872abc 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -27,7 +27,10 @@ import ( "github.com/ethereum/go-ethereum/params" ) -var ErrInvalidChainId = errors.New("invalid chain id for signer") +var ( + ErrInvalidChainId = errors.New("invalid chain id for signer") + errMissingPayerField = errors.New("transaction has no payer field") +) // sigCache is used to cache the derived sender and contains // the signer used to derive it. @@ -44,6 +47,8 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { signer = NewLondonSigner(config.ChainID) case config.IsBerlin(blockNumber): signer = NewEIP2930Signer(config.ChainID) + case config.IsMiko(blockNumber): + signer = NewMikoSigner(config.ChainID) case config.IsEIP155(blockNumber): signer = NewEIP155Signer(config.ChainID) case config.IsHomestead(blockNumber): @@ -69,6 +74,9 @@ func LatestSigner(config *params.ChainConfig) Signer { if config.BerlinBlock != nil { return NewEIP2930Signer(config.ChainID) } + if config.MikoBlock != nil { + return NewMikoSigner(config.ChainID) + } if config.EIP155Block != nil { return NewEIP155Signer(config.ChainID) } @@ -147,6 +155,35 @@ func Sender(signer Signer, tx *Transaction) (common.Address, error) { return addr, nil } +// Payer returns the address derived from payer's signature in sponsored +// transaction or nil in other transaction types. +// +// Payer may cache the address, allowing it to be used regardless of +// signing method. The cache is invalidated if the cached signer does +// not match the signer used in the current call. +func Payer(signer Signer, tx *Transaction) (common.Address, error) { + if tx.Type() != SponsoredTxType { + return common.Address{}, errMissingPayerField + } + + if sc := tx.payer.Load(); sc != nil { + sigCache := sc.(sigCache) + // If the signer used to derive from in a previous + // call is not the same as used current, invalidate + // the cache. + if sigCache.signer.Equal(signer) { + return sigCache.from, nil + } + } + + addr, err := signer.Payer(tx) + if err != nil { + return common.Address{}, err + } + tx.payer.Store(sigCache{signer: signer, from: addr}) + return addr, nil +} + // Signer encapsulates transaction signature handling. The name of this type is slightly // misleading because Signers don't actually sign, they're just for validating and // processing of signatures. @@ -168,6 +205,9 @@ type Signer interface { // Equal returns true if the given signer is the same as the receiver. Equal(Signer) bool + + // Payer returns the payer address of sponsored transaction + Payer(tx *Transaction) (common.Address, error) } type londonSigner struct{ eip2930Signer } @@ -175,10 +215,11 @@ type londonSigner struct{ eip2930Signer } // NewLondonSigner returns a signer that accepts // - EIP-1559 dynamic fee transactions // - EIP-2930 access list transactions, +// - Sponsored transactions, // - EIP-155 replay protected transactions, and // - legacy Homestead transactions. func NewLondonSigner(chainId *big.Int) Signer { - return londonSigner{eip2930Signer{NewEIP155Signer(chainId)}} + return londonSigner{eip2930Signer{MikoSigner{NewEIP155Signer(chainId)}}} } func (s londonSigner) Sender(tx *Transaction) (common.Address, error) { @@ -236,12 +277,14 @@ func (s londonSigner) Hash(tx *Transaction) common.Hash { }) } -type eip2930Signer struct{ EIP155Signer } +// londonSigner.Payer is the same as eip2930Signer.Payer + +type eip2930Signer struct{ MikoSigner } // NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions, // EIP-155 replay protected transactions, and legacy Homestead transactions. func NewEIP2930Signer(chainId *big.Int) Signer { - return eip2930Signer{NewEIP155Signer(chainId)} + return eip2930Signer{MikoSigner{NewEIP155Signer(chainId)}} } func (s eip2930Signer) ChainID() *big.Int { @@ -256,12 +299,8 @@ func (s eip2930Signer) Equal(s2 Signer) bool { func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) { V, R, S := tx.RawSignatureValues() switch tx.Type() { - case LegacyTxType: - if !tx.Protected() { - return HomesteadSigner{}.Sender(tx) - } - V = new(big.Int).Sub(V, s.chainIdMul) - V.Sub(V, big8) + case LegacyTxType, SponsoredTxType: + return s.MikoSigner.Sender(tx) case AccessListTxType: // AL txs are defined to use 0 and 1 as their recovery // id, add 27 to become equivalent to unprotected Homestead signatures. @@ -277,8 +316,8 @@ func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) { func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { switch txdata := tx.inner.(type) { - case *LegacyTx: - return s.EIP155Signer.SignatureValues(tx, sig) + case *LegacyTx, *SponsoredTx: + return s.MikoSigner.SignatureValues(tx, sig) case *AccessListTx: // Check that chain ID of tx matches the signer. We also accept ID zero here, // because it indicates that the chain ID was not specified in the tx. @@ -320,6 +359,8 @@ func (s eip2930Signer) Hash(tx *Transaction) common.Hash { tx.Data(), tx.AccessList(), }) + case SponsoredTxType: + return s.MikoSigner.Hash(tx) default: // This _should_ not happen, but in case someone sends in a bad // json struct via RPC, it's probably more prudent to return an @@ -329,6 +370,114 @@ func (s eip2930Signer) Hash(tx *Transaction) common.Hash { } } +func (s eip2930Signer) Payer(tx *Transaction) (common.Address, error) { + if tx.Type() == SponsoredTxType { + return s.MikoSigner.Payer(tx) + } + + return common.Address{}, ErrInvalidTxType +} + +type MikoSigner struct { + EIP155Signer +} + +func NewMikoSigner(chainId *big.Int) MikoSigner { + return MikoSigner{NewEIP155Signer(chainId)} +} + +func (s MikoSigner) Equal(s2 Signer) bool { + miko, ok := s2.(MikoSigner) + return ok && miko.chainId.Cmp(s.chainId) == 0 +} + +func (s MikoSigner) Sender(tx *Transaction) (common.Address, error) { + switch tx.Type() { + case LegacyTxType: + return s.EIP155Signer.Sender(tx) + case SponsoredTxType: + if tx.ChainId().Cmp(s.chainId) != 0 { + return common.Address{}, ErrInvalidChainId + } + // V in sponsored signature is {0, 1}, but the recoverPlain expects + // {0, 1} + 27, so we need to add 27 to V + V, R, S := tx.RawSignatureValues() + V = new(big.Int).Add(V, big.NewInt(27)) + return recoverPlain(s.Hash(tx), R, S, V, true) + default: + return common.Address{}, ErrTxTypeNotSupported + } +} + +func (s MikoSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + switch tx.Type() { + case LegacyTxType: + return s.EIP155Signer.SignatureValues(tx, sig) + case SponsoredTxType: + // V in sponsored signature is {0, 1}, get it directly from raw signature + // because decodeSignature returns {0, 1} + 27 + R, S, _ := decodeSignature(sig) + V := big.NewInt(int64(sig[64])) + return R, S, V, nil + default: + return nil, nil, nil, ErrTxTypeNotSupported + } +} + +func (s MikoSigner) Hash(tx *Transaction) common.Hash { + switch tx.Type() { + case LegacyTxType: + return s.EIP155Signer.Hash(tx) + case SponsoredTxType: + payerV, payerR, payerS := tx.RawPayerSignatureValues() + return prefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.ExpiredTime(), + payerV, payerR, payerS, + }, + ) + default: + return common.Hash{} + } +} + +func (s MikoSigner) Payer(tx *Transaction) (common.Address, error) { + if tx.Type() != SponsoredTxType { + return common.Address{}, ErrInvalidTxType + } + + sender, err := Sender(s, tx) + if err != nil { + return common.Address{}, err + } + + payerV, payerR, payerS := tx.RawPayerSignatureValues() + payerHash := rlpHash([]interface{}{ + s.chainId, + sender, + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.ExpiredTime(), + }) + + // V in payer signature is {0, 1}, but the recoverPlain expects + // {0, 1} + 27, so we need to add 27 to V + payerV = new(big.Int).Add(payerV, big.NewInt(27)) + return recoverPlain(payerHash, payerR, payerS, payerV, true) +} + // EIP155Signer implements Signer using the EIP-155 rules. This accepts transactions which // are replay-protected as well as unprotected homestead transactions. type EIP155Signer struct { @@ -400,6 +549,10 @@ func (s EIP155Signer) Hash(tx *Transaction) common.Hash { }) } +func (s EIP155Signer) Payer(tx *Transaction) (common.Address, error) { + return common.Address{}, ErrInvalidTxType +} + // HomesteadTransaction implements TransactionInterface using the // homestead rules. type HomesteadSigner struct{ FrontierSigner } @@ -469,6 +622,10 @@ func (fs FrontierSigner) Hash(tx *Transaction) common.Hash { }) } +func (fs FrontierSigner) Payer(tx *Transaction) (common.Address, error) { + return common.Address{}, ErrInvalidTxType +} + func decodeSignature(sig []byte) (r, s, v *big.Int) { if len(sig) != crypto.SignatureLength { panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)) diff --git a/ethclient/signer.go b/ethclient/signer.go index f827d4eb56..ac1ce39e9f 100644 --- a/ethclient/signer.go +++ b/ethclient/signer.go @@ -60,3 +60,6 @@ func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash { func (s *senderFromServer) SignatureValues(tx *types.Transaction, sig []byte) (R, S, V *big.Int, err error) { panic("can't sign with senderFromServer") } +func (s *senderFromServer) Payer(tx *types.Transaction) (common.Address, error) { + panic("can't sign with senderFromServer") +}