-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into arbitrary-prune-point
- Loading branch information
Showing
6 changed files
with
201 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package arbitrum | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
|
||
"github.com/ethereum/go-ethereum/arbitrum_types" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/common/hexutil" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
"github.com/ethereum/go-ethereum/internal/ethapi" | ||
"github.com/ethereum/go-ethereum/log" | ||
"github.com/ethereum/go-ethereum/rpc" | ||
) | ||
|
||
type ArbTransactionAPI struct { | ||
b *APIBackend | ||
} | ||
|
||
func NewArbTransactionAPI(b *APIBackend) *ArbTransactionAPI { | ||
return &ArbTransactionAPI{b} | ||
} | ||
|
||
func (s *ArbTransactionAPI) SendRawTransactionConditional(ctx context.Context, input hexutil.Bytes, options *arbitrum_types.ConditionalOptions) (common.Hash, error) { | ||
tx := new(types.Transaction) | ||
if err := tx.UnmarshalBinary(input); err != nil { | ||
return common.Hash{}, err | ||
} | ||
return SubmitConditionalTransaction(ctx, s.b, tx, options) | ||
} | ||
|
||
func SubmitConditionalTransaction(ctx context.Context, b *APIBackend, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) (common.Hash, error) { | ||
// If the transaction fee cap is already specified, ensure the | ||
// fee of the given transaction is _reasonable_. | ||
if err := ethapi.CheckTxFee(tx.GasPrice(), tx.Gas(), b.RPCTxFeeCap()); err != nil { | ||
return common.Hash{}, err | ||
} | ||
if !b.UnprotectedAllowed() && !tx.Protected() { | ||
// Ensure only eip155 signed transactions are submitted if EIP155Required is set. | ||
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC") | ||
} | ||
if err := b.SendConditionalTx(ctx, tx, options); err != nil { | ||
return common.Hash{}, err | ||
} | ||
// Print a log with full tx details for manual investigations and interventions | ||
signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) | ||
from, err := types.Sender(signer, tx) | ||
if err != nil { | ||
return common.Hash{}, err | ||
} | ||
|
||
if tx.To() == nil { | ||
addr := crypto.CreateAddress(from, tx.Nonce()) | ||
log.Info("Submitted contract creation", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "contract", addr.Hex(), "value", tx.Value()) | ||
} else { | ||
log.Info("Submitted transaction", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "recipient", tx.To(), "value", tx.Value()) | ||
} | ||
return tx.Hash(), nil | ||
} | ||
|
||
func SendConditionalTransactionRPC(ctx context.Context, rpc *rpc.Client, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error { | ||
data, err := tx.MarshalBinary() | ||
if err != nil { | ||
return err | ||
} | ||
return rpc.CallContext(ctx, nil, "eth_sendRawTransactionConditional", hexutil.Encode(data), options) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package arbitrum_types | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"strings" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/common/hexutil" | ||
"github.com/ethereum/go-ethereum/core/state" | ||
"github.com/ethereum/go-ethereum/rpc" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type rejectedError struct { | ||
msg string | ||
} | ||
|
||
func NewRejectedError(msg string) *rejectedError { | ||
return &rejectedError{msg: msg} | ||
} | ||
func (e rejectedError) Error() string { return e.msg } | ||
func (rejectedError) ErrorCode() int { return -32003 } | ||
|
||
type limitExceededError struct { | ||
msg string | ||
} | ||
|
||
func NewLimitExceededError(msg string) *limitExceededError { | ||
return &limitExceededError{msg: msg} | ||
} | ||
func (e limitExceededError) Error() string { return e.msg } | ||
func (limitExceededError) ErrorCode() int { return -32005 } | ||
|
||
func WrapOptionsCheckError(err error, msg string) error { | ||
wrappedMsg := func(e rpc.Error, msg string) string { | ||
return strings.Join([]string{msg, e.Error()}, ":") | ||
} | ||
switch e := err.(type) { | ||
case *rejectedError: | ||
return NewRejectedError(wrappedMsg(e, msg)) | ||
case *limitExceededError: | ||
return NewLimitExceededError(wrappedMsg(e, msg)) | ||
default: | ||
return errors.Wrap(err, msg) | ||
} | ||
} | ||
|
||
type RootHashOrSlots struct { | ||
RootHash *common.Hash | ||
SlotValue map[common.Hash]common.Hash | ||
} | ||
|
||
func (r *RootHashOrSlots) UnmarshalJSON(data []byte) error { | ||
var hash common.Hash | ||
var err error | ||
if err = json.Unmarshal(data, &hash); err == nil { | ||
r.RootHash = &hash | ||
return nil | ||
} | ||
return json.Unmarshal(data, &r.SlotValue) | ||
} | ||
|
||
func (r RootHashOrSlots) MarshalJSON() ([]byte, error) { | ||
if r.RootHash != nil { | ||
return json.Marshal(*r.RootHash) | ||
} | ||
return json.Marshal(r.SlotValue) | ||
} | ||
|
||
type ConditionalOptions struct { | ||
KnownAccounts map[common.Address]RootHashOrSlots `json:"knownAccounts"` | ||
BlockNumberMin *hexutil.Uint64 `json:"blockNumberMin,omitempty"` | ||
BlockNumberMax *hexutil.Uint64 `json:"blockNumberMax,omitempty"` | ||
TimestampMin *hexutil.Uint64 `json:"timestampMin,omitempty"` | ||
TimestampMax *hexutil.Uint64 `json:"timestampMax,omitempty"` | ||
} | ||
|
||
func (o *ConditionalOptions) Check(l1BlockNumber uint64, l2Timestamp uint64, statedb *state.StateDB) error { | ||
if o.BlockNumberMin != nil && l1BlockNumber < uint64(*o.BlockNumberMin) { | ||
return NewRejectedError("BlockNumberMin condition not met") | ||
} | ||
if o.BlockNumberMax != nil && l1BlockNumber > uint64(*o.BlockNumberMax) { | ||
return NewRejectedError("BlockNumberMax condition not met") | ||
} | ||
if o.TimestampMin != nil && l2Timestamp < uint64(*o.TimestampMin) { | ||
return NewRejectedError("TimestampMin condition not met") | ||
} | ||
if o.TimestampMax != nil && l2Timestamp > uint64(*o.TimestampMax) { | ||
return NewRejectedError("TimestampMax condition not met") | ||
} | ||
for address, rootHashOrSlots := range o.KnownAccounts { | ||
if rootHashOrSlots.RootHash != nil { | ||
trie := statedb.StorageTrie(address) | ||
if trie == nil { | ||
return NewRejectedError("Storage trie not found for address key in knownAccounts option") | ||
} | ||
if trie.Hash() != *rootHashOrSlots.RootHash { | ||
return NewRejectedError("Storage root hash condition not met") | ||
} | ||
} else if len(rootHashOrSlots.SlotValue) > 0 { | ||
for slot, value := range rootHashOrSlots.SlotValue { | ||
stored := statedb.GetState(address, slot) | ||
if !bytes.Equal(stored.Bytes(), value.Bytes()) { | ||
return NewRejectedError("Storage slot value condition not met") | ||
} | ||
} | ||
} // else rootHashOrSlots.SlotValue is empty - ignore it and check the rest of conditions | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters