Migrate to in-progress version of FFI backend 0.6.0

Includes:
- Multi-step transaction proposals.
- Changes to support `Synchronizer.proposeShielding` API changes.
This commit is contained in:
Jack Grigg 2024-03-06 03:06:57 +00:00
parent f617f17e2a
commit e9177a28f7
11 changed files with 567 additions and 118 deletions

View File

@ -176,8 +176,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "c90afd6cc092468e71810bc715ddb49be8210b75",
"version" : "0.5.1"
"revision" : "789d0c068fb32e2ab149cdd785f16e0ac88f3594"
}
}
],

View File

@ -122,8 +122,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "c90afd6cc092468e71810bc715ddb49be8210b75",
"version" : "0.5.1"
"revision" : "789d0c068fb32e2ab149cdd785f16e0ac88f3594"
}
}
],

View File

@ -16,7 +16,8 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.19.1"),
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1"),
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", exact: "0.5.1")
// Compiled from revision `97e09ed3709ae9f26226587bec852a725bc783a4`.
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "789d0c068fb32e2ab149cdd785f16e0ac88f3594")
],
targets: [
.target(

View File

@ -13,7 +13,9 @@ public struct Proposal: Equatable {
/// Returns the total fee to be paid across all proposed transactions, in zatoshis.
public func totalFeeRequired() -> Zatoshi {
Zatoshi(Int64(inner.balance.feeRequired))
inner.steps.reduce(Zatoshi.zero) { acc, step in
acc + Zatoshi(Int64(step.balance.feeRequired))
}
}
}

View File

@ -143,18 +143,46 @@ extension FfiFeeRule: CaseIterable {
#endif // swift(>=4.2)
/// A data structure that describes the inputs to be consumed and outputs to
/// be produced in a proposed transaction.
/// A data structure that describes a series of transactions to be created.
struct FfiProposal {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// The version of this serialization format.
var protoVersion: UInt32 = 0
/// The fee rule used in constructing this proposal
var feeRule: FfiFeeRule = .notSpecified
/// The target height for which the proposal was constructed
///
/// The chain must contain at least this many blocks in order for the proposal to
/// be executed.
var minTargetHeight: UInt32 = 0
/// The series of transactions to be created.
var steps: [FfiProposalStep] = []
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
/// A data structure that describes the inputs to be consumed and outputs to
/// be produced in a proposed transaction.
struct FfiProposalStep {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// ZIP 321 serialized transaction request
var transactionRequest: String = String()
/// The vector of selected payment index / output pool mappings. Payment index
/// 0 corresponds to the payment with no explicit index.
var paymentOutputPools: [FfiPaymentOutputPool] = []
/// The anchor height to be used in creating the transaction, if any.
/// Setting the anchor height to zero will disallow the use of any shielded
/// inputs.
@ -174,16 +202,7 @@ struct FfiProposal {
/// Clears the value of `balance`. Subsequent reads from it will return its default value.
mutating func clearBalance() {self._balance = nil}
/// The fee rule used in constructing this proposal
var feeRule: FfiFeeRule = .notSpecified
/// The target height for which the proposal was constructed
///
/// The chain must contain at least this many blocks in order for the proposal to
/// be executed.
var minTargetHeight: UInt32 = 0
/// A flag indicating whether the proposal is for a shielding transaction,
/// A flag indicating whether the step is for a shielding transaction,
/// used for determining which OVK to select for wallet-internal outputs.
var isShielding: Bool = false
@ -194,8 +213,26 @@ struct FfiProposal {
fileprivate var _balance: FfiTransactionBalance? = nil
}
/// The unique identifier and value for each proposed input.
struct FfiProposedInput {
/// A mapping from ZIP 321 payment index to the output pool that has been chosen
/// for that payment, based upon the payment address and the selected inputs to
/// the transaction.
struct FfiPaymentOutputPool {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var paymentIndex: UInt32 = 0
var valuePool: FfiValuePool = .poolNotSpecified
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
/// The unique identifier and value for each proposed input that does not
/// require a back-reference to a prior step of the proposal.
struct FfiReceivedOutput {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -213,14 +250,113 @@ struct FfiProposedInput {
init() {}
}
/// A reference a payment in a prior step of the proposal. This payment must
/// belong to the wallet.
struct FfiPriorStepOutput {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var stepIndex: UInt32 = 0
var paymentIndex: UInt32 = 0
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
/// A reference a change output from a prior step of the proposal.
struct FfiPriorStepChange {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var stepIndex: UInt32 = 0
var changeIndex: UInt32 = 0
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
/// The unique identifier and value for an input to be used in the transaction.
struct FfiProposedInput {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var value: FfiProposedInput.OneOf_Value? = nil
var receivedOutput: FfiReceivedOutput {
get {
if case .receivedOutput(let v)? = value {return v}
return FfiReceivedOutput()
}
set {value = .receivedOutput(newValue)}
}
var priorStepOutput: FfiPriorStepOutput {
get {
if case .priorStepOutput(let v)? = value {return v}
return FfiPriorStepOutput()
}
set {value = .priorStepOutput(newValue)}
}
var priorStepChange: FfiPriorStepChange {
get {
if case .priorStepChange(let v)? = value {return v}
return FfiPriorStepChange()
}
set {value = .priorStepChange(newValue)}
}
var unknownFields = SwiftProtobuf.UnknownStorage()
enum OneOf_Value: Equatable {
case receivedOutput(FfiReceivedOutput)
case priorStepOutput(FfiPriorStepOutput)
case priorStepChange(FfiPriorStepChange)
#if !swift(>=4.1)
static func ==(lhs: FfiProposedInput.OneOf_Value, rhs: FfiProposedInput.OneOf_Value) -> Bool {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch (lhs, rhs) {
case (.receivedOutput, .receivedOutput): return {
guard case .receivedOutput(let l) = lhs, case .receivedOutput(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.priorStepOutput, .priorStepOutput): return {
guard case .priorStepOutput(let l) = lhs, case .priorStepOutput(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.priorStepChange, .priorStepChange): return {
guard case .priorStepChange(let l) = lhs, case .priorStepChange(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
#endif
}
init() {}
}
/// The proposed change outputs and fee value.
struct FfiTransactionBalance {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// A list of change output values.
var proposedChange: [FfiChangeValue] = []
/// The fee to be paid by the proposed transaction, in zatoshis.
var feeRequired: UInt64 = 0
var unknownFields = SwiftProtobuf.UnknownStorage()
@ -235,10 +371,14 @@ struct FfiChangeValue {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// The value of a change output to be created, in zatoshis.
var value: UInt64 = 0
/// The value pool in which the change output should be created.
var valuePool: FfiValuePool = .poolNotSpecified
/// The optional memo that should be associated with the newly created change output.
/// Memos must not be present for transparent change outputs.
var memo: FfiMemoBytes {
get {return _memo ?? FfiMemoBytes()}
set {_memo = newValue}
@ -273,7 +413,13 @@ struct FfiMemoBytes {
extension FfiValuePool: @unchecked Sendable {}
extension FfiFeeRule: @unchecked Sendable {}
extension FfiProposal: @unchecked Sendable {}
extension FfiProposalStep: @unchecked Sendable {}
extension FfiPaymentOutputPool: @unchecked Sendable {}
extension FfiReceivedOutput: @unchecked Sendable {}
extension FfiPriorStepOutput: @unchecked Sendable {}
extension FfiPriorStepChange: @unchecked Sendable {}
extension FfiProposedInput: @unchecked Sendable {}
extension FfiProposedInput.OneOf_Value: @unchecked Sendable {}
extension FfiTransactionBalance: @unchecked Sendable {}
extension FfiChangeValue: @unchecked Sendable {}
extension FfiMemoBytes: @unchecked Sendable {}
@ -305,13 +451,9 @@ extension FfiProposal: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
static let protoMessageName: String = _protobuf_package + ".Proposal"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "protoVersion"),
2: .same(proto: "transactionRequest"),
3: .same(proto: "anchorHeight"),
4: .same(proto: "inputs"),
5: .same(proto: "balance"),
6: .same(proto: "feeRule"),
7: .same(proto: "minTargetHeight"),
8: .same(proto: "isShielding"),
2: .same(proto: "feeRule"),
3: .same(proto: "minTargetHeight"),
4: .same(proto: "steps"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -321,13 +463,63 @@ extension FfiProposal: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.protoVersion) }()
case 2: try { try decoder.decodeSingularStringField(value: &self.transactionRequest) }()
case 2: try { try decoder.decodeSingularEnumField(value: &self.feeRule) }()
case 3: try { try decoder.decodeSingularUInt32Field(value: &self.minTargetHeight) }()
case 4: try { try decoder.decodeRepeatedMessageField(value: &self.steps) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.protoVersion != 0 {
try visitor.visitSingularUInt32Field(value: self.protoVersion, fieldNumber: 1)
}
if self.feeRule != .notSpecified {
try visitor.visitSingularEnumField(value: self.feeRule, fieldNumber: 2)
}
if self.minTargetHeight != 0 {
try visitor.visitSingularUInt32Field(value: self.minTargetHeight, fieldNumber: 3)
}
if !self.steps.isEmpty {
try visitor.visitRepeatedMessageField(value: self.steps, fieldNumber: 4)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: FfiProposal, rhs: FfiProposal) -> Bool {
if lhs.protoVersion != rhs.protoVersion {return false}
if lhs.feeRule != rhs.feeRule {return false}
if lhs.minTargetHeight != rhs.minTargetHeight {return false}
if lhs.steps != rhs.steps {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension FfiProposalStep: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".ProposalStep"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "transactionRequest"),
2: .same(proto: "paymentOutputPools"),
3: .same(proto: "anchorHeight"),
4: .same(proto: "inputs"),
5: .same(proto: "balance"),
6: .same(proto: "isShielding"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularStringField(value: &self.transactionRequest) }()
case 2: try { try decoder.decodeRepeatedMessageField(value: &self.paymentOutputPools) }()
case 3: try { try decoder.decodeSingularUInt32Field(value: &self.anchorHeight) }()
case 4: try { try decoder.decodeRepeatedMessageField(value: &self.inputs) }()
case 5: try { try decoder.decodeSingularMessageField(value: &self._balance) }()
case 6: try { try decoder.decodeSingularEnumField(value: &self.feeRule) }()
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.minTargetHeight) }()
case 8: try { try decoder.decodeSingularBoolField(value: &self.isShielding) }()
case 6: try { try decoder.decodeSingularBoolField(value: &self.isShielding) }()
default: break
}
}
@ -338,11 +530,11 @@ extension FfiProposal: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
if self.protoVersion != 0 {
try visitor.visitSingularUInt32Field(value: self.protoVersion, fieldNumber: 1)
}
if !self.transactionRequest.isEmpty {
try visitor.visitSingularStringField(value: self.transactionRequest, fieldNumber: 2)
try visitor.visitSingularStringField(value: self.transactionRequest, fieldNumber: 1)
}
if !self.paymentOutputPools.isEmpty {
try visitor.visitRepeatedMessageField(value: self.paymentOutputPools, fieldNumber: 2)
}
if self.anchorHeight != 0 {
try visitor.visitSingularUInt32Field(value: self.anchorHeight, fieldNumber: 3)
@ -353,34 +545,64 @@ extension FfiProposal: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
try { if let v = self._balance {
try visitor.visitSingularMessageField(value: v, fieldNumber: 5)
} }()
if self.feeRule != .notSpecified {
try visitor.visitSingularEnumField(value: self.feeRule, fieldNumber: 6)
}
if self.minTargetHeight != 0 {
try visitor.visitSingularUInt32Field(value: self.minTargetHeight, fieldNumber: 7)
}
if self.isShielding != false {
try visitor.visitSingularBoolField(value: self.isShielding, fieldNumber: 8)
try visitor.visitSingularBoolField(value: self.isShielding, fieldNumber: 6)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: FfiProposal, rhs: FfiProposal) -> Bool {
if lhs.protoVersion != rhs.protoVersion {return false}
static func ==(lhs: FfiProposalStep, rhs: FfiProposalStep) -> Bool {
if lhs.transactionRequest != rhs.transactionRequest {return false}
if lhs.paymentOutputPools != rhs.paymentOutputPools {return false}
if lhs.anchorHeight != rhs.anchorHeight {return false}
if lhs.inputs != rhs.inputs {return false}
if lhs._balance != rhs._balance {return false}
if lhs.feeRule != rhs.feeRule {return false}
if lhs.minTargetHeight != rhs.minTargetHeight {return false}
if lhs.isShielding != rhs.isShielding {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension FfiProposedInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".ProposedInput"
extension FfiPaymentOutputPool: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".PaymentOutputPool"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "paymentIndex"),
2: .same(proto: "valuePool"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.paymentIndex) }()
case 2: try { try decoder.decodeSingularEnumField(value: &self.valuePool) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.paymentIndex != 0 {
try visitor.visitSingularUInt32Field(value: self.paymentIndex, fieldNumber: 1)
}
if self.valuePool != .poolNotSpecified {
try visitor.visitSingularEnumField(value: self.valuePool, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: FfiPaymentOutputPool, rhs: FfiPaymentOutputPool) -> Bool {
if lhs.paymentIndex != rhs.paymentIndex {return false}
if lhs.valuePool != rhs.valuePool {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension FfiReceivedOutput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".ReceivedOutput"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "txid"),
2: .same(proto: "valuePool"),
@ -419,7 +641,7 @@ extension FfiProposedInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: FfiProposedInput, rhs: FfiProposedInput) -> Bool {
static func ==(lhs: FfiReceivedOutput, rhs: FfiReceivedOutput) -> Bool {
if lhs.txid != rhs.txid {return false}
if lhs.valuePool != rhs.valuePool {return false}
if lhs.index != rhs.index {return false}
@ -429,6 +651,170 @@ extension FfiProposedInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme
}
}
extension FfiPriorStepOutput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".PriorStepOutput"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "stepIndex"),
2: .same(proto: "paymentIndex"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.stepIndex) }()
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.paymentIndex) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.stepIndex != 0 {
try visitor.visitSingularUInt32Field(value: self.stepIndex, fieldNumber: 1)
}
if self.paymentIndex != 0 {
try visitor.visitSingularUInt32Field(value: self.paymentIndex, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: FfiPriorStepOutput, rhs: FfiPriorStepOutput) -> Bool {
if lhs.stepIndex != rhs.stepIndex {return false}
if lhs.paymentIndex != rhs.paymentIndex {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension FfiPriorStepChange: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".PriorStepChange"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "stepIndex"),
2: .same(proto: "changeIndex"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.stepIndex) }()
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.changeIndex) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.stepIndex != 0 {
try visitor.visitSingularUInt32Field(value: self.stepIndex, fieldNumber: 1)
}
if self.changeIndex != 0 {
try visitor.visitSingularUInt32Field(value: self.changeIndex, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: FfiPriorStepChange, rhs: FfiPriorStepChange) -> Bool {
if lhs.stepIndex != rhs.stepIndex {return false}
if lhs.changeIndex != rhs.changeIndex {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension FfiProposedInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".ProposedInput"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "receivedOutput"),
2: .same(proto: "priorStepOutput"),
3: .same(proto: "priorStepChange"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try {
var v: FfiReceivedOutput?
var hadOneofValue = false
if let current = self.value {
hadOneofValue = true
if case .receivedOutput(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.value = .receivedOutput(v)
}
}()
case 2: try {
var v: FfiPriorStepOutput?
var hadOneofValue = false
if let current = self.value {
hadOneofValue = true
if case .priorStepOutput(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.value = .priorStepOutput(v)
}
}()
case 3: try {
var v: FfiPriorStepChange?
var hadOneofValue = false
if let current = self.value {
hadOneofValue = true
if case .priorStepChange(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.value = .priorStepChange(v)
}
}()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
switch self.value {
case .receivedOutput?: try {
guard case .receivedOutput(let v)? = self.value else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
}()
case .priorStepOutput?: try {
guard case .priorStepOutput(let v)? = self.value else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
}()
case .priorStepChange?: try {
guard case .priorStepChange(let v)? = self.value else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
}()
case nil: break
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: FfiProposedInput, rhs: FfiProposedInput) -> Bool {
if lhs.value != rhs.value {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension FfiTransactionBalance: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".TransactionBalance"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [

View File

@ -6,12 +6,29 @@ syntax = "proto3";
package cash.z.wallet.sdk.ffi;
option swift_prefix = "Ffi";
// A data structure that describes a series of transactions to be created.
message Proposal {
// The version of this serialization format.
uint32 protoVersion = 1;
// The fee rule used in constructing this proposal
FeeRule feeRule = 2;
// The target height for which the proposal was constructed
//
// The chain must contain at least this many blocks in order for the proposal to
// be executed.
uint32 minTargetHeight = 3;
// The series of transactions to be created.
repeated ProposalStep steps = 4;
}
// A data structure that describes the inputs to be consumed and outputs to
// be produced in a proposed transaction.
message Proposal {
uint32 protoVersion = 1;
message ProposalStep {
// ZIP 321 serialized transaction request
string transactionRequest = 2;
string transactionRequest = 1;
// The vector of selected payment index / output pool mappings. Payment index
// 0 corresponds to the payment with no explicit index.
repeated PaymentOutputPool paymentOutputPools = 2;
// The anchor height to be used in creating the transaction, if any.
// Setting the anchor height to zero will disallow the use of any shielded
// inputs.
@ -21,16 +38,9 @@ message Proposal {
// The total value, fee value, and change outputs of the proposed
// transaction
TransactionBalance balance = 5;
// The fee rule used in constructing this proposal
FeeRule feeRule = 6;
// The target height for which the proposal was constructed
//
// The chain must contain at least this many blocks in order for the proposal to
// be executed.
uint32 minTargetHeight = 7;
// A flag indicating whether the proposal is for a shielding transaction,
// A flag indicating whether the step is for a shielding transaction,
// used for determining which OVK to select for wallet-internal outputs.
bool isShielding = 8;
bool isShielding = 6;
}
enum ValuePool {
@ -47,14 +57,45 @@ enum ValuePool {
Orchard = 3;
}
// The unique identifier and value for each proposed input.
message ProposedInput {
// A mapping from ZIP 321 payment index to the output pool that has been chosen
// for that payment, based upon the payment address and the selected inputs to
// the transaction.
message PaymentOutputPool {
uint32 paymentIndex = 1;
ValuePool valuePool = 2;
}
// The unique identifier and value for each proposed input that does not
// require a back-reference to a prior step of the proposal.
message ReceivedOutput {
bytes txid = 1;
ValuePool valuePool = 2;
uint32 index = 3;
uint64 value = 4;
}
// A reference a payment in a prior step of the proposal. This payment must
// belong to the wallet.
message PriorStepOutput {
uint32 stepIndex = 1;
uint32 paymentIndex = 2;
}
// A reference a change output from a prior step of the proposal.
message PriorStepChange {
uint32 stepIndex = 1;
uint32 changeIndex = 2;
}
// The unique identifier and value for an input to be used in the transaction.
message ProposedInput {
oneof value {
ReceivedOutput receivedOutput = 1;
PriorStepOutput priorStepOutput = 2;
PriorStepChange priorStepChange = 3;
}
}
// The fee rule used in constructing a Proposal
enum FeeRule {
// Protobuf requires that enums have a zero discriminant as the default
@ -72,15 +113,21 @@ enum FeeRule {
// The proposed change outputs and fee value.
message TransactionBalance {
// A list of change output values.
repeated ChangeValue proposedChange = 1;
// The fee to be paid by the proposed transaction, in zatoshis.
uint64 feeRequired = 2;
}
// A proposed change output. If the transparent value pool is selected,
// the `memo` field must be null.
message ChangeValue {
// The value of a change output to be created, in zatoshis.
uint64 value = 1;
// The value pool in which the change output should be created.
ValuePool valuePool = 2;
// The optional memo that should be associated with the newly created change output.
// Memos must not be present for transparent change outputs.
MemoBytes memo = 3;
}

View File

@ -622,10 +622,6 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
shieldingThreshold: Zatoshi,
transparentReceiver: String?
) async throws -> FfiProposal? {
if transparentReceiver != nil {
throw ZcashError.rustScanBlocks("TODO: Implement transparentReceiver support in FFI")
}
globalDBLock.lock()
let proposal = zcashlc_propose_shielding(
dbData.0,
@ -633,6 +629,7 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
account,
memo?.bytes,
UInt64(shieldingThreshold.amount),
transparentReceiver.map { [CChar]($0.utf8CString) },
networkType.networkId,
minimumConfirmations,
useZIP317Fees
@ -651,44 +648,46 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
))
}
func createProposedTransaction(
func createProposedTransactions(
proposal: FfiProposal,
usk: UnifiedSpendingKey
) async throws -> Data {
var contiguousTxIdBytes = ContiguousArray<UInt8>([UInt8](repeating: 0x0, count: 32))
) async throws -> [Data] {
let proposalBytes = try proposal.serializedData(partial: false).bytes
globalDBLock.lock()
let success = contiguousTxIdBytes.withUnsafeMutableBufferPointer { txIdBytePtr in
proposalBytes.withUnsafeBufferPointer { proposalPtr in
usk.bytes.withUnsafeBufferPointer { uskPtr in
zcashlc_create_proposed_transaction(
dbData.0,
dbData.1,
proposalPtr.baseAddress,
UInt(proposalBytes.count),
uskPtr.baseAddress,
UInt(usk.bytes.count),
spendParamsPath.0,
spendParamsPath.1,
outputParamsPath.0,
outputParamsPath.1,
networkType.networkId,
txIdBytePtr.baseAddress
)
}
let txIdsPtr = proposalBytes.withUnsafeBufferPointer { proposalPtr in
usk.bytes.withUnsafeBufferPointer { uskPtr in
zcashlc_create_proposed_transactions(
dbData.0,
dbData.1,
proposalPtr.baseAddress,
UInt(proposalBytes.count),
uskPtr.baseAddress,
UInt(usk.bytes.count),
spendParamsPath.0,
spendParamsPath.1,
outputParamsPath.0,
outputParamsPath.1,
networkType.networkId
)
}
}
globalDBLock.unlock()
guard success else {
guard let txIdsPtr else {
throw ZcashError.rustCreateToAddress(lastErrorMessage(fallback: "`createToAddress` failed with unknown error"))
}
return contiguousTxIdBytes.withUnsafeBufferPointer { txIdBytePtr in
Data(txIdBytePtr)
defer { zcashlc_free_txids(txIdsPtr) }
var txIds: [Data] = []
for i in (0 ..< Int(txIdsPtr.pointee.len)) {
let txId = FfiTxId(tuple: txIdsPtr.pointee.ptr.advanced(by: i).pointee)
txIds.append(Data(txId.array))
}
return txIds
}
nonisolated func consensusBranchIdFor(height: Int32) throws -> Int32 {
@ -828,3 +827,12 @@ extension FfiScanProgress {
)
}
}
struct FfiTxId {
var tuple: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
var array: [UInt8] {
withUnsafeBytes(of: self.tuple) { buf in
[UInt8](buf)
}
}
}

View File

@ -233,10 +233,10 @@ protocol ZcashRustBackendWelding {
/// - Parameter proposal: the transaction proposal.
/// - Parameter usk: `UnifiedSpendingKey` for the account that controls the funds to be spent.
/// - Throws: `rustCreateToAddress`.
func createProposedTransaction(
func createProposedTransactions(
proposal: FfiProposal,
usk: UnifiedSpendingKey
) async throws -> Data
) async throws -> [Data]
/// Gets the consensus branch id for the given height
/// - Parameter height: the height you what to know the branch id for

View File

@ -95,13 +95,20 @@ class WalletTransactionEncoder: TransactionEncoder {
throw ZcashError.walletTransEncoderCreateTransactionMissingSaplingParams
}
let txId = try await rustBackend.createProposedTransaction(
let txIds = try await rustBackend.createProposedTransactions(
proposal: proposal.inner,
usk: spendingKey
)
logger.debug("transaction id: \(txId)")
return [try await repository.find(rawID: txId)]
logger.debug("transaction ids: \(txIds)")
var txs: [ZcashTransaction.Overview] = []
for txId in txIds {
txs.append(try await repository.find(rawID: txId))
}
return txs
}
func submit(

View File

@ -2859,36 +2859,36 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
}
}
// MARK: - createProposedTransaction
// MARK: - createProposedTransactions
var createProposedTransactionProposalUskThrowableError: Error?
func setCreateProposedTransactionProposalUskThrowableError(_ param: Error?) async {
createProposedTransactionProposalUskThrowableError = param
var createProposedTransactionsProposalUskThrowableError: Error?
func setCreateProposedTransactionsProposalUskThrowableError(_ param: Error?) async {
createProposedTransactionsProposalUskThrowableError = param
}
var createProposedTransactionProposalUskCallsCount = 0
var createProposedTransactionProposalUskCalled: Bool {
return createProposedTransactionProposalUskCallsCount > 0
var createProposedTransactionsProposalUskCallsCount = 0
var createProposedTransactionsProposalUskCalled: Bool {
return createProposedTransactionsProposalUskCallsCount > 0
}
var createProposedTransactionProposalUskReceivedArguments: (proposal: FfiProposal, usk: UnifiedSpendingKey)?
var createProposedTransactionProposalUskReturnValue: Data!
func setCreateProposedTransactionProposalUskReturnValue(_ param: Data) async {
createProposedTransactionProposalUskReturnValue = param
var createProposedTransactionsProposalUskReceivedArguments: (proposal: FfiProposal, usk: UnifiedSpendingKey)?
var createProposedTransactionsProposalUskReturnValue: [Data]!
func setCreateProposedTransactionsProposalUskReturnValue(_ param: [Data]) async {
createProposedTransactionsProposalUskReturnValue = param
}
var createProposedTransactionProposalUskClosure: ((FfiProposal, UnifiedSpendingKey) async throws -> Data)?
func setCreateProposedTransactionProposalUskClosure(_ param: ((FfiProposal, UnifiedSpendingKey) async throws -> Data)?) async {
createProposedTransactionProposalUskClosure = param
var createProposedTransactionsProposalUskClosure: ((FfiProposal, UnifiedSpendingKey) async throws -> [Data])?
func setCreateProposedTransactionsProposalUskClosure(_ param: ((FfiProposal, UnifiedSpendingKey) async throws -> [Data])?) async {
createProposedTransactionsProposalUskClosure = param
}
func createProposedTransaction(proposal: FfiProposal, usk: UnifiedSpendingKey) async throws -> Data {
if let error = createProposedTransactionProposalUskThrowableError {
func createProposedTransactions(proposal: FfiProposal, usk: UnifiedSpendingKey) async throws -> [Data] {
if let error = createProposedTransactionsProposalUskThrowableError {
throw error
}
createProposedTransactionProposalUskCallsCount += 1
createProposedTransactionProposalUskReceivedArguments = (proposal: proposal, usk: usk)
if let closure = createProposedTransactionProposalUskClosure {
createProposedTransactionsProposalUskCallsCount += 1
createProposedTransactionsProposalUskReceivedArguments = (proposal: proposal, usk: usk)
if let closure = createProposedTransactionsProposalUskClosure {
return try await closure(proposal, usk)
} else {
return createProposedTransactionProposalUskReturnValue
return createProposedTransactionsProposalUskReturnValue
}
}

View File

@ -85,7 +85,7 @@ class RustBackendMockHelper {
await rustBackendMock.setPutUnspentTransparentOutputTxidIndexScriptValueHeightClosure() { _, _, _, _, _ in }
await rustBackendMock.setProposeTransferAccountToValueMemoThrowableError(ZcashError.rustCreateToAddress("mocked error"))
await rustBackendMock.setProposeShieldingAccountMemoShieldingThresholdTransparentReceiverThrowableError(ZcashError.rustShieldFunds("mocked error"))
await rustBackendMock.setCreateProposedTransactionProposalUskThrowableError(ZcashError.rustCreateToAddress("mocked error"))
await rustBackendMock.setCreateProposedTransactionsProposalUskThrowableError(ZcashError.rustCreateToAddress("mocked error"))
await rustBackendMock.setDecryptAndStoreTransactionTxBytesMinedHeightThrowableError(ZcashError.rustDecryptAndStoreTransaction("mock fail"))
await rustBackendMock.setInitDataDbSeedClosure() { seed in