-
Notifications
You must be signed in to change notification settings - Fork 690
RPC: add eth_signTransaction
#11517
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
RPC: add eth_signTransaction
#11517
Changes from all commits
a9d7717
421a559
906a592
1a36247
d7b98e2
60db331
77aba5f
b76e0da
0518dc3
7bd835c
05ad89e
eef3889
149d1c6
3c2657b
988c2b7
e05bc24
441e37a
b956806
0f37644
7fcaa57
4ffcbb1
a7d0f5b
9eda504
529e2e3
703632d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,11 +49,8 @@ public abstract class TransactionForRpc | |
| [JsonIgnore(Condition = JsonIgnoreCondition.Never)] | ||
| public long? Gas { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// True when the transaction type was inferred by <see cref="TransactionJsonConverter"/> rather than | ||
| /// explicitly provided in the JSON request. When set, <see cref="ToTransaction"/> can resolve | ||
| /// the type to match the target block's fork rules. | ||
| /// </summary> | ||
| // True when type came from a fallback (gasPrice-only or absolute default), not from an | ||
| // explicit `type` field or a discriminator. Set only during JSON deserialization. | ||
| [JsonIgnore] | ||
| internal bool IsTypeDefaulted { get; set; } | ||
|
|
||
|
|
@@ -74,10 +71,55 @@ public virtual Result<Transaction> ToTransaction(bool validateUserInput = false, | |
|
|
||
| private TxType ResolveType(IReleaseSpec? spec) | ||
| { | ||
| // Pre-Berlin only knows Legacy; defaulted-type requests downgrade to avoid EVM rejection. | ||
| TxType type = Type ?? default; | ||
| return spec is not null && !spec.IsEip2930Enabled && IsTypeDefaulted ? TxType.Legacy : type; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Validates fields required for signing (gas, fee, nonce), promotes type-defaulted | ||
| /// transactions to EIP-1559, and returns the resulting <see cref="Transaction"/>. | ||
| /// </summary> | ||
| public Result<Transaction> ToSignableTransaction() | ||
| { | ||
| if (Gas is null) | ||
| return Result<Transaction>.Fail("gas not specified"); | ||
|
|
||
| if (!HasFeeFields(this)) | ||
| return Result<Transaction>.Fail("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas"); | ||
|
|
||
| // All concrete tx subtypes (AccessList, EIP1559, Blob, SetCode) derive from LegacyTransactionForRpc. | ||
| if (this is not LegacyTransactionForRpc { Nonce: not null }) | ||
| return Result<Transaction>.Fail("nonce not specified"); | ||
|
|
||
| return PromoteToEip1559IfTypeDefaulted().ToTransaction(validateUserInput: true); | ||
| } | ||
|
|
||
| private static bool HasFeeFields(TransactionForRpc rpcTx) => | ||
| rpcTx is EIP1559TransactionForRpc { MaxFeePerGas: not null, MaxPriorityFeePerGas: not null } | ||
| or LegacyTransactionForRpc { GasPrice: not null }; | ||
|
|
||
| public TransactionForRpc PromoteToEip1559IfTypeDefaulted() | ||
| { | ||
| if (!IsTypeDefaulted) return this; | ||
| // AccessList and its descendants (EIP1559/Blob/SetCode) are already typed — only plain Legacy promotes. | ||
| if (this is AccessListTransactionForRpc) return this; | ||
| if (this is not LegacyTransactionForRpc legacy) return this; | ||
|
|
||
| return new EIP1559TransactionForRpc | ||
| { | ||
| From = legacy.From, | ||
| To = legacy.To, | ||
| Value = legacy.Value, | ||
| Gas = legacy.Gas, | ||
| Nonce = legacy.Nonce, | ||
| Input = legacy.Input, | ||
| ChainId = legacy.ChainId, | ||
| MaxFeePerGas = legacy.GasPrice, | ||
| MaxPriorityFeePerGas = legacy.GasPrice, | ||
| }; | ||
| } | ||
|
|
||
| public abstract bool ShouldSetBaseFee(); | ||
|
|
||
| internal class TransactionJsonConverter : JsonConverter<TransactionForRpc> | ||
|
|
@@ -143,35 +185,44 @@ internal static void RegisterTransactionType<T>() where T : TransactionForRpc, I | |
| Type concreteTxType = DeriveTxType(untyped, options, out bool isDefaulted); | ||
|
|
||
| TransactionForRpc? result = (TransactionForRpc?)JsonSerializer.Deserialize(ref reader, concreteTxType, options); | ||
| result?.IsTypeDefaulted = isDefaulted; | ||
| if (result is not null) | ||
| { | ||
| result.IsTypeDefaulted = isDefaulted; | ||
| } | ||
|
Comment on lines
+188
to
+191
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why downgrade?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The downgrade is a pre-Berlin compatibility guard. ResolveType checks |
||
| return result; | ||
| } | ||
|
|
||
| private Type DeriveTxType(JsonObject untyped, JsonSerializerOptions options, out bool isDefaulted) | ||
| { | ||
| const string gasPriceFieldKey = nameof(LegacyTransactionForRpc.GasPrice); | ||
| const string typeFieldKey = nameof(TransactionForRpc.Type); | ||
| isDefaulted = false; | ||
|
|
||
| if (untyped.TryGetPropertyValue(typeFieldKey, out JsonNode? node)) | ||
| { | ||
| TxType? setType = node.Deserialize<TxType?>(options); | ||
| if (setType is not null) | ||
| { | ||
| isDefaulted = false; | ||
| return _txTypes.FirstOrDefault(p => p.TxType == setType)?.Type ?? throw new JsonException("Unknown transaction type"); | ||
| } | ||
| } | ||
|
|
||
| return untyped.ContainsKey(gasPriceFieldKey) | ||
| ? typeof(LegacyTransactionForRpc) | ||
| : _txTypes.FirstOrDefault(p => p.DiscriminatorProperties.Any(untyped.ContainsKey))?.Type | ||
| ?? GetDefaultType(out isDefaulted); | ||
|
|
||
| static Type GetDefaultType(out bool isDefaulted) | ||
| if (untyped.ContainsKey(gasPriceFieldKey)) | ||
| { | ||
| isDefaulted = true; | ||
| return typeof(EIP1559TransactionForRpc); | ||
| return typeof(LegacyTransactionForRpc); | ||
| } | ||
|
|
||
| // Discriminator field is a strong signal — not a default. | ||
| Type? viaDiscriminator = _txTypes.FirstOrDefault(p => p.DiscriminatorProperties.Any(untyped.ContainsKey))?.Type; | ||
| if (viaDiscriminator is not null) | ||
| { | ||
| isDefaulted = false; | ||
| return viaDiscriminator; | ||
| } | ||
|
|
||
| isDefaulted = true; | ||
| return typeof(EIP1559TransactionForRpc); | ||
| } | ||
|
|
||
| public override void Write(Utf8JsonWriter writer, TransactionForRpc value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.