Skip to content

Commit 7ba1099

Browse files
authored
Merge branch 'master' into arbitrary-prune-point
2 parents 2ed3220 + 86b963d commit 7ba1099

File tree

6 files changed

+201
-4
lines changed

6 files changed

+201
-4
lines changed

arbitrum/apibackend.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
"github.com/ethereum/go-ethereum"
13+
"github.com/ethereum/go-ethereum/arbitrum_types"
1314
"github.com/ethereum/go-ethereum/eth"
1415
"github.com/ethereum/go-ethereum/eth/tracers"
1516
"github.com/ethereum/go-ethereum/log"
@@ -110,6 +111,13 @@ func (a *APIBackend) GetAPIs(filterSystem *filters.FilterSystem) []rpc.API {
110111
Public: true,
111112
})
112113

114+
apis = append(apis, rpc.API{
115+
Namespace: "eth",
116+
Version: "1.0",
117+
Service: NewArbTransactionAPI(a),
118+
Public: true,
119+
})
120+
113121
apis = append(apis, rpc.API{
114122
Namespace: "net",
115123
Version: "1.0",
@@ -470,7 +478,11 @@ func (a *APIBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) even
470478

471479
// Transaction pool API
472480
func (a *APIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
473-
return a.b.EnqueueL2Message(ctx, signedTx)
481+
return a.b.EnqueueL2Message(ctx, signedTx, nil)
482+
}
483+
484+
func (a *APIBackend) SendConditionalTx(ctx context.Context, signedTx *types.Transaction, options *arbitrum_types.ConditionalOptions) error {
485+
return a.b.EnqueueL2Message(ctx, signedTx, options)
474486
}
475487

476488
func (a *APIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {

arbitrum/arbos_interface.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package arbitrum
33
import (
44
"context"
55

6+
"github.com/ethereum/go-ethereum/arbitrum_types"
67
"github.com/ethereum/go-ethereum/core"
78
"github.com/ethereum/go-ethereum/core/types"
89
)
910

1011
type ArbInterface interface {
11-
PublishTransaction(ctx context.Context, tx *types.Transaction) error
12+
PublishTransaction(ctx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error
1213
BlockChain() *core.BlockChain
1314
ArbNode() interface{}
1415
}

arbitrum/backend.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package arbitrum
33
import (
44
"context"
55

6+
"github.com/ethereum/go-ethereum/arbitrum_types"
67
"github.com/ethereum/go-ethereum/core"
78
"github.com/ethereum/go-ethereum/core/bloombits"
89
"github.com/ethereum/go-ethereum/core/types"
@@ -66,8 +67,8 @@ func (b *Backend) ChainDb() ethdb.Database {
6667
return b.chainDb
6768
}
6869

69-
func (b *Backend) EnqueueL2Message(ctx context.Context, tx *types.Transaction) error {
70-
return b.arb.PublishTransaction(ctx, tx)
70+
func (b *Backend) EnqueueL2Message(ctx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error {
71+
return b.arb.PublishTransaction(ctx, tx, options)
7172
}
7273

7374
func (b *Backend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {

arbitrum/conditionaltx.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package arbitrum
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"github.com/ethereum/go-ethereum/arbitrum_types"
8+
"github.com/ethereum/go-ethereum/common"
9+
"github.com/ethereum/go-ethereum/common/hexutil"
10+
"github.com/ethereum/go-ethereum/core/types"
11+
"github.com/ethereum/go-ethereum/crypto"
12+
"github.com/ethereum/go-ethereum/internal/ethapi"
13+
"github.com/ethereum/go-ethereum/log"
14+
"github.com/ethereum/go-ethereum/rpc"
15+
)
16+
17+
type ArbTransactionAPI struct {
18+
b *APIBackend
19+
}
20+
21+
func NewArbTransactionAPI(b *APIBackend) *ArbTransactionAPI {
22+
return &ArbTransactionAPI{b}
23+
}
24+
25+
func (s *ArbTransactionAPI) SendRawTransactionConditional(ctx context.Context, input hexutil.Bytes, options *arbitrum_types.ConditionalOptions) (common.Hash, error) {
26+
tx := new(types.Transaction)
27+
if err := tx.UnmarshalBinary(input); err != nil {
28+
return common.Hash{}, err
29+
}
30+
return SubmitConditionalTransaction(ctx, s.b, tx, options)
31+
}
32+
33+
func SubmitConditionalTransaction(ctx context.Context, b *APIBackend, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) (common.Hash, error) {
34+
// If the transaction fee cap is already specified, ensure the
35+
// fee of the given transaction is _reasonable_.
36+
if err := ethapi.CheckTxFee(tx.GasPrice(), tx.Gas(), b.RPCTxFeeCap()); err != nil {
37+
return common.Hash{}, err
38+
}
39+
if !b.UnprotectedAllowed() && !tx.Protected() {
40+
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
41+
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
42+
}
43+
if err := b.SendConditionalTx(ctx, tx, options); err != nil {
44+
return common.Hash{}, err
45+
}
46+
// Print a log with full tx details for manual investigations and interventions
47+
signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
48+
from, err := types.Sender(signer, tx)
49+
if err != nil {
50+
return common.Hash{}, err
51+
}
52+
53+
if tx.To() == nil {
54+
addr := crypto.CreateAddress(from, tx.Nonce())
55+
log.Info("Submitted contract creation", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "contract", addr.Hex(), "value", tx.Value())
56+
} else {
57+
log.Info("Submitted transaction", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "recipient", tx.To(), "value", tx.Value())
58+
}
59+
return tx.Hash(), nil
60+
}
61+
62+
func SendConditionalTransactionRPC(ctx context.Context, rpc *rpc.Client, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error {
63+
data, err := tx.MarshalBinary()
64+
if err != nil {
65+
return err
66+
}
67+
return rpc.CallContext(ctx, nil, "eth_sendRawTransactionConditional", hexutil.Encode(data), options)
68+
}

arbitrum_types/txoptions.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package arbitrum_types
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"strings"
7+
8+
"github.com/ethereum/go-ethereum/common"
9+
"github.com/ethereum/go-ethereum/common/hexutil"
10+
"github.com/ethereum/go-ethereum/core/state"
11+
"github.com/ethereum/go-ethereum/rpc"
12+
"github.com/pkg/errors"
13+
)
14+
15+
type rejectedError struct {
16+
msg string
17+
}
18+
19+
func NewRejectedError(msg string) *rejectedError {
20+
return &rejectedError{msg: msg}
21+
}
22+
func (e rejectedError) Error() string { return e.msg }
23+
func (rejectedError) ErrorCode() int { return -32003 }
24+
25+
type limitExceededError struct {
26+
msg string
27+
}
28+
29+
func NewLimitExceededError(msg string) *limitExceededError {
30+
return &limitExceededError{msg: msg}
31+
}
32+
func (e limitExceededError) Error() string { return e.msg }
33+
func (limitExceededError) ErrorCode() int { return -32005 }
34+
35+
func WrapOptionsCheckError(err error, msg string) error {
36+
wrappedMsg := func(e rpc.Error, msg string) string {
37+
return strings.Join([]string{msg, e.Error()}, ":")
38+
}
39+
switch e := err.(type) {
40+
case *rejectedError:
41+
return NewRejectedError(wrappedMsg(e, msg))
42+
case *limitExceededError:
43+
return NewLimitExceededError(wrappedMsg(e, msg))
44+
default:
45+
return errors.Wrap(err, msg)
46+
}
47+
}
48+
49+
type RootHashOrSlots struct {
50+
RootHash *common.Hash
51+
SlotValue map[common.Hash]common.Hash
52+
}
53+
54+
func (r *RootHashOrSlots) UnmarshalJSON(data []byte) error {
55+
var hash common.Hash
56+
var err error
57+
if err = json.Unmarshal(data, &hash); err == nil {
58+
r.RootHash = &hash
59+
return nil
60+
}
61+
return json.Unmarshal(data, &r.SlotValue)
62+
}
63+
64+
func (r RootHashOrSlots) MarshalJSON() ([]byte, error) {
65+
if r.RootHash != nil {
66+
return json.Marshal(*r.RootHash)
67+
}
68+
return json.Marshal(r.SlotValue)
69+
}
70+
71+
type ConditionalOptions struct {
72+
KnownAccounts map[common.Address]RootHashOrSlots `json:"knownAccounts"`
73+
BlockNumberMin *hexutil.Uint64 `json:"blockNumberMin,omitempty"`
74+
BlockNumberMax *hexutil.Uint64 `json:"blockNumberMax,omitempty"`
75+
TimestampMin *hexutil.Uint64 `json:"timestampMin,omitempty"`
76+
TimestampMax *hexutil.Uint64 `json:"timestampMax,omitempty"`
77+
}
78+
79+
func (o *ConditionalOptions) Check(l1BlockNumber uint64, l2Timestamp uint64, statedb *state.StateDB) error {
80+
if o.BlockNumberMin != nil && l1BlockNumber < uint64(*o.BlockNumberMin) {
81+
return NewRejectedError("BlockNumberMin condition not met")
82+
}
83+
if o.BlockNumberMax != nil && l1BlockNumber > uint64(*o.BlockNumberMax) {
84+
return NewRejectedError("BlockNumberMax condition not met")
85+
}
86+
if o.TimestampMin != nil && l2Timestamp < uint64(*o.TimestampMin) {
87+
return NewRejectedError("TimestampMin condition not met")
88+
}
89+
if o.TimestampMax != nil && l2Timestamp > uint64(*o.TimestampMax) {
90+
return NewRejectedError("TimestampMax condition not met")
91+
}
92+
for address, rootHashOrSlots := range o.KnownAccounts {
93+
if rootHashOrSlots.RootHash != nil {
94+
trie := statedb.StorageTrie(address)
95+
if trie == nil {
96+
return NewRejectedError("Storage trie not found for address key in knownAccounts option")
97+
}
98+
if trie.Hash() != *rootHashOrSlots.RootHash {
99+
return NewRejectedError("Storage root hash condition not met")
100+
}
101+
} else if len(rootHashOrSlots.SlotValue) > 0 {
102+
for slot, value := range rootHashOrSlots.SlotValue {
103+
stored := statedb.GetState(address, slot)
104+
if !bytes.Equal(stored.Bytes(), value.Bytes()) {
105+
return NewRejectedError("Storage slot value condition not met")
106+
}
107+
}
108+
} // else rootHashOrSlots.SlotValue is empty - ignore it and check the rest of conditions
109+
}
110+
return nil
111+
}

internal/ethapi/api.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2225,6 +2225,10 @@ func (s *NetAPI) Version() string {
22252225
return fmt.Sprintf("%d", s.networkVersion)
22262226
}
22272227

2228+
func CheckTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
2229+
return checkTxFee(gasPrice, gas, cap)
2230+
}
2231+
22282232
// checkTxFee is an internal function used to check whether the fee of
22292233
// the given transaction is _reasonable_(under the cap).
22302234
func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error {

0 commit comments

Comments
 (0)