diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 5d0f5a09..6dcc92b6 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -13,6 +13,10 @@ jobs: strategy: matrix: go: ["1.20", "1.21"] + rust: + - version: stable + clippy: true + - version: 1.72.0 # As specified in rust-toolchain steps: - uses: actions/checkout@v3 @@ -21,11 +25,18 @@ jobs: with: go-version: ${{ matrix.go }} + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust.version }} + override: true + profile: minimal + - name: Build - run: go build -v ./... + run: make all - name: Install Linters run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 - name: Test - run: sh ./goclean.sh + run: make test diff --git a/.gitignore b/.gitignore index 967bcd2e..18be0c3d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,10 @@ profile.tmp profile.cov utreexod + +# Cargo +target/ +Cargo.lock + +# Running stuff +/signet diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..67a1f185 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] + +members = [ + "bdkwallet/bdkgo_crate", +] + +resolver = "2" diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..571a6635 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +help: ## Display help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +install-rust: ## Install rust if not installed + if ! type cargo; then curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ; fi + +install-uniffi-bindgen-go: install-rust ## Install uniffi-bindgen-go if not installed + if ! type uniffi-bindgen-go; then cargo install uniffi-bindgen-go --git https://github.com/NordSecurity/uniffi-bindgen-go --tag v0.2.0+v0.25.0 ; fi + +build-bdk: install-uniffi-bindgen-go ## Build BDK static library + uniffi-bindgen-go -o bdkwallet bdkwallet/bdkgo_crate/src/bdkgo.udl + cargo build --release + +build-utreexod: build-bdk ## Build utreexod with all features + go build --tags=bdkwallet -o . ./... + +build-utreexod-without-bdk: ## Build utreexod without BDK wallet + go build -o . ./... + +test: build-bdk ## Run all test + cargo test + sh ./goclean.sh + +all: build-bdk build-utreexod ## Build all + +clean: ## Clean all + rm utreexod + go clean + cargo clean diff --git a/bdkwallet/bdkgo/bdkgo.c b/bdkwallet/bdkgo/bdkgo.c new file mode 100644 index 00000000..2bd64c01 --- /dev/null +++ b/bdkwallet/bdkgo/bdkgo.c @@ -0,0 +1,8 @@ +#include + +// This file exists beacause of +// https://github.com/golang/go/issues/11263 + +void cgo_rust_task_callback_bridge_bdkgo(RustTaskCallback cb, const void * taskData, int8_t status) { + cb(taskData, status); +} \ No newline at end of file diff --git a/bdkwallet/bdkgo/bdkgo.go b/bdkwallet/bdkgo/bdkgo.go new file mode 100644 index 00000000..73e2db1e --- /dev/null +++ b/bdkwallet/bdkgo/bdkgo.go @@ -0,0 +1,2260 @@ +package bdkgo + +// #include +import "C" + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "math" + "runtime" + "sync/atomic" + "unsafe" +) + +type RustBuffer = C.RustBuffer + +type RustBufferI interface { + AsReader() *bytes.Reader + Free() + ToGoBytes() []byte + Data() unsafe.Pointer + Len() int + Capacity() int +} + +func RustBufferFromExternal(b RustBufferI) RustBuffer { + return RustBuffer{ + capacity: C.int(b.Capacity()), + len: C.int(b.Len()), + data: (*C.uchar)(b.Data()), + } +} + +func (cb RustBuffer) Capacity() int { + return int(cb.capacity) +} + +func (cb RustBuffer) Len() int { + return int(cb.len) +} + +func (cb RustBuffer) Data() unsafe.Pointer { + return unsafe.Pointer(cb.data) +} + +func (cb RustBuffer) AsReader() *bytes.Reader { + b := unsafe.Slice((*byte)(cb.data), C.int(cb.len)) + return bytes.NewReader(b) +} + +func (cb RustBuffer) Free() { + rustCall(func(status *C.RustCallStatus) bool { + C.ffi_bdkgo_rustbuffer_free(cb, status) + return false + }) +} + +func (cb RustBuffer) ToGoBytes() []byte { + return C.GoBytes(unsafe.Pointer(cb.data), C.int(cb.len)) +} + +func stringToRustBuffer(str string) RustBuffer { + return bytesToRustBuffer([]byte(str)) +} + +func bytesToRustBuffer(b []byte) RustBuffer { + if len(b) == 0 { + return RustBuffer{} + } + // We can pass the pointer along here, as it is pinned + // for the duration of this call + foreign := C.ForeignBytes{ + len: C.int(len(b)), + data: (*C.uchar)(unsafe.Pointer(&b[0])), + } + + return rustCall(func(status *C.RustCallStatus) RustBuffer { + return C.ffi_bdkgo_rustbuffer_from_bytes(foreign, status) + }) +} + +type BufLifter[GoType any] interface { + Lift(value RustBufferI) GoType +} + +type BufLowerer[GoType any] interface { + Lower(value GoType) RustBuffer +} + +type FfiConverter[GoType any, FfiType any] interface { + Lift(value FfiType) GoType + Lower(value GoType) FfiType +} + +type BufReader[GoType any] interface { + Read(reader io.Reader) GoType +} + +type BufWriter[GoType any] interface { + Write(writer io.Writer, value GoType) +} + +type FfiRustBufConverter[GoType any, FfiType any] interface { + FfiConverter[GoType, FfiType] + BufReader[GoType] +} + +func LowerIntoRustBuffer[GoType any](bufWriter BufWriter[GoType], value GoType) RustBuffer { + // This might be not the most efficient way but it does not require knowing allocation size + // beforehand + var buffer bytes.Buffer + bufWriter.Write(&buffer, value) + + bytes, err := io.ReadAll(&buffer) + if err != nil { + panic(fmt.Errorf("reading written data: %w", err)) + } + return bytesToRustBuffer(bytes) +} + +func LiftFromRustBuffer[GoType any](bufReader BufReader[GoType], rbuf RustBufferI) GoType { + defer rbuf.Free() + reader := rbuf.AsReader() + item := bufReader.Read(reader) + if reader.Len() > 0 { + // TODO: Remove this + leftover, _ := io.ReadAll(reader) + panic(fmt.Errorf("Junk remaining in buffer after lifting: %s", string(leftover))) + } + return item +} + +func rustCallWithError[U any](converter BufLifter[error], callback func(*C.RustCallStatus) U) (U, error) { + var status C.RustCallStatus + returnValue := callback(&status) + err := checkCallStatus(converter, status) + + return returnValue, err +} + +func checkCallStatus(converter BufLifter[error], status C.RustCallStatus) error { + switch status.code { + case 0: + return nil + case 1: + return converter.Lift(status.errorBuf) + case 2: + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if status.errorBuf.len > 0 { + panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf))) + } else { + panic(fmt.Errorf("Rust panicked while handling Rust panic")) + } + default: + return fmt.Errorf("unknown status code: %d", status.code) + } +} + +func checkCallStatusUnknown(status C.RustCallStatus) error { + switch status.code { + case 0: + return nil + case 1: + panic(fmt.Errorf("function not returning an error returned an error")) + case 2: + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if status.errorBuf.len > 0 { + panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf))) + } else { + panic(fmt.Errorf("Rust panicked while handling Rust panic")) + } + default: + return fmt.Errorf("unknown status code: %d", status.code) + } +} + +func rustCall[U any](callback func(*C.RustCallStatus) U) U { + returnValue, err := rustCallWithError(nil, callback) + if err != nil { + panic(err) + } + return returnValue +} + +func writeInt8(writer io.Writer, value int8) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint8(writer io.Writer, value uint8) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeInt16(writer io.Writer, value int16) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint16(writer io.Writer, value uint16) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeInt32(writer io.Writer, value int32) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint32(writer io.Writer, value uint32) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeInt64(writer io.Writer, value int64) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeUint64(writer io.Writer, value uint64) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeFloat32(writer io.Writer, value float32) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func writeFloat64(writer io.Writer, value float64) { + if err := binary.Write(writer, binary.BigEndian, value); err != nil { + panic(err) + } +} + +func readInt8(reader io.Reader) int8 { + var result int8 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint8(reader io.Reader) uint8 { + var result uint8 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readInt16(reader io.Reader) int16 { + var result int16 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint16(reader io.Reader) uint16 { + var result uint16 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readInt32(reader io.Reader) int32 { + var result int32 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint32(reader io.Reader) uint32 { + var result uint32 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readInt64(reader io.Reader) int64 { + var result int64 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readUint64(reader io.Reader) uint64 { + var result uint64 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readFloat32(reader io.Reader) float32 { + var result float32 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func readFloat64(reader io.Reader) float64 { + var result float64 + if err := binary.Read(reader, binary.BigEndian, &result); err != nil { + panic(err) + } + return result +} + +func init() { + + uniffiCheckChecksums() +} + +func uniffiCheckChecksums() { + // Get the bindings contract version from our ComponentInterface + bindingsContractVersion := 24 + // Get the scaffolding contract version by calling the into the dylib + scaffoldingContractVersion := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint32_t { + return C.ffi_bdkgo_uniffi_contract_version(uniffiStatus) + }) + if bindingsContractVersion != int(scaffoldingContractVersion) { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: UniFFI contract version mismatch") + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_apply_block(uniffiStatus) + }) + if checksum != 38677 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_apply_block: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_apply_mempool(uniffiStatus) + }) + if checksum != 15979 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_apply_mempool: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_balance(uniffiStatus) + }) + if checksum != 26195 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_balance: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_create_tx(uniffiStatus) + }) + if checksum != 54855 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_create_tx: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_fresh_address(uniffiStatus) + }) + if checksum != 39819 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_fresh_address: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_genesis_hash(uniffiStatus) + }) + if checksum != 65013 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_genesis_hash: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_increment_reference_counter(uniffiStatus) + }) + if checksum != 61284 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_increment_reference_counter: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_last_unused_address(uniffiStatus) + }) + if checksum != 34780 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_last_unused_address: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_mnemonic_words(uniffiStatus) + }) + if checksum != 3138 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_mnemonic_words: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_peek_address(uniffiStatus) + }) + if checksum != 60510 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_peek_address: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_recent_blocks(uniffiStatus) + }) + if checksum != 57902 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_recent_blocks: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_transactions(uniffiStatus) + }) + if checksum != 39598 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_transactions: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_method_wallet_utxos(uniffiStatus) + }) + if checksum != 53540 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_method_wallet_utxos: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_constructor_wallet_create_new(uniffiStatus) + }) + if checksum != 23490 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_constructor_wallet_create_new: UniFFI API checksum mismatch") + } + } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_bdkgo_checksum_constructor_wallet_load(uniffiStatus) + }) + if checksum != 64083 { + // If this happens try cleaning and rebuilding your project + panic("bdkgo: uniffi_bdkgo_checksum_constructor_wallet_load: UniFFI API checksum mismatch") + } + } +} + +type FfiConverterUint32 struct{} + +var FfiConverterUint32INSTANCE = FfiConverterUint32{} + +func (FfiConverterUint32) Lower(value uint32) C.uint32_t { + return C.uint32_t(value) +} + +func (FfiConverterUint32) Write(writer io.Writer, value uint32) { + writeUint32(writer, value) +} + +func (FfiConverterUint32) Lift(value C.uint32_t) uint32 { + return uint32(value) +} + +func (FfiConverterUint32) Read(reader io.Reader) uint32 { + return readUint32(reader) +} + +type FfiDestroyerUint32 struct{} + +func (FfiDestroyerUint32) Destroy(_ uint32) {} + +type FfiConverterUint64 struct{} + +var FfiConverterUint64INSTANCE = FfiConverterUint64{} + +func (FfiConverterUint64) Lower(value uint64) C.uint64_t { + return C.uint64_t(value) +} + +func (FfiConverterUint64) Write(writer io.Writer, value uint64) { + writeUint64(writer, value) +} + +func (FfiConverterUint64) Lift(value C.uint64_t) uint64 { + return uint64(value) +} + +func (FfiConverterUint64) Read(reader io.Reader) uint64 { + return readUint64(reader) +} + +type FfiDestroyerUint64 struct{} + +func (FfiDestroyerUint64) Destroy(_ uint64) {} + +type FfiConverterFloat32 struct{} + +var FfiConverterFloat32INSTANCE = FfiConverterFloat32{} + +func (FfiConverterFloat32) Lower(value float32) C.float { + return C.float(value) +} + +func (FfiConverterFloat32) Write(writer io.Writer, value float32) { + writeFloat32(writer, value) +} + +func (FfiConverterFloat32) Lift(value C.float) float32 { + return float32(value) +} + +func (FfiConverterFloat32) Read(reader io.Reader) float32 { + return readFloat32(reader) +} + +type FfiDestroyerFloat32 struct{} + +func (FfiDestroyerFloat32) Destroy(_ float32) {} + +type FfiConverterBool struct{} + +var FfiConverterBoolINSTANCE = FfiConverterBool{} + +func (FfiConverterBool) Lower(value bool) C.int8_t { + if value { + return C.int8_t(1) + } + return C.int8_t(0) +} + +func (FfiConverterBool) Write(writer io.Writer, value bool) { + if value { + writeInt8(writer, 1) + } else { + writeInt8(writer, 0) + } +} + +func (FfiConverterBool) Lift(value C.int8_t) bool { + return value != 0 +} + +func (FfiConverterBool) Read(reader io.Reader) bool { + return readInt8(reader) != 0 +} + +type FfiDestroyerBool struct{} + +func (FfiDestroyerBool) Destroy(_ bool) {} + +type FfiConverterString struct{} + +var FfiConverterStringINSTANCE = FfiConverterString{} + +func (FfiConverterString) Lift(rb RustBufferI) string { + defer rb.Free() + reader := rb.AsReader() + b, err := io.ReadAll(reader) + if err != nil { + panic(fmt.Errorf("reading reader: %w", err)) + } + return string(b) +} + +func (FfiConverterString) Read(reader io.Reader) string { + length := readInt32(reader) + buffer := make([]byte, length) + read_length, err := reader.Read(buffer) + if err != nil { + panic(err) + } + if read_length != int(length) { + panic(fmt.Errorf("bad read length when reading string, expected %d, read %d", length, read_length)) + } + return string(buffer) +} + +func (FfiConverterString) Lower(value string) RustBuffer { + return stringToRustBuffer(value) +} + +func (FfiConverterString) Write(writer io.Writer, value string) { + if len(value) > math.MaxInt32 { + panic("String is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + write_length, err := io.WriteString(writer, value) + if err != nil { + panic(err) + } + if write_length != len(value) { + panic(fmt.Errorf("bad write length when writing string, expected %d, written %d", len(value), write_length)) + } +} + +type FfiDestroyerString struct{} + +func (FfiDestroyerString) Destroy(_ string) {} + +type FfiConverterBytes struct{} + +var FfiConverterBytesINSTANCE = FfiConverterBytes{} + +func (c FfiConverterBytes) Lower(value []byte) RustBuffer { + return LowerIntoRustBuffer[[]byte](c, value) +} + +func (c FfiConverterBytes) Write(writer io.Writer, value []byte) { + if len(value) > math.MaxInt32 { + panic("[]byte is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + write_length, err := writer.Write(value) + if err != nil { + panic(err) + } + if write_length != len(value) { + panic(fmt.Errorf("bad write length when writing []byte, expected %d, written %d", len(value), write_length)) + } +} + +func (c FfiConverterBytes) Lift(rb RustBufferI) []byte { + return LiftFromRustBuffer[[]byte](c, rb) +} + +func (c FfiConverterBytes) Read(reader io.Reader) []byte { + length := readInt32(reader) + buffer := make([]byte, length) + read_length, err := reader.Read(buffer) + if err != nil { + panic(err) + } + if read_length != int(length) { + panic(fmt.Errorf("bad read length when reading []byte, expected %d, read %d", length, read_length)) + } + return buffer +} + +type FfiDestroyerBytes struct{} + +func (FfiDestroyerBytes) Destroy(_ []byte) {} + +// Below is an implementation of synchronization requirements outlined in the link. +// https://github.com/mozilla/uniffi-rs/blob/0dc031132d9493ca812c3af6e7dd60ad2ea95bf0/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt#L31 + +type FfiObject struct { + pointer unsafe.Pointer + callCounter atomic.Int64 + freeFunction func(unsafe.Pointer, *C.RustCallStatus) + destroyed atomic.Bool +} + +func newFfiObject(pointer unsafe.Pointer, freeFunction func(unsafe.Pointer, *C.RustCallStatus)) FfiObject { + return FfiObject{ + pointer: pointer, + freeFunction: freeFunction, + } +} + +func (ffiObject *FfiObject) incrementPointer(debugName string) unsafe.Pointer { + for { + counter := ffiObject.callCounter.Load() + if counter <= -1 { + panic(fmt.Errorf("%v object has already been destroyed", debugName)) + } + if counter == math.MaxInt64 { + panic(fmt.Errorf("%v object call counter would overflow", debugName)) + } + if ffiObject.callCounter.CompareAndSwap(counter, counter+1) { + break + } + } + + return ffiObject.pointer +} + +func (ffiObject *FfiObject) decrementPointer() { + if ffiObject.callCounter.Add(-1) == -1 { + ffiObject.freeRustArcPtr() + } +} + +func (ffiObject *FfiObject) destroy() { + if ffiObject.destroyed.CompareAndSwap(false, true) { + if ffiObject.callCounter.Add(-1) == -1 { + ffiObject.freeRustArcPtr() + } + } +} + +func (ffiObject *FfiObject) freeRustArcPtr() { + rustCall(func(status *C.RustCallStatus) int32 { + ffiObject.freeFunction(ffiObject.pointer, status) + return 0 + }) +} + +type Wallet struct { + ffiObject FfiObject +} + +func WalletCreateNew(dbPath string, network string, genesisHash []byte) (*Wallet, error) { + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeCreateNewError{}, func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { + return C.uniffi_bdkgo_fn_constructor_wallet_create_new(FfiConverterStringINSTANCE.Lower(dbPath), FfiConverterStringINSTANCE.Lower(network), FfiConverterBytesINSTANCE.Lower(genesisHash), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue *Wallet + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterWalletINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} +func WalletLoad(dbPath string) (*Wallet, error) { + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeLoadError{}, func(_uniffiStatus *C.RustCallStatus) unsafe.Pointer { + return C.uniffi_bdkgo_fn_constructor_wallet_load(FfiConverterStringINSTANCE.Lower(dbPath), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue *Wallet + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterWalletINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Wallet) ApplyBlock(height uint32, blockBytes []byte) (ApplyResult, error) { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeApplyBlockError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_apply_block( + _pointer, FfiConverterUint32INSTANCE.Lower(height), FfiConverterBytesINSTANCE.Lower(blockBytes), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue ApplyResult + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterTypeApplyResultINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Wallet) ApplyMempool(txs []MempoolTx) (ApplyResult, error) { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeApplyMempoolError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_apply_mempool( + _pointer, FfiConverterSequenceTypeMempoolTxINSTANCE.Lower(txs), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue ApplyResult + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterTypeApplyResultINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Wallet) Balance() Balance { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + return FfiConverterTypeBalanceINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_balance( + _pointer, _uniffiStatus) + })) +} + +func (_self *Wallet) CreateTx(feerate float32, recipients []Recipient) ([]byte, error) { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeCreateTxError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_create_tx( + _pointer, FfiConverterFloat32INSTANCE.Lower(feerate), FfiConverterSequenceTypeRecipientINSTANCE.Lower(recipients), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue []byte + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterBytesINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Wallet) FreshAddress() (AddressInfo, error) { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeDatabaseError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_fresh_address( + _pointer, _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue AddressInfo + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterTypeAddressInfoINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Wallet) GenesisHash() []byte { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + return FfiConverterBytesINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_genesis_hash( + _pointer, _uniffiStatus) + })) +} + +func (_self *Wallet) IncrementReferenceCounter() { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + rustCall(func(_uniffiStatus *C.RustCallStatus) bool { + C.uniffi_bdkgo_fn_method_wallet_increment_reference_counter( + _pointer, _uniffiStatus) + return false + }) +} + +func (_self *Wallet) LastUnusedAddress() (AddressInfo, error) { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeDatabaseError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_last_unused_address( + _pointer, _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue AddressInfo + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterTypeAddressInfoINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Wallet) MnemonicWords() []string { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + return FfiConverterSequenceStringINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_mnemonic_words( + _pointer, _uniffiStatus) + })) +} + +func (_self *Wallet) PeekAddress(index uint32) (AddressInfo, error) { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeDatabaseError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_peek_address( + _pointer, FfiConverterUint32INSTANCE.Lower(index), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue AddressInfo + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterTypeAddressInfoINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + +func (_self *Wallet) RecentBlocks(count uint32) []BlockId { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + return FfiConverterSequenceTypeBlockIdINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_recent_blocks( + _pointer, FfiConverterUint32INSTANCE.Lower(count), _uniffiStatus) + })) +} + +func (_self *Wallet) Transactions() []TxInfo { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + return FfiConverterSequenceTypeTxInfoINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_transactions( + _pointer, _uniffiStatus) + })) +} + +func (_self *Wallet) Utxos() []UtxoInfo { + _pointer := _self.ffiObject.incrementPointer("*Wallet") + defer _self.ffiObject.decrementPointer() + return FfiConverterSequenceTypeUtxoInfoINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_bdkgo_fn_method_wallet_utxos( + _pointer, _uniffiStatus) + })) +} + +func (object *Wallet) Destroy() { + runtime.SetFinalizer(object, nil) + object.ffiObject.destroy() +} + +type FfiConverterWallet struct{} + +var FfiConverterWalletINSTANCE = FfiConverterWallet{} + +func (c FfiConverterWallet) Lift(pointer unsafe.Pointer) *Wallet { + result := &Wallet{ + newFfiObject( + pointer, + func(pointer unsafe.Pointer, status *C.RustCallStatus) { + C.uniffi_bdkgo_fn_free_wallet(pointer, status) + }), + } + runtime.SetFinalizer(result, (*Wallet).Destroy) + return result +} + +func (c FfiConverterWallet) Read(reader io.Reader) *Wallet { + return c.Lift(unsafe.Pointer(uintptr(readUint64(reader)))) +} + +func (c FfiConverterWallet) Lower(value *Wallet) unsafe.Pointer { + // TODO: this is bad - all synchronization from ObjectRuntime.go is discarded here, + // because the pointer will be decremented immediately after this function returns, + // and someone will be left holding onto a non-locked pointer. + pointer := value.ffiObject.incrementPointer("*Wallet") + defer value.ffiObject.decrementPointer() + return pointer +} + +func (c FfiConverterWallet) Write(writer io.Writer, value *Wallet) { + writeUint64(writer, uint64(uintptr(c.Lower(value)))) +} + +type FfiDestroyerWallet struct{} + +func (_ FfiDestroyerWallet) Destroy(value *Wallet) { + value.Destroy() +} + +type AddressInfo struct { + Index uint32 + Address string +} + +func (r *AddressInfo) Destroy() { + FfiDestroyerUint32{}.Destroy(r.Index) + FfiDestroyerString{}.Destroy(r.Address) +} + +type FfiConverterTypeAddressInfo struct{} + +var FfiConverterTypeAddressInfoINSTANCE = FfiConverterTypeAddressInfo{} + +func (c FfiConverterTypeAddressInfo) Lift(rb RustBufferI) AddressInfo { + return LiftFromRustBuffer[AddressInfo](c, rb) +} + +func (c FfiConverterTypeAddressInfo) Read(reader io.Reader) AddressInfo { + return AddressInfo{ + FfiConverterUint32INSTANCE.Read(reader), + FfiConverterStringINSTANCE.Read(reader), + } +} + +func (c FfiConverterTypeAddressInfo) Lower(value AddressInfo) RustBuffer { + return LowerIntoRustBuffer[AddressInfo](c, value) +} + +func (c FfiConverterTypeAddressInfo) Write(writer io.Writer, value AddressInfo) { + FfiConverterUint32INSTANCE.Write(writer, value.Index) + FfiConverterStringINSTANCE.Write(writer, value.Address) +} + +type FfiDestroyerTypeAddressInfo struct{} + +func (_ FfiDestroyerTypeAddressInfo) Destroy(value AddressInfo) { + value.Destroy() +} + +type ApplyResult struct { + RelevantTxids [][]byte +} + +func (r *ApplyResult) Destroy() { + FfiDestroyerSequenceBytes{}.Destroy(r.RelevantTxids) +} + +type FfiConverterTypeApplyResult struct{} + +var FfiConverterTypeApplyResultINSTANCE = FfiConverterTypeApplyResult{} + +func (c FfiConverterTypeApplyResult) Lift(rb RustBufferI) ApplyResult { + return LiftFromRustBuffer[ApplyResult](c, rb) +} + +func (c FfiConverterTypeApplyResult) Read(reader io.Reader) ApplyResult { + return ApplyResult{ + FfiConverterSequenceBytesINSTANCE.Read(reader), + } +} + +func (c FfiConverterTypeApplyResult) Lower(value ApplyResult) RustBuffer { + return LowerIntoRustBuffer[ApplyResult](c, value) +} + +func (c FfiConverterTypeApplyResult) Write(writer io.Writer, value ApplyResult) { + FfiConverterSequenceBytesINSTANCE.Write(writer, value.RelevantTxids) +} + +type FfiDestroyerTypeApplyResult struct{} + +func (_ FfiDestroyerTypeApplyResult) Destroy(value ApplyResult) { + value.Destroy() +} + +type Balance struct { + Immature uint64 + TrustedPending uint64 + UntrustedPending uint64 + Confirmed uint64 +} + +func (r *Balance) Destroy() { + FfiDestroyerUint64{}.Destroy(r.Immature) + FfiDestroyerUint64{}.Destroy(r.TrustedPending) + FfiDestroyerUint64{}.Destroy(r.UntrustedPending) + FfiDestroyerUint64{}.Destroy(r.Confirmed) +} + +type FfiConverterTypeBalance struct{} + +var FfiConverterTypeBalanceINSTANCE = FfiConverterTypeBalance{} + +func (c FfiConverterTypeBalance) Lift(rb RustBufferI) Balance { + return LiftFromRustBuffer[Balance](c, rb) +} + +func (c FfiConverterTypeBalance) Read(reader io.Reader) Balance { + return Balance{ + FfiConverterUint64INSTANCE.Read(reader), + FfiConverterUint64INSTANCE.Read(reader), + FfiConverterUint64INSTANCE.Read(reader), + FfiConverterUint64INSTANCE.Read(reader), + } +} + +func (c FfiConverterTypeBalance) Lower(value Balance) RustBuffer { + return LowerIntoRustBuffer[Balance](c, value) +} + +func (c FfiConverterTypeBalance) Write(writer io.Writer, value Balance) { + FfiConverterUint64INSTANCE.Write(writer, value.Immature) + FfiConverterUint64INSTANCE.Write(writer, value.TrustedPending) + FfiConverterUint64INSTANCE.Write(writer, value.UntrustedPending) + FfiConverterUint64INSTANCE.Write(writer, value.Confirmed) +} + +type FfiDestroyerTypeBalance struct{} + +func (_ FfiDestroyerTypeBalance) Destroy(value Balance) { + value.Destroy() +} + +type BlockId struct { + Height uint32 + Hash []byte +} + +func (r *BlockId) Destroy() { + FfiDestroyerUint32{}.Destroy(r.Height) + FfiDestroyerBytes{}.Destroy(r.Hash) +} + +type FfiConverterTypeBlockId struct{} + +var FfiConverterTypeBlockIdINSTANCE = FfiConverterTypeBlockId{} + +func (c FfiConverterTypeBlockId) Lift(rb RustBufferI) BlockId { + return LiftFromRustBuffer[BlockId](c, rb) +} + +func (c FfiConverterTypeBlockId) Read(reader io.Reader) BlockId { + return BlockId{ + FfiConverterUint32INSTANCE.Read(reader), + FfiConverterBytesINSTANCE.Read(reader), + } +} + +func (c FfiConverterTypeBlockId) Lower(value BlockId) RustBuffer { + return LowerIntoRustBuffer[BlockId](c, value) +} + +func (c FfiConverterTypeBlockId) Write(writer io.Writer, value BlockId) { + FfiConverterUint32INSTANCE.Write(writer, value.Height) + FfiConverterBytesINSTANCE.Write(writer, value.Hash) +} + +type FfiDestroyerTypeBlockId struct{} + +func (_ FfiDestroyerTypeBlockId) Destroy(value BlockId) { + value.Destroy() +} + +type MempoolTx struct { + Tx []byte + AddedUnix uint64 +} + +func (r *MempoolTx) Destroy() { + FfiDestroyerBytes{}.Destroy(r.Tx) + FfiDestroyerUint64{}.Destroy(r.AddedUnix) +} + +type FfiConverterTypeMempoolTx struct{} + +var FfiConverterTypeMempoolTxINSTANCE = FfiConverterTypeMempoolTx{} + +func (c FfiConverterTypeMempoolTx) Lift(rb RustBufferI) MempoolTx { + return LiftFromRustBuffer[MempoolTx](c, rb) +} + +func (c FfiConverterTypeMempoolTx) Read(reader io.Reader) MempoolTx { + return MempoolTx{ + FfiConverterBytesINSTANCE.Read(reader), + FfiConverterUint64INSTANCE.Read(reader), + } +} + +func (c FfiConverterTypeMempoolTx) Lower(value MempoolTx) RustBuffer { + return LowerIntoRustBuffer[MempoolTx](c, value) +} + +func (c FfiConverterTypeMempoolTx) Write(writer io.Writer, value MempoolTx) { + FfiConverterBytesINSTANCE.Write(writer, value.Tx) + FfiConverterUint64INSTANCE.Write(writer, value.AddedUnix) +} + +type FfiDestroyerTypeMempoolTx struct{} + +func (_ FfiDestroyerTypeMempoolTx) Destroy(value MempoolTx) { + value.Destroy() +} + +type Recipient struct { + Address string + Amount uint64 +} + +func (r *Recipient) Destroy() { + FfiDestroyerString{}.Destroy(r.Address) + FfiDestroyerUint64{}.Destroy(r.Amount) +} + +type FfiConverterTypeRecipient struct{} + +var FfiConverterTypeRecipientINSTANCE = FfiConverterTypeRecipient{} + +func (c FfiConverterTypeRecipient) Lift(rb RustBufferI) Recipient { + return LiftFromRustBuffer[Recipient](c, rb) +} + +func (c FfiConverterTypeRecipient) Read(reader io.Reader) Recipient { + return Recipient{ + FfiConverterStringINSTANCE.Read(reader), + FfiConverterUint64INSTANCE.Read(reader), + } +} + +func (c FfiConverterTypeRecipient) Lower(value Recipient) RustBuffer { + return LowerIntoRustBuffer[Recipient](c, value) +} + +func (c FfiConverterTypeRecipient) Write(writer io.Writer, value Recipient) { + FfiConverterStringINSTANCE.Write(writer, value.Address) + FfiConverterUint64INSTANCE.Write(writer, value.Amount) +} + +type FfiDestroyerTypeRecipient struct{} + +func (_ FfiDestroyerTypeRecipient) Destroy(value Recipient) { + value.Destroy() +} + +type TxInfo struct { + Txid []byte + Tx []byte + Spent uint64 + Received uint64 + Confirmations uint32 +} + +func (r *TxInfo) Destroy() { + FfiDestroyerBytes{}.Destroy(r.Txid) + FfiDestroyerBytes{}.Destroy(r.Tx) + FfiDestroyerUint64{}.Destroy(r.Spent) + FfiDestroyerUint64{}.Destroy(r.Received) + FfiDestroyerUint32{}.Destroy(r.Confirmations) +} + +type FfiConverterTypeTxInfo struct{} + +var FfiConverterTypeTxInfoINSTANCE = FfiConverterTypeTxInfo{} + +func (c FfiConverterTypeTxInfo) Lift(rb RustBufferI) TxInfo { + return LiftFromRustBuffer[TxInfo](c, rb) +} + +func (c FfiConverterTypeTxInfo) Read(reader io.Reader) TxInfo { + return TxInfo{ + FfiConverterBytesINSTANCE.Read(reader), + FfiConverterBytesINSTANCE.Read(reader), + FfiConverterUint64INSTANCE.Read(reader), + FfiConverterUint64INSTANCE.Read(reader), + FfiConverterUint32INSTANCE.Read(reader), + } +} + +func (c FfiConverterTypeTxInfo) Lower(value TxInfo) RustBuffer { + return LowerIntoRustBuffer[TxInfo](c, value) +} + +func (c FfiConverterTypeTxInfo) Write(writer io.Writer, value TxInfo) { + FfiConverterBytesINSTANCE.Write(writer, value.Txid) + FfiConverterBytesINSTANCE.Write(writer, value.Tx) + FfiConverterUint64INSTANCE.Write(writer, value.Spent) + FfiConverterUint64INSTANCE.Write(writer, value.Received) + FfiConverterUint32INSTANCE.Write(writer, value.Confirmations) +} + +type FfiDestroyerTypeTxInfo struct{} + +func (_ FfiDestroyerTypeTxInfo) Destroy(value TxInfo) { + value.Destroy() +} + +type UtxoInfo struct { + Txid []byte + Vout uint32 + Amount uint64 + ScriptPubkey []byte + IsChange bool + DerivationIndex uint32 + Confirmations uint32 +} + +func (r *UtxoInfo) Destroy() { + FfiDestroyerBytes{}.Destroy(r.Txid) + FfiDestroyerUint32{}.Destroy(r.Vout) + FfiDestroyerUint64{}.Destroy(r.Amount) + FfiDestroyerBytes{}.Destroy(r.ScriptPubkey) + FfiDestroyerBool{}.Destroy(r.IsChange) + FfiDestroyerUint32{}.Destroy(r.DerivationIndex) + FfiDestroyerUint32{}.Destroy(r.Confirmations) +} + +type FfiConverterTypeUtxoInfo struct{} + +var FfiConverterTypeUtxoInfoINSTANCE = FfiConverterTypeUtxoInfo{} + +func (c FfiConverterTypeUtxoInfo) Lift(rb RustBufferI) UtxoInfo { + return LiftFromRustBuffer[UtxoInfo](c, rb) +} + +func (c FfiConverterTypeUtxoInfo) Read(reader io.Reader) UtxoInfo { + return UtxoInfo{ + FfiConverterBytesINSTANCE.Read(reader), + FfiConverterUint32INSTANCE.Read(reader), + FfiConverterUint64INSTANCE.Read(reader), + FfiConverterBytesINSTANCE.Read(reader), + FfiConverterBoolINSTANCE.Read(reader), + FfiConverterUint32INSTANCE.Read(reader), + FfiConverterUint32INSTANCE.Read(reader), + } +} + +func (c FfiConverterTypeUtxoInfo) Lower(value UtxoInfo) RustBuffer { + return LowerIntoRustBuffer[UtxoInfo](c, value) +} + +func (c FfiConverterTypeUtxoInfo) Write(writer io.Writer, value UtxoInfo) { + FfiConverterBytesINSTANCE.Write(writer, value.Txid) + FfiConverterUint32INSTANCE.Write(writer, value.Vout) + FfiConverterUint64INSTANCE.Write(writer, value.Amount) + FfiConverterBytesINSTANCE.Write(writer, value.ScriptPubkey) + FfiConverterBoolINSTANCE.Write(writer, value.IsChange) + FfiConverterUint32INSTANCE.Write(writer, value.DerivationIndex) + FfiConverterUint32INSTANCE.Write(writer, value.Confirmations) +} + +type FfiDestroyerTypeUtxoInfo struct{} + +func (_ FfiDestroyerTypeUtxoInfo) Destroy(value UtxoInfo) { + value.Destroy() +} + +type ApplyBlockError struct { + err error +} + +func (err ApplyBlockError) Error() string { + return fmt.Sprintf("ApplyBlockError: %s", err.err.Error()) +} + +func (err ApplyBlockError) Unwrap() error { + return err.err +} + +// Err* are used for checking error type with `errors.Is` +var ErrApplyBlockErrorDecodeBlock = fmt.Errorf("ApplyBlockErrorDecodeBlock") +var ErrApplyBlockErrorCannotConnect = fmt.Errorf("ApplyBlockErrorCannotConnect") +var ErrApplyBlockErrorDatabase = fmt.Errorf("ApplyBlockErrorDatabase") + +// Variant structs +type ApplyBlockErrorDecodeBlock struct { + message string +} + +func NewApplyBlockErrorDecodeBlock() *ApplyBlockError { + return &ApplyBlockError{ + err: &ApplyBlockErrorDecodeBlock{}, + } +} + +func (err ApplyBlockErrorDecodeBlock) Error() string { + return fmt.Sprintf("DecodeBlock: %s", err.message) +} + +func (self ApplyBlockErrorDecodeBlock) Is(target error) bool { + return target == ErrApplyBlockErrorDecodeBlock +} + +type ApplyBlockErrorCannotConnect struct { + message string +} + +func NewApplyBlockErrorCannotConnect() *ApplyBlockError { + return &ApplyBlockError{ + err: &ApplyBlockErrorCannotConnect{}, + } +} + +func (err ApplyBlockErrorCannotConnect) Error() string { + return fmt.Sprintf("CannotConnect: %s", err.message) +} + +func (self ApplyBlockErrorCannotConnect) Is(target error) bool { + return target == ErrApplyBlockErrorCannotConnect +} + +type ApplyBlockErrorDatabase struct { + message string +} + +func NewApplyBlockErrorDatabase() *ApplyBlockError { + return &ApplyBlockError{ + err: &ApplyBlockErrorDatabase{}, + } +} + +func (err ApplyBlockErrorDatabase) Error() string { + return fmt.Sprintf("Database: %s", err.message) +} + +func (self ApplyBlockErrorDatabase) Is(target error) bool { + return target == ErrApplyBlockErrorDatabase +} + +type FfiConverterTypeApplyBlockError struct{} + +var FfiConverterTypeApplyBlockErrorINSTANCE = FfiConverterTypeApplyBlockError{} + +func (c FfiConverterTypeApplyBlockError) Lift(eb RustBufferI) error { + return LiftFromRustBuffer[error](c, eb) +} + +func (c FfiConverterTypeApplyBlockError) Lower(value *ApplyBlockError) RustBuffer { + return LowerIntoRustBuffer[*ApplyBlockError](c, value) +} + +func (c FfiConverterTypeApplyBlockError) Read(reader io.Reader) error { + errorID := readUint32(reader) + + message := FfiConverterStringINSTANCE.Read(reader) + switch errorID { + case 1: + return &ApplyBlockError{&ApplyBlockErrorDecodeBlock{message}} + case 2: + return &ApplyBlockError{&ApplyBlockErrorCannotConnect{message}} + case 3: + return &ApplyBlockError{&ApplyBlockErrorDatabase{message}} + default: + panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeApplyBlockError.Read()", errorID)) + } + +} + +func (c FfiConverterTypeApplyBlockError) Write(writer io.Writer, value *ApplyBlockError) { + switch variantValue := value.err.(type) { + case *ApplyBlockErrorDecodeBlock: + writeInt32(writer, 1) + case *ApplyBlockErrorCannotConnect: + writeInt32(writer, 2) + case *ApplyBlockErrorDatabase: + writeInt32(writer, 3) + default: + _ = variantValue + panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeApplyBlockError.Write", value)) + } +} + +type ApplyMempoolError struct { + err error +} + +func (err ApplyMempoolError) Error() string { + return fmt.Sprintf("ApplyMempoolError: %s", err.err.Error()) +} + +func (err ApplyMempoolError) Unwrap() error { + return err.err +} + +// Err* are used for checking error type with `errors.Is` +var ErrApplyMempoolErrorDatabase = fmt.Errorf("ApplyMempoolErrorDatabase") + +// Variant structs +type ApplyMempoolErrorDatabase struct { + message string +} + +func NewApplyMempoolErrorDatabase() *ApplyMempoolError { + return &ApplyMempoolError{ + err: &ApplyMempoolErrorDatabase{}, + } +} + +func (err ApplyMempoolErrorDatabase) Error() string { + return fmt.Sprintf("Database: %s", err.message) +} + +func (self ApplyMempoolErrorDatabase) Is(target error) bool { + return target == ErrApplyMempoolErrorDatabase +} + +type FfiConverterTypeApplyMempoolError struct{} + +var FfiConverterTypeApplyMempoolErrorINSTANCE = FfiConverterTypeApplyMempoolError{} + +func (c FfiConverterTypeApplyMempoolError) Lift(eb RustBufferI) error { + return LiftFromRustBuffer[error](c, eb) +} + +func (c FfiConverterTypeApplyMempoolError) Lower(value *ApplyMempoolError) RustBuffer { + return LowerIntoRustBuffer[*ApplyMempoolError](c, value) +} + +func (c FfiConverterTypeApplyMempoolError) Read(reader io.Reader) error { + errorID := readUint32(reader) + + message := FfiConverterStringINSTANCE.Read(reader) + switch errorID { + case 1: + return &ApplyMempoolError{&ApplyMempoolErrorDatabase{message}} + default: + panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeApplyMempoolError.Read()", errorID)) + } + +} + +func (c FfiConverterTypeApplyMempoolError) Write(writer io.Writer, value *ApplyMempoolError) { + switch variantValue := value.err.(type) { + case *ApplyMempoolErrorDatabase: + writeInt32(writer, 1) + default: + _ = variantValue + panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeApplyMempoolError.Write", value)) + } +} + +type CreateNewError struct { + err error +} + +func (err CreateNewError) Error() string { + return fmt.Sprintf("CreateNewError: %s", err.err.Error()) +} + +func (err CreateNewError) Unwrap() error { + return err.err +} + +// Err* are used for checking error type with `errors.Is` +var ErrCreateNewErrorParseNetwork = fmt.Errorf("CreateNewErrorParseNetwork") +var ErrCreateNewErrorParseGenesisHash = fmt.Errorf("CreateNewErrorParseGenesisHash") +var ErrCreateNewErrorDatabase = fmt.Errorf("CreateNewErrorDatabase") +var ErrCreateNewErrorWallet = fmt.Errorf("CreateNewErrorWallet") + +// Variant structs +type CreateNewErrorParseNetwork struct { + message string +} + +func NewCreateNewErrorParseNetwork() *CreateNewError { + return &CreateNewError{ + err: &CreateNewErrorParseNetwork{}, + } +} + +func (err CreateNewErrorParseNetwork) Error() string { + return fmt.Sprintf("ParseNetwork: %s", err.message) +} + +func (self CreateNewErrorParseNetwork) Is(target error) bool { + return target == ErrCreateNewErrorParseNetwork +} + +type CreateNewErrorParseGenesisHash struct { + message string +} + +func NewCreateNewErrorParseGenesisHash() *CreateNewError { + return &CreateNewError{ + err: &CreateNewErrorParseGenesisHash{}, + } +} + +func (err CreateNewErrorParseGenesisHash) Error() string { + return fmt.Sprintf("ParseGenesisHash: %s", err.message) +} + +func (self CreateNewErrorParseGenesisHash) Is(target error) bool { + return target == ErrCreateNewErrorParseGenesisHash +} + +type CreateNewErrorDatabase struct { + message string +} + +func NewCreateNewErrorDatabase() *CreateNewError { + return &CreateNewError{ + err: &CreateNewErrorDatabase{}, + } +} + +func (err CreateNewErrorDatabase) Error() string { + return fmt.Sprintf("Database: %s", err.message) +} + +func (self CreateNewErrorDatabase) Is(target error) bool { + return target == ErrCreateNewErrorDatabase +} + +type CreateNewErrorWallet struct { + message string +} + +func NewCreateNewErrorWallet() *CreateNewError { + return &CreateNewError{ + err: &CreateNewErrorWallet{}, + } +} + +func (err CreateNewErrorWallet) Error() string { + return fmt.Sprintf("Wallet: %s", err.message) +} + +func (self CreateNewErrorWallet) Is(target error) bool { + return target == ErrCreateNewErrorWallet +} + +type FfiConverterTypeCreateNewError struct{} + +var FfiConverterTypeCreateNewErrorINSTANCE = FfiConverterTypeCreateNewError{} + +func (c FfiConverterTypeCreateNewError) Lift(eb RustBufferI) error { + return LiftFromRustBuffer[error](c, eb) +} + +func (c FfiConverterTypeCreateNewError) Lower(value *CreateNewError) RustBuffer { + return LowerIntoRustBuffer[*CreateNewError](c, value) +} + +func (c FfiConverterTypeCreateNewError) Read(reader io.Reader) error { + errorID := readUint32(reader) + + message := FfiConverterStringINSTANCE.Read(reader) + switch errorID { + case 1: + return &CreateNewError{&CreateNewErrorParseNetwork{message}} + case 2: + return &CreateNewError{&CreateNewErrorParseGenesisHash{message}} + case 3: + return &CreateNewError{&CreateNewErrorDatabase{message}} + case 4: + return &CreateNewError{&CreateNewErrorWallet{message}} + default: + panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeCreateNewError.Read()", errorID)) + } + +} + +func (c FfiConverterTypeCreateNewError) Write(writer io.Writer, value *CreateNewError) { + switch variantValue := value.err.(type) { + case *CreateNewErrorParseNetwork: + writeInt32(writer, 1) + case *CreateNewErrorParseGenesisHash: + writeInt32(writer, 2) + case *CreateNewErrorDatabase: + writeInt32(writer, 3) + case *CreateNewErrorWallet: + writeInt32(writer, 4) + default: + _ = variantValue + panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeCreateNewError.Write", value)) + } +} + +type CreateTxError struct { + err error +} + +func (err CreateTxError) Error() string { + return fmt.Sprintf("CreateTxError: %s", err.err.Error()) +} + +func (err CreateTxError) Unwrap() error { + return err.err +} + +// Err* are used for checking error type with `errors.Is` +var ErrCreateTxErrorInvalidAddress = fmt.Errorf("CreateTxErrorInvalidAddress") +var ErrCreateTxErrorCreateTx = fmt.Errorf("CreateTxErrorCreateTx") +var ErrCreateTxErrorSignTx = fmt.Errorf("CreateTxErrorSignTx") + +// Variant structs +type CreateTxErrorInvalidAddress struct { + message string +} + +func NewCreateTxErrorInvalidAddress() *CreateTxError { + return &CreateTxError{ + err: &CreateTxErrorInvalidAddress{}, + } +} + +func (err CreateTxErrorInvalidAddress) Error() string { + return fmt.Sprintf("InvalidAddress: %s", err.message) +} + +func (self CreateTxErrorInvalidAddress) Is(target error) bool { + return target == ErrCreateTxErrorInvalidAddress +} + +type CreateTxErrorCreateTx struct { + message string +} + +func NewCreateTxErrorCreateTx() *CreateTxError { + return &CreateTxError{ + err: &CreateTxErrorCreateTx{}, + } +} + +func (err CreateTxErrorCreateTx) Error() string { + return fmt.Sprintf("CreateTx: %s", err.message) +} + +func (self CreateTxErrorCreateTx) Is(target error) bool { + return target == ErrCreateTxErrorCreateTx +} + +type CreateTxErrorSignTx struct { + message string +} + +func NewCreateTxErrorSignTx() *CreateTxError { + return &CreateTxError{ + err: &CreateTxErrorSignTx{}, + } +} + +func (err CreateTxErrorSignTx) Error() string { + return fmt.Sprintf("SignTx: %s", err.message) +} + +func (self CreateTxErrorSignTx) Is(target error) bool { + return target == ErrCreateTxErrorSignTx +} + +type FfiConverterTypeCreateTxError struct{} + +var FfiConverterTypeCreateTxErrorINSTANCE = FfiConverterTypeCreateTxError{} + +func (c FfiConverterTypeCreateTxError) Lift(eb RustBufferI) error { + return LiftFromRustBuffer[error](c, eb) +} + +func (c FfiConverterTypeCreateTxError) Lower(value *CreateTxError) RustBuffer { + return LowerIntoRustBuffer[*CreateTxError](c, value) +} + +func (c FfiConverterTypeCreateTxError) Read(reader io.Reader) error { + errorID := readUint32(reader) + + message := FfiConverterStringINSTANCE.Read(reader) + switch errorID { + case 1: + return &CreateTxError{&CreateTxErrorInvalidAddress{message}} + case 2: + return &CreateTxError{&CreateTxErrorCreateTx{message}} + case 3: + return &CreateTxError{&CreateTxErrorSignTx{message}} + default: + panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeCreateTxError.Read()", errorID)) + } + +} + +func (c FfiConverterTypeCreateTxError) Write(writer io.Writer, value *CreateTxError) { + switch variantValue := value.err.(type) { + case *CreateTxErrorInvalidAddress: + writeInt32(writer, 1) + case *CreateTxErrorCreateTx: + writeInt32(writer, 2) + case *CreateTxErrorSignTx: + writeInt32(writer, 3) + default: + _ = variantValue + panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeCreateTxError.Write", value)) + } +} + +type DatabaseError struct { + err error +} + +func (err DatabaseError) Error() string { + return fmt.Sprintf("DatabaseError: %s", err.err.Error()) +} + +func (err DatabaseError) Unwrap() error { + return err.err +} + +// Err* are used for checking error type with `errors.Is` +var ErrDatabaseErrorWrite = fmt.Errorf("DatabaseErrorWrite") + +// Variant structs +type DatabaseErrorWrite struct { + message string +} + +func NewDatabaseErrorWrite() *DatabaseError { + return &DatabaseError{ + err: &DatabaseErrorWrite{}, + } +} + +func (err DatabaseErrorWrite) Error() string { + return fmt.Sprintf("Write: %s", err.message) +} + +func (self DatabaseErrorWrite) Is(target error) bool { + return target == ErrDatabaseErrorWrite +} + +type FfiConverterTypeDatabaseError struct{} + +var FfiConverterTypeDatabaseErrorINSTANCE = FfiConverterTypeDatabaseError{} + +func (c FfiConverterTypeDatabaseError) Lift(eb RustBufferI) error { + return LiftFromRustBuffer[error](c, eb) +} + +func (c FfiConverterTypeDatabaseError) Lower(value *DatabaseError) RustBuffer { + return LowerIntoRustBuffer[*DatabaseError](c, value) +} + +func (c FfiConverterTypeDatabaseError) Read(reader io.Reader) error { + errorID := readUint32(reader) + + message := FfiConverterStringINSTANCE.Read(reader) + switch errorID { + case 1: + return &DatabaseError{&DatabaseErrorWrite{message}} + default: + panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeDatabaseError.Read()", errorID)) + } + +} + +func (c FfiConverterTypeDatabaseError) Write(writer io.Writer, value *DatabaseError) { + switch variantValue := value.err.(type) { + case *DatabaseErrorWrite: + writeInt32(writer, 1) + default: + _ = variantValue + panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeDatabaseError.Write", value)) + } +} + +type LoadError struct { + err error +} + +func (err LoadError) Error() string { + return fmt.Sprintf("LoadError: %s", err.err.Error()) +} + +func (err LoadError) Unwrap() error { + return err.err +} + +// Err* are used for checking error type with `errors.Is` +var ErrLoadErrorDatabase = fmt.Errorf("LoadErrorDatabase") +var ErrLoadErrorParseHeader = fmt.Errorf("LoadErrorParseHeader") +var ErrLoadErrorHeaderVersion = fmt.Errorf("LoadErrorHeaderVersion") +var ErrLoadErrorWallet = fmt.Errorf("LoadErrorWallet") + +// Variant structs +type LoadErrorDatabase struct { + message string +} + +func NewLoadErrorDatabase() *LoadError { + return &LoadError{ + err: &LoadErrorDatabase{}, + } +} + +func (err LoadErrorDatabase) Error() string { + return fmt.Sprintf("Database: %s", err.message) +} + +func (self LoadErrorDatabase) Is(target error) bool { + return target == ErrLoadErrorDatabase +} + +type LoadErrorParseHeader struct { + message string +} + +func NewLoadErrorParseHeader() *LoadError { + return &LoadError{ + err: &LoadErrorParseHeader{}, + } +} + +func (err LoadErrorParseHeader) Error() string { + return fmt.Sprintf("ParseHeader: %s", err.message) +} + +func (self LoadErrorParseHeader) Is(target error) bool { + return target == ErrLoadErrorParseHeader +} + +type LoadErrorHeaderVersion struct { + message string +} + +func NewLoadErrorHeaderVersion() *LoadError { + return &LoadError{ + err: &LoadErrorHeaderVersion{}, + } +} + +func (err LoadErrorHeaderVersion) Error() string { + return fmt.Sprintf("HeaderVersion: %s", err.message) +} + +func (self LoadErrorHeaderVersion) Is(target error) bool { + return target == ErrLoadErrorHeaderVersion +} + +type LoadErrorWallet struct { + message string +} + +func NewLoadErrorWallet() *LoadError { + return &LoadError{ + err: &LoadErrorWallet{}, + } +} + +func (err LoadErrorWallet) Error() string { + return fmt.Sprintf("Wallet: %s", err.message) +} + +func (self LoadErrorWallet) Is(target error) bool { + return target == ErrLoadErrorWallet +} + +type FfiConverterTypeLoadError struct{} + +var FfiConverterTypeLoadErrorINSTANCE = FfiConverterTypeLoadError{} + +func (c FfiConverterTypeLoadError) Lift(eb RustBufferI) error { + return LiftFromRustBuffer[error](c, eb) +} + +func (c FfiConverterTypeLoadError) Lower(value *LoadError) RustBuffer { + return LowerIntoRustBuffer[*LoadError](c, value) +} + +func (c FfiConverterTypeLoadError) Read(reader io.Reader) error { + errorID := readUint32(reader) + + message := FfiConverterStringINSTANCE.Read(reader) + switch errorID { + case 1: + return &LoadError{&LoadErrorDatabase{message}} + case 2: + return &LoadError{&LoadErrorParseHeader{message}} + case 3: + return &LoadError{&LoadErrorHeaderVersion{message}} + case 4: + return &LoadError{&LoadErrorWallet{message}} + default: + panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeLoadError.Read()", errorID)) + } + +} + +func (c FfiConverterTypeLoadError) Write(writer io.Writer, value *LoadError) { + switch variantValue := value.err.(type) { + case *LoadErrorDatabase: + writeInt32(writer, 1) + case *LoadErrorParseHeader: + writeInt32(writer, 2) + case *LoadErrorHeaderVersion: + writeInt32(writer, 3) + case *LoadErrorWallet: + writeInt32(writer, 4) + default: + _ = variantValue + panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeLoadError.Write", value)) + } +} + +type FfiConverterSequenceString struct{} + +var FfiConverterSequenceStringINSTANCE = FfiConverterSequenceString{} + +func (c FfiConverterSequenceString) Lift(rb RustBufferI) []string { + return LiftFromRustBuffer[[]string](c, rb) +} + +func (c FfiConverterSequenceString) Read(reader io.Reader) []string { + length := readInt32(reader) + if length == 0 { + return nil + } + result := make([]string, 0, length) + for i := int32(0); i < length; i++ { + result = append(result, FfiConverterStringINSTANCE.Read(reader)) + } + return result +} + +func (c FfiConverterSequenceString) Lower(value []string) RustBuffer { + return LowerIntoRustBuffer[[]string](c, value) +} + +func (c FfiConverterSequenceString) Write(writer io.Writer, value []string) { + if len(value) > math.MaxInt32 { + panic("[]string is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + for _, item := range value { + FfiConverterStringINSTANCE.Write(writer, item) + } +} + +type FfiDestroyerSequenceString struct{} + +func (FfiDestroyerSequenceString) Destroy(sequence []string) { + for _, value := range sequence { + FfiDestroyerString{}.Destroy(value) + } +} + +type FfiConverterSequenceBytes struct{} + +var FfiConverterSequenceBytesINSTANCE = FfiConverterSequenceBytes{} + +func (c FfiConverterSequenceBytes) Lift(rb RustBufferI) [][]byte { + return LiftFromRustBuffer[[][]byte](c, rb) +} + +func (c FfiConverterSequenceBytes) Read(reader io.Reader) [][]byte { + length := readInt32(reader) + if length == 0 { + return nil + } + result := make([][]byte, 0, length) + for i := int32(0); i < length; i++ { + result = append(result, FfiConverterBytesINSTANCE.Read(reader)) + } + return result +} + +func (c FfiConverterSequenceBytes) Lower(value [][]byte) RustBuffer { + return LowerIntoRustBuffer[[][]byte](c, value) +} + +func (c FfiConverterSequenceBytes) Write(writer io.Writer, value [][]byte) { + if len(value) > math.MaxInt32 { + panic("[][]byte is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + for _, item := range value { + FfiConverterBytesINSTANCE.Write(writer, item) + } +} + +type FfiDestroyerSequenceBytes struct{} + +func (FfiDestroyerSequenceBytes) Destroy(sequence [][]byte) { + for _, value := range sequence { + FfiDestroyerBytes{}.Destroy(value) + } +} + +type FfiConverterSequenceTypeBlockId struct{} + +var FfiConverterSequenceTypeBlockIdINSTANCE = FfiConverterSequenceTypeBlockId{} + +func (c FfiConverterSequenceTypeBlockId) Lift(rb RustBufferI) []BlockId { + return LiftFromRustBuffer[[]BlockId](c, rb) +} + +func (c FfiConverterSequenceTypeBlockId) Read(reader io.Reader) []BlockId { + length := readInt32(reader) + if length == 0 { + return nil + } + result := make([]BlockId, 0, length) + for i := int32(0); i < length; i++ { + result = append(result, FfiConverterTypeBlockIdINSTANCE.Read(reader)) + } + return result +} + +func (c FfiConverterSequenceTypeBlockId) Lower(value []BlockId) RustBuffer { + return LowerIntoRustBuffer[[]BlockId](c, value) +} + +func (c FfiConverterSequenceTypeBlockId) Write(writer io.Writer, value []BlockId) { + if len(value) > math.MaxInt32 { + panic("[]BlockId is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + for _, item := range value { + FfiConverterTypeBlockIdINSTANCE.Write(writer, item) + } +} + +type FfiDestroyerSequenceTypeBlockId struct{} + +func (FfiDestroyerSequenceTypeBlockId) Destroy(sequence []BlockId) { + for _, value := range sequence { + FfiDestroyerTypeBlockId{}.Destroy(value) + } +} + +type FfiConverterSequenceTypeMempoolTx struct{} + +var FfiConverterSequenceTypeMempoolTxINSTANCE = FfiConverterSequenceTypeMempoolTx{} + +func (c FfiConverterSequenceTypeMempoolTx) Lift(rb RustBufferI) []MempoolTx { + return LiftFromRustBuffer[[]MempoolTx](c, rb) +} + +func (c FfiConverterSequenceTypeMempoolTx) Read(reader io.Reader) []MempoolTx { + length := readInt32(reader) + if length == 0 { + return nil + } + result := make([]MempoolTx, 0, length) + for i := int32(0); i < length; i++ { + result = append(result, FfiConverterTypeMempoolTxINSTANCE.Read(reader)) + } + return result +} + +func (c FfiConverterSequenceTypeMempoolTx) Lower(value []MempoolTx) RustBuffer { + return LowerIntoRustBuffer[[]MempoolTx](c, value) +} + +func (c FfiConverterSequenceTypeMempoolTx) Write(writer io.Writer, value []MempoolTx) { + if len(value) > math.MaxInt32 { + panic("[]MempoolTx is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + for _, item := range value { + FfiConverterTypeMempoolTxINSTANCE.Write(writer, item) + } +} + +type FfiDestroyerSequenceTypeMempoolTx struct{} + +func (FfiDestroyerSequenceTypeMempoolTx) Destroy(sequence []MempoolTx) { + for _, value := range sequence { + FfiDestroyerTypeMempoolTx{}.Destroy(value) + } +} + +type FfiConverterSequenceTypeRecipient struct{} + +var FfiConverterSequenceTypeRecipientINSTANCE = FfiConverterSequenceTypeRecipient{} + +func (c FfiConverterSequenceTypeRecipient) Lift(rb RustBufferI) []Recipient { + return LiftFromRustBuffer[[]Recipient](c, rb) +} + +func (c FfiConverterSequenceTypeRecipient) Read(reader io.Reader) []Recipient { + length := readInt32(reader) + if length == 0 { + return nil + } + result := make([]Recipient, 0, length) + for i := int32(0); i < length; i++ { + result = append(result, FfiConverterTypeRecipientINSTANCE.Read(reader)) + } + return result +} + +func (c FfiConverterSequenceTypeRecipient) Lower(value []Recipient) RustBuffer { + return LowerIntoRustBuffer[[]Recipient](c, value) +} + +func (c FfiConverterSequenceTypeRecipient) Write(writer io.Writer, value []Recipient) { + if len(value) > math.MaxInt32 { + panic("[]Recipient is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + for _, item := range value { + FfiConverterTypeRecipientINSTANCE.Write(writer, item) + } +} + +type FfiDestroyerSequenceTypeRecipient struct{} + +func (FfiDestroyerSequenceTypeRecipient) Destroy(sequence []Recipient) { + for _, value := range sequence { + FfiDestroyerTypeRecipient{}.Destroy(value) + } +} + +type FfiConverterSequenceTypeTxInfo struct{} + +var FfiConverterSequenceTypeTxInfoINSTANCE = FfiConverterSequenceTypeTxInfo{} + +func (c FfiConverterSequenceTypeTxInfo) Lift(rb RustBufferI) []TxInfo { + return LiftFromRustBuffer[[]TxInfo](c, rb) +} + +func (c FfiConverterSequenceTypeTxInfo) Read(reader io.Reader) []TxInfo { + length := readInt32(reader) + if length == 0 { + return nil + } + result := make([]TxInfo, 0, length) + for i := int32(0); i < length; i++ { + result = append(result, FfiConverterTypeTxInfoINSTANCE.Read(reader)) + } + return result +} + +func (c FfiConverterSequenceTypeTxInfo) Lower(value []TxInfo) RustBuffer { + return LowerIntoRustBuffer[[]TxInfo](c, value) +} + +func (c FfiConverterSequenceTypeTxInfo) Write(writer io.Writer, value []TxInfo) { + if len(value) > math.MaxInt32 { + panic("[]TxInfo is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + for _, item := range value { + FfiConverterTypeTxInfoINSTANCE.Write(writer, item) + } +} + +type FfiDestroyerSequenceTypeTxInfo struct{} + +func (FfiDestroyerSequenceTypeTxInfo) Destroy(sequence []TxInfo) { + for _, value := range sequence { + FfiDestroyerTypeTxInfo{}.Destroy(value) + } +} + +type FfiConverterSequenceTypeUtxoInfo struct{} + +var FfiConverterSequenceTypeUtxoInfoINSTANCE = FfiConverterSequenceTypeUtxoInfo{} + +func (c FfiConverterSequenceTypeUtxoInfo) Lift(rb RustBufferI) []UtxoInfo { + return LiftFromRustBuffer[[]UtxoInfo](c, rb) +} + +func (c FfiConverterSequenceTypeUtxoInfo) Read(reader io.Reader) []UtxoInfo { + length := readInt32(reader) + if length == 0 { + return nil + } + result := make([]UtxoInfo, 0, length) + for i := int32(0); i < length; i++ { + result = append(result, FfiConverterTypeUtxoInfoINSTANCE.Read(reader)) + } + return result +} + +func (c FfiConverterSequenceTypeUtxoInfo) Lower(value []UtxoInfo) RustBuffer { + return LowerIntoRustBuffer[[]UtxoInfo](c, value) +} + +func (c FfiConverterSequenceTypeUtxoInfo) Write(writer io.Writer, value []UtxoInfo) { + if len(value) > math.MaxInt32 { + panic("[]UtxoInfo is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + for _, item := range value { + FfiConverterTypeUtxoInfoINSTANCE.Write(writer, item) + } +} + +type FfiDestroyerSequenceTypeUtxoInfo struct{} + +func (FfiDestroyerSequenceTypeUtxoInfo) Destroy(sequence []UtxoInfo) { + for _, value := range sequence { + FfiDestroyerTypeUtxoInfo{}.Destroy(value) + } +} diff --git a/bdkwallet/bdkgo/bdkgo.h b/bdkwallet/bdkgo/bdkgo.h new file mode 100644 index 00000000..14a5f5b2 --- /dev/null +++ b/bdkwallet/bdkgo/bdkgo.h @@ -0,0 +1,524 @@ + + +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + + + +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V6 + #ifndef UNIFFI_SHARED_HEADER_V6 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V6 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V6 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️ + +typedef struct RustBuffer { + int32_t capacity; + int32_t len; + uint8_t *data; +} RustBuffer; + +typedef int32_t (*ForeignCallback)(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *); + +// Task defined in Rust that Go executes +typedef void (*RustTaskCallback)(const void *, int8_t); + +// Callback to execute Rust tasks using a Go routine +// +// Args: +// executor: ForeignExecutor lowered into a uint64_t value +// delay: Delay in MS +// task: RustTaskCallback to call +// task_data: data to pass the task callback +typedef int8_t (*ForeignExecutorCallback)(uint64_t, uint32_t, RustTaskCallback, void *); + +typedef struct ForeignBytes { + int32_t len; + const uint8_t *data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// Continuation callback for UniFFI Futures +typedef void (*RustFutureContinuation)(void * , int8_t); + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H + +// Needed because we can't execute the callback directly from go. +void cgo_rust_task_callback_bridge_bdkgo(RustTaskCallback, const void *, int8_t); + +int8_t uniffiForeignExecutorCallbackbdkgo(uint64_t, uint32_t, RustTaskCallback, void*); + +void uniffiFutureContinuationCallbackbdkgo(void*, int8_t); + +void uniffi_bdkgo_fn_free_wallet( + void* ptr, + RustCallStatus* out_status +); + +void* uniffi_bdkgo_fn_constructor_wallet_create_new( + RustBuffer db_path, + RustBuffer network, + RustBuffer genesis_hash, + RustCallStatus* out_status +); + +void* uniffi_bdkgo_fn_constructor_wallet_load( + RustBuffer db_path, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_apply_block( + void* ptr, + uint32_t height, + RustBuffer block_bytes, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_apply_mempool( + void* ptr, + RustBuffer txs, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_balance( + void* ptr, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_create_tx( + void* ptr, + float feerate, + RustBuffer recipients, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_fresh_address( + void* ptr, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_genesis_hash( + void* ptr, + RustCallStatus* out_status +); + +void uniffi_bdkgo_fn_method_wallet_increment_reference_counter( + void* ptr, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_last_unused_address( + void* ptr, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_mnemonic_words( + void* ptr, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_peek_address( + void* ptr, + uint32_t index, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_recent_blocks( + void* ptr, + uint32_t count, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_transactions( + void* ptr, + RustCallStatus* out_status +); + +RustBuffer uniffi_bdkgo_fn_method_wallet_utxos( + void* ptr, + RustCallStatus* out_status +); + +RustBuffer ffi_bdkgo_rustbuffer_alloc( + int32_t size, + RustCallStatus* out_status +); + +RustBuffer ffi_bdkgo_rustbuffer_from_bytes( + ForeignBytes bytes, + RustCallStatus* out_status +); + +void ffi_bdkgo_rustbuffer_free( + RustBuffer buf, + RustCallStatus* out_status +); + +RustBuffer ffi_bdkgo_rustbuffer_reserve( + RustBuffer buf, + int32_t additional, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_continuation_callback_set( + RustFutureContinuation callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_u8( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_u8( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_u8( + void* handle, + RustCallStatus* out_status +); + +uint8_t ffi_bdkgo_rust_future_complete_u8( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_i8( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_i8( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_i8( + void* handle, + RustCallStatus* out_status +); + +int8_t ffi_bdkgo_rust_future_complete_i8( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_u16( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_u16( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_u16( + void* handle, + RustCallStatus* out_status +); + +uint16_t ffi_bdkgo_rust_future_complete_u16( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_i16( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_i16( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_i16( + void* handle, + RustCallStatus* out_status +); + +int16_t ffi_bdkgo_rust_future_complete_i16( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_u32( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_u32( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_u32( + void* handle, + RustCallStatus* out_status +); + +uint32_t ffi_bdkgo_rust_future_complete_u32( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_i32( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_i32( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_i32( + void* handle, + RustCallStatus* out_status +); + +int32_t ffi_bdkgo_rust_future_complete_i32( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_u64( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_u64( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_u64( + void* handle, + RustCallStatus* out_status +); + +uint64_t ffi_bdkgo_rust_future_complete_u64( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_i64( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_i64( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_i64( + void* handle, + RustCallStatus* out_status +); + +int64_t ffi_bdkgo_rust_future_complete_i64( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_f32( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_f32( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_f32( + void* handle, + RustCallStatus* out_status +); + +float ffi_bdkgo_rust_future_complete_f32( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_f64( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_f64( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_f64( + void* handle, + RustCallStatus* out_status +); + +double ffi_bdkgo_rust_future_complete_f64( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_pointer( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_pointer( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_pointer( + void* handle, + RustCallStatus* out_status +); + +void* ffi_bdkgo_rust_future_complete_pointer( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_rust_buffer( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_rust_buffer( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_rust_buffer( + void* handle, + RustCallStatus* out_status +); + +RustBuffer ffi_bdkgo_rust_future_complete_rust_buffer( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_poll_void( + void* handle, + void* uniffi_callback, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_cancel_void( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_free_void( + void* handle, + RustCallStatus* out_status +); + +void ffi_bdkgo_rust_future_complete_void( + void* handle, + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_apply_block( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_apply_mempool( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_balance( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_create_tx( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_fresh_address( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_genesis_hash( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_increment_reference_counter( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_last_unused_address( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_mnemonic_words( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_peek_address( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_recent_blocks( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_transactions( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_method_wallet_utxos( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_constructor_wallet_create_new( + RustCallStatus* out_status +); + +uint16_t uniffi_bdkgo_checksum_constructor_wallet_load( + RustCallStatus* out_status +); + +uint32_t ffi_bdkgo_uniffi_contract_version( + RustCallStatus* out_status +); + + + diff --git a/bdkwallet/bdkgo_crate/Cargo.toml b/bdkwallet/bdkgo_crate/Cargo.toml new file mode 100644 index 00000000..2e62b5fa --- /dev/null +++ b/bdkwallet/bdkgo_crate/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "bdkgo" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["staticlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +uniffi = "=0.25.0" +weedle2 = "=4.0.0" +thiserror = "1.0" +bincode = "1" +serde = { version = "1", features = ["derive"] } +bdk = { git = "https://github.com/bitcoindevkit/bdk.git", rev = "070fffb95cddb9153f326d5662f206d25ecbcc7c", features = ["keys-bip39"] } +bdk_file_store = { git = "https://github.com/bitcoindevkit/bdk.git", rev = "070fffb95cddb9153f326d5662f206d25ecbcc7c" } +rand = "0.8.5" + +[build-dependencies] +uniffi = { version = "=0.25.0", features = [ "build" ] } +weedle2 = "=4.0.0" diff --git a/bdkwallet/bdkgo_crate/build.rs b/bdkwallet/bdkgo_crate/build.rs new file mode 100644 index 00000000..74144e7f --- /dev/null +++ b/bdkwallet/bdkgo_crate/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::generate_scaffolding("src/bdkgo.udl").unwrap(); +} diff --git a/bdkwallet/bdkgo_crate/src/bdkgo.udl b/bdkwallet/bdkgo_crate/src/bdkgo.udl new file mode 100644 index 00000000..4c2879d9 --- /dev/null +++ b/bdkwallet/bdkgo_crate/src/bdkgo.udl @@ -0,0 +1,130 @@ +namespace bdkgo {}; + +[Error] +enum CreateNewError { + "ParseNetwork", + "ParseGenesisHash", + "Database", + "Wallet", +}; + +[Error] +enum LoadError { + "Database", + "ParseHeader", + "HeaderVersion", + "Wallet", +}; + +[Error] +enum DatabaseError { + "Write", +}; + +[Error] +enum ApplyBlockError { + "DecodeBlock", + "CannotConnect", + "Database", +}; + +[Error] +enum ApplyMempoolError { + "Database", +}; + +[Error] +enum CreateTxError { + "InvalidAddress", + "CreateTx", + "SignTx", +}; + +dictionary AddressInfo { + u32 index; + string address; +}; + +dictionary Balance { + u64 immature; + u64 trusted_pending; + u64 untrusted_pending; + u64 confirmed; +}; + +interface Wallet { + [Name=create_new, Throws=CreateNewError] + constructor(string db_path, string network, bytes genesis_hash); + + [Name=load, Throws=LoadError] + constructor(string db_path); + + void increment_reference_counter(); + + [Throws=DatabaseError] + AddressInfo last_unused_address(); + + [Throws=DatabaseError] + AddressInfo fresh_address(); + + [Throws=DatabaseError] + AddressInfo peek_address(u32 index); + + Balance balance(); + + bytes genesis_hash(); + + sequence recent_blocks(u32 count); + + [Throws=ApplyBlockError] + ApplyResult apply_block(u32 height, [ByRef] bytes block_bytes); + + [Throws=ApplyMempoolError] + ApplyResult apply_mempool(sequence txs); + + [Throws=CreateTxError] + bytes create_tx(f32 feerate, sequence recipients); + + sequence mnemonic_words(); + + sequence transactions(); + + sequence utxos(); +}; + +dictionary Recipient { + string address; + u64 amount; +}; + +dictionary BlockId { + u32 height; + bytes hash; +}; + +dictionary TxInfo { + bytes txid; + bytes tx; + u64 spent; + u64 received; + u32 confirmations; +}; + +dictionary UtxoInfo { + bytes txid; + u32 vout; + u64 amount; + bytes script_pubkey; + boolean is_change; + u32 derivation_index; + u32 confirmations; +}; + +dictionary MempoolTx { + bytes tx; + u64 added_unix; +}; + +dictionary ApplyResult { + sequence relevant_txids; +}; diff --git a/bdkwallet/bdkgo_crate/src/lib.rs b/bdkwallet/bdkgo_crate/src/lib.rs new file mode 100644 index 00000000..8fb383f0 --- /dev/null +++ b/bdkwallet/bdkgo_crate/src/lib.rs @@ -0,0 +1,500 @@ +use std::{ + cmp::Reverse, + io::Read, + str::FromStr, + sync::{Arc, Mutex}, +}; + +pub use bdk::wallet::Balance; +use bdk::{ + bitcoin::{ + self, + consensus::{Decodable, Encodable}, + hashes::Hash, + network::constants::ParseNetworkError, + Address, BlockHash, Network, Transaction, + }, + keys::{DerivableKey, ExtendedKey}, + template::Bip86, + wallet::AddressIndex, + FeeRate, KeychainKind, SignOptions, +}; +use bincode::Options; +use rand::RngCore; +use uniffi::deps::bytes::Buf; + +uniffi::include_scaffolding!("bdkgo"); + +const DB_MAGIC: &str = "utreexod.bdk.345e94cf"; +const DB_MAGIC_LEN: usize = DB_MAGIC.len(); +const ENTROPY_LEN: usize = 16; // 12 words + +type BdkWallet = bdk::Wallet>; + +fn bincode_config() -> impl bincode::Options { + bincode::options().with_fixint_encoding() +} + +#[derive(Debug, thiserror::Error)] +pub enum CreateNewError { + #[error("failed to parse network type string: {0}")] + ParseNetwork(ParseNetworkError), + #[error("failed to parse genesis hash: {0}")] + ParseGenesisHash(bdk::bitcoin::hashes::Error), + #[error("failed to create new db file: {0}")] + Database(bdk_file_store::FileError), + #[error("failed to init wallet: {0}")] + Wallet(bdk::wallet::NewError), +} + +#[derive(Debug, thiserror::Error)] +pub enum LoadError { + #[error("failed to load db: {0}")] + Database(bdk_file_store::FileError), + #[error("failed to decode wallet header: {0}")] + ParseHeader(bincode::Error), + #[error("wallet header version unsupported")] + HeaderVersion, + #[error("failed to init wallet: {0}")] + Wallet(bdk::wallet::LoadError), +} + +#[derive(Debug, thiserror::Error)] +pub enum DatabaseError { + #[error("failed to write to db: {0}")] + Write(std::io::Error), +} + +#[derive(Debug, thiserror::Error)] +pub enum ApplyBlockError { + #[error("failed to decode block: {0}")] + DecodeBlock(bdk::bitcoin::consensus::encode::Error), + #[error("block cannot connect with wallet's chain: {0}")] + CannotConnect(bdk::chain::local_chain::CannotConnectError), + #[error("failed to write block to db: {0}")] + Database(std::io::Error), +} + +#[derive(Debug, thiserror::Error)] +pub enum ApplyMempoolError { + #[error("failed to write mempool txs to db: {0}")] + Database(std::io::Error), +} + +#[derive(Debug, thiserror::Error)] +pub enum CreateTxError { + #[error("recipient address is invalid: {0}")] + InvalidAddress(bdk::bitcoin::address::Error), + #[error("failed to create tx: {0}")] + CreateTx(bdk::wallet::error::CreateTxError), + #[error("failed to sign tx: {0}")] + SignTx(bdk::wallet::signer::SignerError), +} + +pub struct AddressInfo { + pub index: u32, + pub address: String, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct WalletHeader { + pub version: [u8; DB_MAGIC_LEN], + pub entropy: [u8; ENTROPY_LEN], + pub network: Network, +} + +impl WalletHeader { + pub fn new(network: Network) -> Self { + let mut version = [0_u8; DB_MAGIC_LEN]; + version.copy_from_slice(DB_MAGIC.as_bytes()); + let mut entropy = [0_u8; ENTROPY_LEN]; + rand::thread_rng().fill_bytes(&mut entropy); + Self { + version, + entropy, + network, + } + } + + pub fn encode(&mut self) -> Vec { + self.version.copy_from_slice(DB_MAGIC.as_bytes()); + let b = bincode_config() + .serialize(&self) + .expect("bincode must serialize"); + let l = (b.len() as u32).to_le_bytes(); + l.into_iter().chain(b).collect::>() + } + + pub fn decode(mut r: R) -> Result<(Self, Vec), LoadError> { + let mut l_buf = [0_u8; 4]; + r.read_exact(&mut l_buf) + .map_err(|err| LoadError::Database(bdk_file_store::FileError::Io(err)))?; + let l = u32::from_le_bytes(l_buf); + let mut b = vec![0; l as usize]; + r.read_exact(&mut b) + .map_err(|err| LoadError::Database(bdk_file_store::FileError::Io(err)))?; + + let header = bincode_config() + .deserialize::(&b) + .map_err(LoadError::ParseHeader)?; + if header.version != DB_MAGIC.as_bytes() { + return Err(LoadError::HeaderVersion); + } + + let header_b = l_buf.into_iter().chain(b).collect::>(); + Ok((header, header_b)) + } + + pub fn descriptor(&self, keychain: KeychainKind) -> Bip86> { + let mnemonic = + bdk::keys::bip39::Mnemonic::from_entropy(&self.entropy).expect("must get mnemonic"); + let mut ext_key: bdk::keys::ExtendedKey = mnemonic + .into_extended_key() + .expect("must become extended key"); + match &mut ext_key { + ExtendedKey::Private((key, _)) => key.network = self.network, + ExtendedKey::Public((key, _)) => key.network = self.network, + }; + Bip86(ext_key, keychain) + } + + pub fn mnemonic_words(&self) -> Vec { + let mnemonic = + bdk::keys::bip39::Mnemonic::from_entropy(&self.entropy).expect("must get mnemonic"); + mnemonic.word_iter().map(|w| w.to_string()).collect() + } +} + +pub struct Wallet { + inner: Mutex, + header: Mutex, +} + +impl Wallet { + /// Increments the `Arc` pointer exposed via uniffi. + /// + /// This is due to a bug with golang uniffi where decrementing this counter is too aggressive. + /// The caveat of this is that `Wallet` will never be destroyed. This is an okay sacrifice as + /// typically you want to keep the wallet for the lifetime of the node. + pub fn increment_reference_counter(self: &Arc) { + unsafe { Arc::increment_strong_count(Arc::into_raw(Arc::clone(self))) } + } + + pub fn create_new( + db_path: String, + network: String, + genesis_hash: Vec, + ) -> Result { + let network = Network::from_str(&network).map_err(CreateNewError::ParseNetwork)?; + let genesis_hash = + BlockHash::from_slice(&genesis_hash).map_err(CreateNewError::ParseGenesisHash)?; + + let mut header = WalletHeader::new(network); + let header_bytes = header.encode(); + let db = bdk_file_store::Store::create_new(&header_bytes, &db_path) + .map_err(CreateNewError::Database)?; + let bdk_wallet = match bdk::Wallet::new_with_genesis_hash( + header.descriptor(KeychainKind::External), + Some(header.descriptor(KeychainKind::Internal)), + db, + network, + genesis_hash, + ) { + Ok(w) => w, + Err(err) => { + let _ = std::fs::remove_file(db_path); + return Err(CreateNewError::Wallet(err)); + } + }; + + let inner = Mutex::new(bdk_wallet); + let header = Mutex::new(header); + Ok(Self { inner, header }) + } + + pub fn load(db_path: String) -> Result { + let file = std::fs::File::open(&db_path) + .map_err(|err| LoadError::Database(bdk_file_store::FileError::Io(err)))?; + let (header, header_bytes) = WalletHeader::decode(file)?; + let db = + bdk_file_store::Store::open(&header_bytes, db_path).map_err(LoadError::Database)?; + let bdk_wallet = bdk::Wallet::load( + header.descriptor(KeychainKind::External), + Some(header.descriptor(KeychainKind::Internal)), + db, + ) + .map_err(LoadError::Wallet)?; + + let inner = Mutex::new(bdk_wallet); + let header = Mutex::new(header); + Ok(Self { inner, header }) + } + + fn address(self: Arc, index: AddressIndex) -> Result { + self.increment_reference_counter(); + let mut wallet = self.inner.lock().unwrap(); + let address_info = wallet + .try_get_address(index) + .map_err(DatabaseError::Write)?; + Ok(AddressInfo { + index: address_info.index, + address: address_info.address.to_string(), + }) + } + + pub fn last_unused_address(self: Arc) -> Result { + self.increment_reference_counter(); + self.address(AddressIndex::LastUnused) + } + + pub fn fresh_address(self: Arc) -> Result { + self.increment_reference_counter(); + self.address(AddressIndex::New) + } + + pub fn peek_address(self: Arc, index: u32) -> Result { + self.increment_reference_counter(); + self.address(AddressIndex::Peek(index)) + } + + pub fn balance(self: Arc) -> bdk::wallet::Balance { + self.increment_reference_counter(); + let wallet = self.inner.lock().unwrap(); + wallet.get_balance() + } + + pub fn genesis_hash(self: Arc) -> Vec { + self.increment_reference_counter(); + self.inner + .lock() + .unwrap() + .local_chain() + .genesis_hash() + .to_byte_array() + .to_vec() + } + + pub fn recent_blocks(self: Arc, count: u32) -> Vec { + self.increment_reference_counter(); + let tip = self.inner.lock().unwrap().latest_checkpoint(); + tip.into_iter() + .take(count as _) + .map(|cp| BlockId { + height: cp.height(), + hash: cp.hash().to_byte_array().to_vec(), + }) + .collect() + } + + pub fn apply_block( + self: Arc, + height: u32, + block_bytes: &[u8], + ) -> Result { + self.increment_reference_counter(); + + let mut wallet = self.inner.lock().unwrap(); + + let mut reader = block_bytes.reader(); + let block = bitcoin::Block::consensus_decode_from_finite_reader(&mut reader) + .map_err(ApplyBlockError::DecodeBlock)?; + + let tip = wallet.latest_checkpoint(); + + if tip.height() == 0 { + wallet + .apply_block_connected_to(&block, height, tip.block_id()) + .map_err(|err| match err { + bdk::chain::local_chain::ApplyHeaderError::InconsistentBlocks => { + unreachable!("cannot happen") + } + bdk::chain::local_chain::ApplyHeaderError::CannotConnect(err) => { + ApplyBlockError::CannotConnect(err) + } + })?; + } else { + wallet + .apply_block(&block, height) + .map_err(ApplyBlockError::CannotConnect)?; + } + let res = ApplyResult::new(&wallet); + wallet.commit().map_err(ApplyBlockError::Database)?; + Ok(res) + } + + pub fn apply_mempool( + self: Arc, + txs: Vec, + ) -> Result { + self.increment_reference_counter(); + let mut wallet = self.inner.lock().unwrap(); + let txs = txs + .into_iter() + .map(|mtx| { + ( + Transaction::consensus_decode_from_finite_reader(&mut mtx.tx.reader()) + .expect("must decode tx"), + mtx.added_unix, + ) + }) + .collect::>(); + wallet.apply_unconfirmed_txs(txs.iter().map(|(tx, added)| (tx, *added))); + + let res = ApplyResult::new(&wallet); + wallet.commit().map_err(ApplyMempoolError::Database)?; + Ok(res) + } + + pub fn create_tx( + self: Arc, + feerate: f32, + recipients: Vec, + ) -> Result, CreateTxError> { + self.increment_reference_counter(); + let mut wallet = self.inner.lock().unwrap(); + let recipients = recipients + .into_iter() + .map(|r| -> Result<_, _> { + let addr = Address::from_str(&r.address) + .map_err(CreateTxError::InvalidAddress)? + .require_network(wallet.network()) + .map_err(CreateTxError::InvalidAddress)?; + Ok((addr.script_pubkey(), r.amount)) + }) + .collect::, _>>()?; + + let mut psbt = wallet + .build_tx() + .set_recipients(recipients) + .fee_rate(FeeRate::from_sat_per_vb(feerate)) + .enable_rbf() + .clone() + .finish() + .map_err(CreateTxError::CreateTx)?; + let is_finalized = wallet + .sign(&mut psbt, SignOptions::default()) + .map_err(CreateTxError::SignTx)?; + assert!(is_finalized, "tx should always be finalized"); + + let mut raw_bytes = Vec::::new(); + psbt.extract_tx() + .consensus_encode(&mut raw_bytes) + .expect("must encode tx"); + Ok(raw_bytes) + } + + pub fn mnemonic_words(self: Arc) -> Vec { + self.increment_reference_counter(); + self.header.lock().unwrap().mnemonic_words() + } + + pub fn transactions(self: Arc) -> Vec { + self.increment_reference_counter(); + let wallet = self.inner.lock().unwrap(); + let height = wallet.latest_checkpoint().height(); + let mut txs = wallet + .transactions() + .map(|ctx| { + let txid = ctx.tx_node.txid.to_byte_array().to_vec(); + let mut tx = Vec::::new(); + ctx.tx_node + .tx + .consensus_encode(&mut tx) + .expect("must encode"); + let (spent, received) = wallet.sent_and_received(ctx.tx_node.tx); + let confirmations = ctx + .chain_position + .confirmation_height_upper_bound() + .map_or(0, |conf_height| (1 + height).saturating_sub(conf_height)); + TxInfo { + txid, + tx, + spent, + received, + confirmations, + } + }) + .collect::>(); + txs.sort_unstable_by_key(|tx| Reverse(tx.confirmations)); + txs + } + + pub fn utxos(self: Arc) -> Vec { + self.increment_reference_counter(); + let wallet = self.inner.lock().unwrap(); + let wallet_height = wallet.latest_checkpoint().height(); + let mut utxos = wallet + .list_unspent() + .map(|utxo| UtxoInfo { + txid: utxo.outpoint.txid.to_byte_array().to_vec(), + vout: utxo.outpoint.vout, + amount: utxo.txout.value, + script_pubkey: utxo.txout.script_pubkey.to_bytes(), + is_change: utxo.keychain == KeychainKind::Internal, + derivation_index: utxo.derivation_index, + confirmations: match utxo.confirmation_time { + bdk::chain::ConfirmationTime::Confirmed { height, .. } => { + (1 + wallet_height).saturating_sub(height) + } + bdk::chain::ConfirmationTime::Unconfirmed { .. } => 0, + }, + }) + .collect::>(); + utxos.sort_unstable_by_key(|utxo| Reverse(utxo.confirmations)); + utxos + } +} + +pub struct Recipient { + pub address: String, + pub amount: u64, +} + +pub struct BlockId { + pub height: u32, + pub hash: Vec, +} + +pub struct TxInfo { + pub txid: Vec, + pub tx: Vec, + /// Sum of inputs spending from owned script pubkeys. + pub spent: u64, + /// Sum of outputs containing owned script pubkeys. + pub received: u64, + /// How confirmed is this transaction? + pub confirmations: u32, +} + +pub struct UtxoInfo { + pub txid: Vec, + pub vout: u32, + pub amount: u64, + pub script_pubkey: Vec, + pub is_change: bool, + pub derivation_index: u32, + pub confirmations: u32, +} + +pub struct MempoolTx { + pub tx: Vec, + pub added_unix: u64, +} + +pub struct ApplyResult { + pub relevant_txids: Vec>, +} + +impl ApplyResult { + pub fn new(wallet: &BdkWallet) -> Self { + let relevant_txids = wallet + .staged() + .indexed_tx_graph + .graph + .txs + .iter() + .map(|tx| tx.txid().to_byte_array().to_vec()) + .collect::>(); + Self { relevant_txids } + } +} diff --git a/bdkwallet/bdkwallet.go b/bdkwallet/bdkwallet.go new file mode 100644 index 00000000..b079eb90 --- /dev/null +++ b/bdkwallet/bdkwallet.go @@ -0,0 +1,246 @@ +//go:build bdkwallet + +package bdkwallet + +//#cgo LDFLAGS: ./target/release/libbdkgo.a -ldl -lm +import "C" + +import ( + "bytes" + + "github.com/utreexo/utreexod/bdkwallet/bdkgo" + "github.com/utreexo/utreexod/btcutil" + "github.com/utreexo/utreexod/chaincfg" + "github.com/utreexo/utreexod/chaincfg/chainhash" + "github.com/utreexo/utreexod/mempool" + "github.com/utreexo/utreexod/wire" +) + +func init() { + walletFactory = &BDKWalletFactory{} +} + +type BDKWalletFactory struct{} + +func (*BDKWalletFactory) Create(dbPath string, chainParams *chaincfg.Params) (Wallet, error) { + // used for the address format + // this is parsed as `bitcoin::Network` in rust + // supported strings: bitcoin, testnet, signet, regtest + // https://docs.rs/bitcoin/latest/bitcoin/network/enum.Network.html + network := chainParams.Name + log.Infof("Creating wallet with network: %v", network) + switch network { + case "mainnet": + network = "bitcoin" + case "testnet3": + network = "testnet" + } + + genesisHash := chainParams.GenesisHash.CloneBytes() + + inner, err := bdkgo.WalletCreateNew(dbPath, network, genesisHash) + if err != nil { + return nil, err + } + + // This increments the reference count of the Arc pointer in rust. We are + // doing this due to a bug with uniffi-bindgen-go's generated code + // decrementing this count too aggressively. + inner.IncrementReferenceCounter() + return &BDKWallet{*inner}, nil +} + +func (*BDKWalletFactory) Load(dbPath string) (Wallet, error) { + inner, err := bdkgo.WalletLoad(dbPath) + if err != nil { + return nil, err + } + + // This increments the reference count of the Arc pointer in rust. We are + // doing this due to a bug with uniffi-bindgen-go's generated code + // decrementing this count too aggressively. + inner.IncrementReferenceCounter() + return &BDKWallet{*inner}, nil +} + +// Wallet is a BDK wallet. +type BDKWallet struct { + inner bdkgo.Wallet +} + +// UnusedAddress returns the earliest address which have not received any funds. +func (w *BDKWallet) UnusedAddress() (uint, btcutil.Address, error) { + info, err := w.inner.LastUnusedAddress() + if err != nil { + return uint(info.Index), nil, err + } + addr, err := btcutil.DecodeAddress(info.Address, nil) + if err != nil { + return uint(info.Index), nil, err + } + return uint(info.Index), addr, nil +} + +// FreshAddress always returns a new address. This means it always increments +// the last derivation index even though the previous derivation indexes have +// not received funds. +func (w *BDKWallet) FreshAddress() (uint, btcutil.Address, error) { + info, err := w.inner.FreshAddress() + if err != nil { + return uint(info.Index), nil, err + } + addr, err := btcutil.DecodeAddress(info.Address, nil) + if err != nil { + return uint(info.Index), nil, err + } + return uint(info.Index), addr, nil +} + +// PeekAddress previews the address at the derivation index. This does not +// increment the last revealed index. +func (w *BDKWallet) PeekAddress(index uint32) (uint, btcutil.Address, error) { + info, err := w.inner.PeekAddress(uint32(index)) + if err != nil { + return uint(info.Index), nil, err + } + addr, err := btcutil.DecodeAddress(info.Address, nil) + if err != nil { + return uint(info.Index), nil, err + } + return uint(info.Index), addr, nil +} + +// Balance returns the balance of the wallet. +func (w *BDKWallet) Balance() Balance { + balance := w.inner.Balance() + return Balance{ + Immature: btcutil.Amount(balance.Immature), + TrustedPending: btcutil.Amount(balance.TrustedPending), + UntrustedPending: btcutil.Amount(balance.UntrustedPending), + Confirmed: btcutil.Amount(balance.Confirmed), + } +} + +// RecentBlocks returns the most recent blocks +func (w *BDKWallet) RecentBlocks(count uint32) []BlockId { + generatedCodeBlocks := w.inner.RecentBlocks(uint32(count)) + out := make([]BlockId, 0, len(generatedCodeBlocks)) + for _, block := range generatedCodeBlocks { + out = append(out, BlockId{ + Height: uint(block.Height), + Hash: hashFromBytes(block.Hash), + }) + } + return out +} + +// ApplyBlock updates the wallet with the given block. +func (w *BDKWallet) ApplyBlock(block *btcutil.Block) error { + var b bytes.Buffer + if err := block.MsgBlock().BtcEncode(&b, wire.FeeFilterVersion, wire.WitnessEncoding); err != nil { + return err + } + bheight := uint32(block.Height()) + res, err := w.inner.ApplyBlock(bheight, b.Bytes()) + if err != nil { + return err + } + + for _, txid := range res.RelevantTxids { + hash, err := chainhash.NewHash(txid) + if err != nil { + return err + } + log.Infof("Found relevant tx %v in block %v:%v.", + hash.String(), bheight, block.Hash().String()) + } + return nil +} + +// ApplyMempoolTransactions updates the wallet with the given mempool transactions. +func (w *BDKWallet) ApplyMempoolTransactions(txns []*mempool.TxDesc) error { + if len(txns) == 0 { + return nil + } + genTxns := make([]bdkgo.MempoolTx, 0, len(txns)) + for _, tx := range txns { + var txb bytes.Buffer + if err := tx.Tx.MsgTx().BtcEncode(&txb, 0, wire.WitnessEncoding); err != nil { + return err + } + genTxns = append(genTxns, bdkgo.MempoolTx{ + Tx: txb.Bytes(), + AddedUnix: uint64(tx.Added.Unix()), + }) + } + res, err := w.inner.ApplyMempool(genTxns) + if err != nil { + return err + } + for _, txid := range res.RelevantTxids { + hash, err := chainhash.NewHash(txid) + if err != nil { + return err + } + log.Infof("Found relevant tx %v in mempool.", hash.String()) + } + return nil +} + +// CreateTx creates and signs a transaction spending from the wallet. +func (w *BDKWallet) CreateTx(feerate float32, recipients []Recipient) ([]byte, error) { + genRecipients := make([]bdkgo.Recipient, 0, len(recipients)) + for _, r := range recipients { + genRecipients = append(genRecipients, bdkgo.Recipient{ + Address: r.Address, + Amount: uint64(r.Amount), + }) + } + return w.inner.CreateTx(feerate, genRecipients) +} + +// MnemonicWords returns the mnemonic words to backup the wallet. +func (w *BDKWallet) MnemonicWords() []string { + return w.inner.MnemonicWords() +} + +// Transactions returns the list of wallet transactions. +func (w *BDKWallet) Transactions() ([]TxInfo, error) { + genOut := w.inner.Transactions() + out := make([]TxInfo, 0, len(genOut)) + + for _, info := range genOut { + tx, err := btcutil.NewTxFromBytes(info.Tx) + if err != nil { + return nil, err + } + + out = append(out, TxInfo{ + Txid: hashFromBytes(info.Txid), + Tx: *tx, + Spent: btcutil.Amount(info.Spent), + Received: btcutil.Amount(info.Received), + Confirmations: uint(info.Confirmations), + }) + } + + return out, nil +} + +// Utxos returns the list of wallet UTXOs. +func (w *BDKWallet) UTXOs() []UTXOInfo { + genOut := w.inner.Utxos() + out := make([]UTXOInfo, 0, len(genOut)) + for _, info := range genOut { + out = append(out, UTXOInfo{ + Txid: hashFromBytes(info.Txid), + Vout: uint(info.Vout), + Amount: btcutil.Amount(info.Amount), + ScriptPubKey: info.ScriptPubkey, + IsChange: info.IsChange, + DerivationIndex: uint(info.DerivationIndex), + Confirmations: uint(info.Confirmations), + }) + } + return out +} diff --git a/bdkwallet/bdkwallet_test.go b/bdkwallet/bdkwallet_test.go new file mode 100644 index 00000000..b06766a8 --- /dev/null +++ b/bdkwallet/bdkwallet_test.go @@ -0,0 +1,74 @@ +//go:build bdkwallet + +package bdkwallet + +import ( + "path/filepath" + "testing" + + "github.com/utreexo/utreexod/chaincfg" +) + +func TestCreateAndLoad(t *testing.T) { + factory, err := factory() + if err != nil { + t.Fatal(err) + } + + dbPath := filepath.Join(t.TempDir(), "bdk.db") + + { + wallet, err := factory.Create(dbPath, &chaincfg.MainNetParams) + if err != nil { + t.Fatalf("failed to create db: %v", err) + } + + freshI, freshAddr, err := wallet.FreshAddress() + if err != nil { + t.Fatalf("failed to get fresh address: %v", err) + } + if freshI != 0 { + t.Fatal("fresh address of new wallet should be 0") + } + t.Logf("address at index 0: %v", freshAddr) + + unusedI, unusedAddr, err := wallet.UnusedAddress() + if err != nil { + t.Fatalf("failed to get unused address: %v", err) + } + if unusedI != freshI { + t.Fatalf("the fresh address is not used so unused index should be 0") + } + if unusedAddr.String() != freshAddr.String() { + t.Fatalf("unused addr should be the same as fresh addr: unused=%v", unusedAddr) + } + + // derive 4 more addresses (total 5 addresses) + for i := 1; i < 5; i++ { + addrI, _, err := wallet.FreshAddress() + if err != nil { + t.Fatalf("failed to derive addr %v: %v", i, err) + } + if i != int(addrI) { + t.Fatalf("derived addr index is unexpected") + } + } + } + + // load wallet and see what happens + { + wallet, err := factory.Load(dbPath) + if err != nil { + t.Fatalf("failed to load wallet: %v", err) + } + + addrI, _, err := wallet.FreshAddress() + if err != nil { + t.Fatalf("failed to get fresh addr: %v", err) + } + + if addrI != 5 { + t.Fatalf("this should be 5: %v", addrI) + } + } +} diff --git a/bdkwallet/log.go b/bdkwallet/log.go new file mode 100644 index 00000000..32ac4554 --- /dev/null +++ b/bdkwallet/log.go @@ -0,0 +1,24 @@ +package bdkwallet + +import "github.com/btcsuite/btclog" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + DisableLog() +} + +// DisableLog disables all library log output. Logging output is disabled +// by default until UseLogger is called. +func DisableLog() { + log = btclog.Disabled +} + +// UseLogger uses a specified Logger to output package logging info. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/bdkwallet/manager.go b/bdkwallet/manager.go new file mode 100644 index 00000000..614cfbb2 --- /dev/null +++ b/bdkwallet/manager.go @@ -0,0 +1,116 @@ +package bdkwallet + +import ( + "os" + "path/filepath" + + "github.com/utreexo/utreexod/blockchain" + "github.com/utreexo/utreexod/btcutil" + "github.com/utreexo/utreexod/chaincfg" + "github.com/utreexo/utreexod/mempool" +) + +var defaultWalletPath = "bdkwallet" +var defaultWalletFileName = "default.dat" + +// ManagerConfig is a configuration struct used to +type ManagerConfig struct { + Chain *blockchain.BlockChain + TxMemPool *mempool.TxPool + ChainParams *chaincfg.Params + DataDir string +} + +// Manager handles the configuration and handling data in between the utreexo node +// and the bdk wallet library. +type Manager struct { + config ManagerConfig + + // Wallet is the underlying wallet that calls out to the + // bdk rust library. + Wallet Wallet // wallet does not need a mutex as it's done in Rust +} + +func WalletDir(dataDir string) string { + return filepath.Join(dataDir, defaultWalletPath) +} + +func DoesWalletDirExist(dataDir string) (bool, error) { + walletDir := WalletDir(dataDir) + if _, err := os.Stat(walletDir); err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return true, nil +} + +func NewManager(config ManagerConfig) (*Manager, error) { + factory, err := factory() + if err != nil { + return nil, err + } + + walletDir := WalletDir(config.DataDir) + if err := os.MkdirAll(walletDir, os.ModePerm); err != nil { + return nil, err + } + + dbPath := filepath.Join(walletDir, defaultWalletFileName) + var wallet Wallet + if _, err := os.Stat(dbPath); err != nil { + if !os.IsNotExist(err) { + return nil, err + } + if wallet, err = factory.Create(dbPath, config.ChainParams); err != nil { + return nil, err + } + } else { + if wallet, err = factory.Load(dbPath); err != nil { + return nil, err + } + } + + m := Manager{ + config: config, + Wallet: wallet, + } + if config.Chain != nil { + // Subscribe to new blocks/reorged blocks. + config.Chain.Subscribe(m.handleBlockchainNotification) + } + + log.Info("Started the BDK wallet manager.") + return &m, nil +} + +func (m *Manager) NotifyNewTransactions(txns []*mempool.TxDesc) { + if m.Wallet == nil { + return + } + + if err := m.Wallet.ApplyMempoolTransactions(txns); err != nil { + log.Errorf("Failed to apply mempool txs to the wallet. %v", err) + } +} + +func (m *Manager) handleBlockchainNotification(notification *blockchain.Notification) { + if m.Wallet == nil { + return + } + + switch notification.Type { + // A block has been accepted into the block chain. + case blockchain.NTBlockConnected: + block, ok := notification.Data.(*btcutil.Block) + if !ok { + log.Warnf("Chain connected notification is not a block.") + return + } + err := m.Wallet.ApplyBlock(block) + if err != nil { + log.Criticalf("Couldn't apply block to the wallet. %v", err) + } + } +} diff --git a/bdkwallet/wallet.go b/bdkwallet/wallet.go new file mode 100644 index 00000000..e7387311 --- /dev/null +++ b/bdkwallet/wallet.go @@ -0,0 +1,111 @@ +package bdkwallet + +import ( + "errors" + + "github.com/utreexo/utreexod/btcutil" + "github.com/utreexo/utreexod/chaincfg" + "github.com/utreexo/utreexod/chaincfg/chainhash" + "github.com/utreexo/utreexod/mempool" +) + +var ( + ErrNoRecipient = errors.New("must have atleast one recipient") + ErrNoBDK = errors.New("utreexod must be built with the 'bdkwallet' tag to enable the BDK wallet") +) + +// walletFactory is nil unless we build with the 'bdkwallet' build tag. +var walletFactory WalletFactory + +// factory returns the wallet factory (if it exists). Otherwise, an error will be returned. +func factory() (WalletFactory, error) { + if walletFactory == nil { + return nil, ErrNoBDK + } + return walletFactory, nil +} + +// WalletFactory creates wallets. +type WalletFactory interface { + Create(dbPath string, chainParams *chaincfg.Params) (Wallet, error) + Load(dbPath string) (Wallet, error) +} + +// Wallet tracks addresses and transactions sending/receiving to/from those addresses. The wallet is +// updated by incoming blocks and new mempool transactions. +type Wallet interface { + UnusedAddress() (uint, btcutil.Address, error) + FreshAddress() (uint, btcutil.Address, error) + PeekAddress(index uint32) (uint, btcutil.Address, error) + Balance() Balance + RecentBlocks(count uint32) []BlockId + ApplyBlock(block *btcutil.Block) error + ApplyMempoolTransactions(txns []*mempool.TxDesc) error + CreateTx(feerate float32, recipients []Recipient) ([]byte, error) + MnemonicWords() []string + Transactions() ([]TxInfo, error) + UTXOs() []UTXOInfo +} + +// Balance in satoshis. +type Balance struct { + Immature btcutil.Amount // immature coinbase balance + TrustedPending btcutil.Amount // unconfirmed balance that is part of our change keychain + UntrustedPending btcutil.Amount // unconfirmed balance that is part of our public keychain + Confirmed btcutil.Amount // confirmed balance +} + +// TrustedSpendable are funds that are safe to spend. +func (b *Balance) TrustedSpendable() btcutil.Amount { + return b.Confirmed + b.TrustedPending +} + +// Total is the total funds of the wallet. +func (b *Balance) Total() btcutil.Amount { + return b.Immature + b.TrustedPending + b.UntrustedPending + b.Confirmed +} + +// BlockId consists of a block height and a block hash. This identifies a block. +type BlockId struct { + Height uint // block height + Hash chainhash.Hash // block hash +} + +// Recipient specifies the intended amount and destination address for a transaction output. +type Recipient struct { + Amount btcutil.Amount // amount to send + Address string // recipient address to send to (in human-readable form) +} + +// TxInfo is information on a given transaction. +type TxInfo struct { + Txid chainhash.Hash + Tx btcutil.Tx + Spent btcutil.Amount // sum of owned inputs + Received btcutil.Amount // sum of owned outputs + Confirmations uint // number of confirmations for this tx +} + +// UtxoInfo is information on a given transaction. +type UTXOInfo struct { + Txid chainhash.Hash + Vout uint + Amount btcutil.Amount + ScriptPubKey []byte + IsChange bool + DerivationIndex uint + Confirmations uint // number of confirmations for this utxo +} + +func hashFromBytes(b []byte) chainhash.Hash { + return *(*[32]byte)(b) +} + +func uintPointerFromUint32Pointer(v *uint32) *uint { + if v == nil { + return nil + } + + v2 := uint(*v) + return &v2 +} diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index 4f43444e..7cc1888b 100644 --- a/btcjson/chainsvrcmds.go +++ b/btcjson/chainsvrcmds.go @@ -48,6 +48,15 @@ func NewAddNodeCmd(addr string, subCmd AddNodeSubCmd) *AddNodeCmd { } } +// BalanceCmd defines the balance JSON-RPC command. +type BalanceCmd struct{} + +// NewBalanceCmd returns a new instance which can be used to issue an balance +// JSON-RPC command. +func NewBalanceCmd() *BalanceCmd { + return &BalanceCmd{} +} + // TransactionInput represents the inputs to a transaction. Specifically a // transaction hash and output number pair. type TransactionInput struct { @@ -55,6 +64,27 @@ type TransactionInput struct { Vout uint32 `json:"vout"` } +// Recipient is the recipient information needed to create a transaction from the bdk wallet. +type Recipient struct { + Amount int64 `json:"amount"` + Address string `json:"address"` +} + +// CreateTransactionFromBDKWalletCmd defines the createtransactionfrombdkwallet JSON-RPC command. +type CreateTransactionFromBDKWalletCmd struct { + FeeRate float32 + Recipients []Recipient +} + +// NewCreateTransactionFromBDKWalletCmd returns a new instance which can be used to issue +// a createtransactionfrombdkwallet command. +func NewCreateTransactionFromBDKWalletCmd(feeRate float32, recipients []Recipient) *CreateTransactionFromBDKWalletCmd { + return &CreateTransactionFromBDKWalletCmd{ + FeeRate: feeRate, + Recipients: recipients, + } +} + // CreateRawTransactionCmd defines the createrawtransaction JSON-RPC command. type CreateRawTransactionCmd struct { Inputs []TransactionInput @@ -149,6 +179,15 @@ type FundRawTransactionOpts struct { EstimateMode *EstimateSmartFeeMode `json:"estimate_mode,omitempty"` } +// FreshAddressCmd defines the freshaddress JSON-RPC command +type FreshAddressCmd struct{} + +// NewFreshAddressCmd returns a new instance which can be used to issue a +// freshaddress JSON-RPC command. +func NewFreshAddressCmd() *FreshAddressCmd { + return &FreshAddressCmd{} +} + // FundRawTransactionCmd defines the fundrawtransaction JSON-RPC command type FundRawTransactionCmd struct { HexTx string @@ -567,6 +606,15 @@ func NewGetMiningInfoCmd() *GetMiningInfoCmd { return &GetMiningInfoCmd{} } +// GetMnemonicWordsCmd defines the getmnemonicwords JSON-RPC command. +type GetMnemonicWordsCmd struct{} + +// NewGetMiningInfoCmd returns a new instance which can be used to issue a +// getmnemonicwords JSON-RPC command. +func NewGetMnemonicWordsCmd() *GetMnemonicWordsCmd { + return &GetMnemonicWordsCmd{} +} + // GetNetworkInfoCmd defines the getnetworkinfo JSON-RPC command. type GetNetworkInfoCmd struct{} @@ -808,6 +856,24 @@ func NewHelpCmd(command *string) *HelpCmd { } } +// ListBDKTransactionsCmd defines the listbdktransactions JSON-RPC command. +type ListBDKTransactionsCmd struct{} + +// NewListBDKTransactionsCmd returns a new instance which can be used to issue a +// listbdktransactions JSON-RPC command. +func NewListBDKTransactionsCmd() *ListBDKTransactionsCmd { + return &ListBDKTransactionsCmd{} +} + +// ListBDKUTXOsCmd defines the listbdkutxos JSON-RPC command. +type ListBDKUTXOsCmd struct{} + +// NewListBDKUTXOsCmd returns a new instance which can be used to issue a +// listbdkutxos JSON-RPC command. +func NewListBDKUTXOsCmd() *ListBDKUTXOsCmd { + return &ListBDKUTXOsCmd{} +} + // InvalidateBlockCmd defines the invalidateblock JSON-RPC command. type InvalidateBlockCmd struct { BlockHash string @@ -821,6 +887,17 @@ func NewInvalidateBlockCmd(blockHash string) *InvalidateBlockCmd { } } +// PeekAddressCmd defines the peekaddress JSON-RPC command. +type PeekAddressCmd struct { + Index uint32 +} + +// NewPeekAddressCmd returns a new instance which can be used to issue a peekaddress JSON-RPC +// command. +func NewPeekAddressCmd() *PeekAddressCmd { + return &PeekAddressCmd{} +} + // PingCmd defines the ping JSON-RPC command. type PingCmd struct{} @@ -900,6 +977,16 @@ func NewRegisterAddressesToWatchOnlyWalletCmd(addresses []string) *RegisterAddre } } +// RebroadcastUnconfirmedBDKTxsCmd defines the rebroadcastunconfirmedbdktxs JSON-RPC +// command. +type RebroadcastUnconfirmedBDKTxsCmd struct{} + +// NewRebroadcastUnconfirmedBDKTxsCmd returns a new instance which can be used to issue a +// registeraddressestowatchonlywallet JSON-RPC command. +func NewRebroadcastUnconfirmedBDKTxsCmd() *RebroadcastUnconfirmedBDKTxsCmd { + return &RebroadcastUnconfirmedBDKTxsCmd{} +} + // SearchRawTransactionsCmd defines the searchrawtransactions JSON-RPC command. type SearchRawTransactionsCmd struct { Address string @@ -1081,6 +1168,15 @@ func NewSubmitBlockCmd(hexBlock string, options *SubmitBlockOptions) *SubmitBloc } } +// UnusedAddressCmd defines the unusedaddress JSON-RPC command. +type UnusedAddressCmd struct{} + +// NewUnusedAddressCmd returns a new instance which can be used to issue an unusedaddress +// JSON-RPC command. +func NewUnusedAddressCmd() *UnusedAddressCmd { + return &UnusedAddressCmd{} +} + // UptimeCmd defines the uptime JSON-RPC command. type UptimeCmd struct{} @@ -1167,10 +1263,13 @@ func init() { flags := UsageFlag(0) MustRegisterCmd("addnode", (*AddNodeCmd)(nil), flags) + MustRegisterCmd("balance", (*BalanceCmd)(nil), flags) + MustRegisterCmd("createtransactionfrombdkwallet", (*CreateTransactionFromBDKWalletCmd)(nil), flags) MustRegisterCmd("createrawtransaction", (*CreateRawTransactionCmd)(nil), flags) MustRegisterCmd("decoderawtransaction", (*DecodeRawTransactionCmd)(nil), flags) MustRegisterCmd("decodescript", (*DecodeScriptCmd)(nil), flags) MustRegisterCmd("deriveaddresses", (*DeriveAddressesCmd)(nil), flags) + MustRegisterCmd("freshaddress", (*FreshAddressCmd)(nil), flags) MustRegisterCmd("fundrawtransaction", (*FundRawTransactionCmd)(nil), flags) MustRegisterCmd("getaddednodeinfo", (*GetAddedNodeInfoCmd)(nil), flags) MustRegisterCmd("getbestblockhash", (*GetBestBlockHashCmd)(nil), flags) @@ -1195,6 +1294,7 @@ func init() { MustRegisterCmd("getmempoolentry", (*GetMempoolEntryCmd)(nil), flags) MustRegisterCmd("getmempoolinfo", (*GetMempoolInfoCmd)(nil), flags) MustRegisterCmd("getmininginfo", (*GetMiningInfoCmd)(nil), flags) + MustRegisterCmd("getmnemonicwords", (*GetMnemonicWordsCmd)(nil), flags) MustRegisterCmd("getnetworkinfo", (*GetNetworkInfoCmd)(nil), flags) MustRegisterCmd("getnettotals", (*GetNetTotalsCmd)(nil), flags) MustRegisterCmd("gettxtotals", (*GetTxTotalsCmd)(nil), flags) @@ -1212,12 +1312,16 @@ func init() { MustRegisterCmd("getwork", (*GetWorkCmd)(nil), flags) MustRegisterCmd("getwatchonlybalance", (*GetWatchOnlyBalanceCmd)(nil), flags) MustRegisterCmd("help", (*HelpCmd)(nil), flags) + MustRegisterCmd("listbdktransactions", (*ListBDKTransactionsCmd)(nil), flags) + MustRegisterCmd("listbdkutxos", (*ListBDKUTXOsCmd)(nil), flags) MustRegisterCmd("invalidateblock", (*InvalidateBlockCmd)(nil), flags) + MustRegisterCmd("peekaddress", (*PeekAddressCmd)(nil), flags) MustRegisterCmd("ping", (*PingCmd)(nil), flags) MustRegisterCmd("preciousblock", (*PreciousBlockCmd)(nil), flags) MustRegisterCmd("proveutxochaintipinclusion", (*ProveUtxoChainTipInclusionCmd)(nil), flags) MustRegisterCmd("provewatchonlychaintipinclusion", (*ProveWatchOnlyChainTipInclusionCmd)(nil), flags) MustRegisterCmd("registeraddressestowatchonlywallet", (*RegisterAddressesToWatchOnlyWalletCmd)(nil), flags) + MustRegisterCmd("rebroadcastunconfirmedbdktxs", (*RebroadcastUnconfirmedBDKTxsCmd)(nil), flags) MustRegisterCmd("reconsiderblock", (*ReconsiderBlockCmd)(nil), flags) MustRegisterCmd("searchrawtransactions", (*SearchRawTransactionsCmd)(nil), flags) MustRegisterCmd("sendrawtransaction", (*SendRawTransactionCmd)(nil), flags) @@ -1225,6 +1329,7 @@ func init() { MustRegisterCmd("signmessagewithprivkey", (*SignMessageWithPrivKeyCmd)(nil), flags) MustRegisterCmd("stop", (*StopCmd)(nil), flags) MustRegisterCmd("submitblock", (*SubmitBlockCmd)(nil), flags) + MustRegisterCmd("unusedaddress", (*UnusedAddressCmd)(nil), flags) MustRegisterCmd("uptime", (*UptimeCmd)(nil), flags) MustRegisterCmd("validateaddress", (*ValidateAddressCmd)(nil), flags) MustRegisterCmd("verifychain", (*VerifyChainCmd)(nil), flags) diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 59041c5d..dc30867f 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -15,6 +15,38 @@ import ( "github.com/utreexo/utreexod/wire" ) +// BalanceResult models the data from the balance command. +type BalanceResult struct { + // Immature is the coinbase balance that's not been confirmed 100 times. + Immature int64 `json:"immature"` + // TrustedPending is the balance that's part of our change keychain. + TrustedPending int64 `json:"trustedpending"` + // UntrustedPending is the balance that's part of our public keychain. + UntrustedPending int64 `json:"untrustedpending"` + // Confirmed is the confirmed balance. + Confirmed int64 `json:"confirmed"` +} + +// ListBDKTransactionsResult models the data from the listbdktransactions command. +type ListBDKTransactionsResult struct { + Txid string `json:"txid"` // txid of the tx + RawBytes string `json:"rawbytes"` // hex encoded raw tx + Spent int64 `json:"spent"` // sum of owned inputs + Received int64 `json:"received"` // sum of owned outputs + Confirmations uint `json:"confirmations"` // number of confirmations for this tx +} + +// ListBDKUTXOsResult models the data from the listbdkutxos command. +type ListBDKUTXOsResult struct { + Txid string `json:"txid"` + Vout uint `json:"vout"` + Amount int64 `json:"amount"` + ScriptPubKey string `json:"scriptpubkey"` + IsChange bool `json:"ischange"` + DerivationIndex uint `json:"derivationindex"` + Confirmations uint `json:"confirmations"` +} + // GetBlockHeaderVerboseResult models the data from the getblockheader command when // the verbose flag is set. When the verbose flag is not set, getblockheader // returns a hex-encoded string. @@ -909,3 +941,17 @@ type ProveWatchOnlyChainTipInclusionVerboseResult struct { HashesProven []string `json:"hashesproven"` Hex string `json:"hex"` } + +// BDKAddressResult models the data for all rpc calls that the bdk wallet returns. +// This includes the following commands: unusedaddress, freshaddress, and peekaddress. +type BDKAddressResult struct { + Index int `json:"index"` + Address string `json:"address"` +} + +// CreateTransactionFromBDKWalletResult models the data from the +// createtransactionfrombdkwallet command. +type CreateTransactionFromBDKWalletResult struct { + TxHash string `json:"txhash"` + RawBytes string `json:"rawbytes"` +} diff --git a/config.go b/config.go index df18ccf5..ee3f3965 100644 --- a/config.go +++ b/config.go @@ -216,6 +216,7 @@ type config struct { RegisterAddressToWatchOnlyWallet []string `long:"registeraddresstowatchonlywallet" description:"Registers addresses to be watched to the watch only wallet. Must have --watchonlywallet enabled"` RegisterExtendedPubKeysToWatchOnlyWallet []string `long:"registerextendedpubkeystowatchonlywallet" description:"Registers extended pubkeys to be watched to the watch only wallet. Must have --watchonlywallet enabled."` RegisterExtendedPubKeysWithAddrTypeToWatchOnlyWallet []string `long:"registerextendedpubkeyswithaddresstypetowatchonlywallet" description:"Registers extended pubkeys to be watched to the watch only wallet and let's the user override the hd type of the extended public key. Must have --watchonlywallet enabled. Format: ':
. Supported address types: '{p2pkh, p2wpkh, p2sh}'"` + NoBdkWallet bool `long:"nobdkwallet" description:"Disable the BDK wallet."` // Electrum server options. ElectrumListeners []string `long:"electrumlisteners" description:"Interface/port for the electrum server to listen to. (default 50001). Electrum server is only enabled when --watchonlywallet is enabled"` diff --git a/goclean.sh b/goclean.sh index e77d580b..e5b819bb 100755 --- a/goclean.sh +++ b/goclean.sh @@ -7,11 +7,12 @@ set -ex -go test -short -race -tags="rpctest" ./... +go test -short -race -tags="rpctest" -tags="bdkwallet" ./... # Automatic checks golangci-lint run --deadline=10m --disable-all \ --enable=gofmt \ --enable=vet \ --enable=gosimple \ ---enable=unconvert +--enable=unconvert \ +--skip-dirs=bdkwallet/bdkgo # these are generated files diff --git a/integration/getchaintips_test.go b/integration/getchaintips_test.go index 9fb62bdc..f7f20b01 100644 --- a/integration/getchaintips_test.go +++ b/integration/getchaintips_test.go @@ -167,7 +167,7 @@ func TestGetChainTips(t *testing.T) { "0000000000000000000000000000000000000000000000000000" // Set up regtest chain. - r, err := rpctest.New(&chaincfg.RegressionNetParams, nil, []string{"--noutreexo"}, "") + r, err := rpctest.New(&chaincfg.RegressionNetParams, nil, []string{"--noutreexo", "--nobdkwallet"}, "") if err != nil { t.Fatal("TestGetChainTips fail. Unable to create primary harness: ", err) } diff --git a/integration/getutreexoroots_test.go b/integration/getutreexoroots_test.go index 82291f62..f3777276 100644 --- a/integration/getutreexoroots_test.go +++ b/integration/getutreexoroots_test.go @@ -63,6 +63,7 @@ func TestGetUtreexoRoots(t *testing.T) { utreexoProofIdxArgs := []string{ "--utreexoproofindex", "--noutreexo", + "--nobdkwallet", } utreexoProofNode, err := rpctest.New(&chaincfg.RegressionNetParams, nil, utreexoProofIdxArgs, "") if err != nil { @@ -77,6 +78,7 @@ func TestGetUtreexoRoots(t *testing.T) { flatUtreexoProofIdxArgs := []string{ "--flatutreexoproofindex", "--noutreexo", + "--nobdkwallet", } flatUtreexoProofNode, err := rpctest.New(&chaincfg.RegressionNetParams, nil, flatUtreexoProofIdxArgs, "") if err != nil { @@ -88,7 +90,7 @@ func TestGetUtreexoRoots(t *testing.T) { defer flatUtreexoProofNode.TearDown() // Set up the CSN. - csn, err := rpctest.New(&chaincfg.RegressionNetParams, nil, nil, "") + csn, err := rpctest.New(&chaincfg.RegressionNetParams, nil, []string{"--nobdkwallet"}, "") if err != nil { t.Fatal("TestGetUtreexoRoots fail. Unable to create primary harness: ", err) } diff --git a/integration/invalidate_reconsider_block_test.go b/integration/invalidate_reconsider_block_test.go index de26ee9d..7fff3b3e 100644 --- a/integration/invalidate_reconsider_block_test.go +++ b/integration/invalidate_reconsider_block_test.go @@ -9,7 +9,7 @@ import ( func TestInvalidateAndReconsiderBlock(t *testing.T) { // Set up regtest chain. - r, err := rpctest.New(&chaincfg.RegressionNetParams, nil, []string{"--flatutreexoproofindex", "--utreexoproofindex"}, "") + r, err := rpctest.New(&chaincfg.RegressionNetParams, nil, []string{"--flatutreexoproofindex", "--utreexoproofindex", "--nobdkwallet"}, "") if err != nil { t.Fatalf("TestInvalidateAndReconsiderBlock fail. Unable to create primary harness: %v", err) } diff --git a/integration/utreexocompactstatenode_test.go b/integration/utreexocompactstatenode_test.go index 13ca90b3..5dfaa25d 100644 --- a/integration/utreexocompactstatenode_test.go +++ b/integration/utreexocompactstatenode_test.go @@ -87,6 +87,7 @@ func TestUtreexoCSN(t *testing.T) { bridgeNodeArgs := []string{ "--flatutreexoproofindex", "--noutreexo", + "--nobdkwallet", } // Set up regtest chain for the bridge node. bridgeNode, err := rpctest.New(&chaincfg.RegressionNetParams, nil, bridgeNodeArgs, "") @@ -190,6 +191,7 @@ func TestUtreexoCSN(t *testing.T) { // Set up the CSN. csnArgs := []string{ "--watchonlywallet", + "--nobdkwallet", } for _, addr := range watchAddrs { csnArgs = append(csnArgs, fmt.Sprintf("--registeraddresstowatchonlywallet=%s", addr.String())) diff --git a/log.go b/log.go index 95349560..02f006a3 100644 --- a/log.go +++ b/log.go @@ -11,6 +11,7 @@ import ( "path/filepath" "github.com/utreexo/utreexod/addrmgr" + "github.com/utreexo/utreexod/bdkwallet" "github.com/utreexo/utreexod/blockchain" "github.com/utreexo/utreexod/blockchain/indexers" "github.com/utreexo/utreexod/connmgr" @@ -73,6 +74,7 @@ var ( txmpLog = backendLog.Logger("TXMP") wlltLog = backendLog.Logger("WLLT") elecLog = backendLog.Logger("ELEC") + bdkwLog = backendLog.Logger("BDKW") ) // Initialize package-global logger variables. @@ -90,6 +92,7 @@ func init() { mempool.UseLogger(txmpLog) wallet.UseLogger(wlltLog) electrum.UseLogger(elecLog) + bdkwallet.UseLogger(bdkwLog) } // subsystemLoggers maps each subsystem identifier to its associated logger. @@ -111,6 +114,7 @@ var subsystemLoggers = map[string]btclog.Logger{ "TXMP": txmpLog, "WLLT": wlltLog, "ELEC": elecLog, + "BDKW": bdkwLog, } // initLogRotator initializes the logging rotater to write logs to logFile and diff --git a/rpcserver.go b/rpcserver.go index 2c09d09e..343b865f 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -29,6 +29,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/websocket" + "github.com/utreexo/utreexod/bdkwallet" "github.com/utreexo/utreexod/blockchain" "github.com/utreexo/utreexod/blockchain/indexers" "github.com/utreexo/utreexod/btcjson" @@ -120,7 +121,7 @@ var ( // command is recognized as a wallet command. ErrRPCNoWallet = &btcjson.RPCError{ Code: btcjson.ErrRPCNoWallet, - Message: "This implementation does not implement wallet commands", + Message: "This implementation does not implement btcwallet commands", } ) @@ -132,11 +133,14 @@ type commandHandler func(*rpcServer, interface{}, <-chan struct{}) (interface{}, var rpcHandlers map[string]commandHandler var rpcHandlersBeforeInit = map[string]commandHandler{ "addnode": handleAddNode, + "balance": handleBalance, + "createtransactionfrombdkwallet": handleCreateTransactionFromBDKWallet, "createrawtransaction": handleCreateRawTransaction, "debuglevel": handleDebugLevel, "decoderawtransaction": handleDecodeRawTransaction, "decodescript": handleDecodeScript, "estimatefee": handleEstimateFee, + "freshaddress": handleFreshAddress, "generate": handleGenerate, "getaddednodeinfo": handleGetAddedNodeInfo, "getbestblock": handleGetBestBlock, @@ -159,6 +163,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "getinfo": handleGetInfo, "getmempoolinfo": handleGetMempoolInfo, "getmininginfo": handleGetMiningInfo, + "getmnemonicwords": handleGetMnemonicWords, "getnettotals": handleGetNetTotals, "gettxtotals": handleGetTxTotals, "getnetworkhashps": handleGetNetworkHashPS, @@ -173,10 +178,14 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "getwatchonlybalance": handleGetWatchOnlyBalance, "invalidateblock": handleInvalidateBlock, "help": handleHelp, + "listbdktransactions": handleListBDKTransactions, + "listbdkutxos": handleListBDKUTXOs, "node": handleNode, + "peekaddress": handlePeekAddress, "ping": handlePing, "proveutxochaintipinclusion": handleProveUtxoChainTipInclusion, "provewatchonlychaintipinclusion": handleProveWatchOnlyChainTipInclusion, + "rebroadcastunconfirmedbdktxs": handleRebroadcastUnconfirmedBDKTxs, "reconsiderblock": handleReconsiderBlock, "registeraddressestowatchonlywallet": handleRegisterAddressesToWatchOnlyWallet, "searchrawtransactions": handleSearchRawTransactions, @@ -185,6 +194,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "signmessagewithprivkey": handleSignMessageWithPrivKey, "stop": handleStop, "submitblock": handleSubmitBlock, + "unusedaddress": handleUnusedAddress, "uptime": handleUptime, "validateaddress": handleValidateAddress, "verifychain": handleVerifyChain, @@ -412,6 +422,17 @@ func handleAddNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (in return nil, nil } +// handleBalance handles the balance command. +func handleBalance(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + balance := s.cfg.BDKWallet.Wallet.Balance() + return btcjson.BalanceResult{ + Immature: int64(balance.Immature), + TrustedPending: int64(balance.TrustedPending), + UntrustedPending: int64(balance.UntrustedPending), + Confirmed: int64(balance.Confirmed), + }, nil +} + // handleNode handles node commands. func handleNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*btcjson.NodeCmd) @@ -518,6 +539,81 @@ func peerExists(connMgr rpcserverConnManager, addr string, nodeID int32) bool { return false } +// handleCreateTransactionFromBDKWallet handles createtransactionfrombdkwallet command. +func handleCreateTransactionFromBDKWallet(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + // Before doing anything, check that the bdk wallet is active. + if s.cfg.BDKWallet == nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: "BDK wallet must be enabled. (--bdkwallet)", + } + } + + c := cmd.(*btcjson.CreateTransactionFromBDKWalletCmd) + + recipients := make([]bdkwallet.Recipient, len(c.Recipients)) + for i := range recipients { + addr, err := btcutil.DecodeAddress(c.Recipients[i].Address, s.cfg.ChainParams) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: fmt.Sprintf("Couldn't decode address %v. %v", + c.Recipients[i].Address, err), + } + } + + recipients[i].Address = addr.String() + recipients[i].Amount = btcutil.Amount(c.Recipients[i].Amount) + } + + bytes, err := s.cfg.BDKWallet.Wallet.CreateTx(c.FeeRate, recipients) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: fmt.Sprintf("Failed to create tx. %v", err), + } + } + + tx, err := btcutil.NewTxFromBytes(bytes) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: fmt.Sprintf("Failed to create tx. %v", err), + } + } + + if s.cfg.Chain.IsUtreexoViewActive() { + // This is not ideal but since bdk doesn't handle utreexo proofs yet, there's + // no way for us to broadcast it normally unless we make some undesired changes + // to the mempool code. Just send it off to mempool.space until we can do things + // properly. + _, err = sendTxToMempoolSpace(txHexString(tx.MsgTx()), s.cfg.ChainParams) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: fmt.Sprintf("Failed to broadcast transaction to mempool.space. %v", err), + } + } + txD := &mempool.TxDesc{ + TxDesc: mining.TxDesc{Tx: tx}, + } + // Notify bdkwallet and other listeners. + s.NotifyNewTransactions([]*mempool.TxDesc{txD}) + } else { + err = s.rpcProcessTx(tx, true, false) + if err != nil { + return nil, err + } + } + + res := btcjson.CreateTransactionFromBDKWalletResult{ + TxHash: tx.Hash().String(), + RawBytes: hex.EncodeToString(bytes), + } + + return res, nil +} + // messageToHex serializes a message to the wire protocol encoding using the // latest protocol version and returns a hex-encoded string of the result. func messageToHex(msg wire.Message) (string, error) { @@ -899,6 +995,20 @@ func handleEstimateFee(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) return float64(feeRate), nil } +// handleFreshAddress implements the freshaddress command. +func handleFreshAddress(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + index, address, err := s.cfg.BDKWallet.Wallet.FreshAddress() + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: "Failed to retrieve new address: " + err.Error(), + } + } + + result := btcjson.BDKAddressResult{Index: int(index), Address: address.String()} + return result, nil +} + // handleGenerate handles generate commands. func handleGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { // Respond with an error if there are no addresses to pay the @@ -2430,6 +2540,12 @@ func handleGetMiningInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{ return &result, nil } +// handleGetMnemonicWords implements the getmnemonicwords command. +func handleGetMnemonicWords(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + words := s.cfg.BDKWallet.Wallet.MnemonicWords() + return words, nil +} + // handleGetNetTotals implements the getnettotals command. func handleGetNetTotals(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { totalBytesRecv, totalBytesSent := s.cfg.ConnMgr.NetTotals() @@ -2970,6 +3086,76 @@ func handleHelp(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (inter return help, nil } +// handleListBDKTransactions handles listbdktransactions commands. +func handleListBDKTransactions(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + txs, err := s.cfg.BDKWallet.Wallet.Transactions() + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: "Unable to fetch transactions " + err.Error(), + } + } + + res := make([]btcjson.ListBDKTransactionsResult, len(txs)) + for i := range res { + + var buf bytes.Buffer + err := txs[i].Tx.MsgTx().Serialize(&buf) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: "Unable to serialize tx " + err.Error(), + } + } + res[i] = btcjson.ListBDKTransactionsResult{ + Txid: txs[i].Txid.String(), + RawBytes: hex.EncodeToString(buf.Bytes()), + Spent: int64(txs[i].Spent), + Received: int64(txs[i].Received), + Confirmations: txs[i].Confirmations, + } + } + + return res, nil +} + +// handleListBDKUTXOs handles handlelistbdkutxos commands. +func handleListBDKUTXOs(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + utxos := s.cfg.BDKWallet.Wallet.UTXOs() + + res := make([]btcjson.ListBDKUTXOsResult, len(utxos)) + for i := range res { + res[i] = btcjson.ListBDKUTXOsResult{ + Txid: utxos[i].Txid.String(), + Vout: utxos[i].Vout, + Amount: int64(utxos[i].Amount), + ScriptPubKey: hex.EncodeToString(utxos[i].ScriptPubKey), + IsChange: utxos[i].IsChange, + DerivationIndex: utxos[i].DerivationIndex, + Confirmations: utxos[i].Confirmations, + } + } + + return res, nil +} + +// handlePeekAddress implements the peekaddress command. +func handlePeekAddress(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + c := cmd.(*btcjson.PeekAddressCmd) + + index, address, err := s.cfg.BDKWallet.Wallet.PeekAddress(c.Index) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: fmt.Sprintf("Failed to retrieve address at index %v: %v", + c.Index, err), + } + } + + result := btcjson.BDKAddressResult{Index: int(index), Address: address.String()} + return result, nil +} + // handlePing implements the ping command. func handlePing(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { // Ask server to ping \o_ @@ -3610,6 +3796,93 @@ func fetchMempoolTxnsForAddress(s *rpcServer, addr btcutil.Address, numToSkip, n return mpTxns[numToSkip:rangeEnd], numToSkip } +func sendTxToMempoolSpace(rawTx string, chainParams *chaincfg.Params) (string, error) { + client := &http.Client{} + var data = strings.NewReader(rawTx) + + link := "https://mempool.space/api/tx" + switch chainParams.Name { + case chaincfg.MainNetParams.Name: + case chaincfg.TestNet3Params.Name: + link = "https://mempool.space/testnet/api/tx" + case chaincfg.SigNetParams.Name: + link = "https://mempool.space/signet/api/tx" + default: + return "", fmt.Errorf("unsupported network %v", chainParams.Name) + } + + req, err := http.NewRequest("POST", link, data) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + bodyText, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(bodyText), nil +} + +// handleRebroadcastUnconfirmedBDKTxs implements the rebroadcastunconfirmedbdktxs command. +func handleRebroadcastUnconfirmedBDKTxs(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + allTxs, err := s.cfg.BDKWallet.Wallet.Transactions() + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: fmt.Sprintf("Failed to fetch transaction from bdk wallet. %v", err), + } + } + + unconfirmedTxs := make([]bdkwallet.TxInfo, 0, len(allTxs)) + for _, tx := range allTxs { + if tx.Confirmations == 0 { + unconfirmedTxs = append(unconfirmedTxs, tx) + } + } + + // Nothing to rebroadcast. + if len(unconfirmedTxs) == 0 { + return []string{}, nil + } + + rebroadcasted := make([]string, 0, len(unconfirmedTxs)) + for _, unconfirmedTx := range unconfirmedTxs { + tx := unconfirmedTx.Tx + + // If it's already in the mempool, then we've already broadcasted it. + if s.cfg.TxMemPool.IsTransactionInPool(tx.Hash()) { + continue + } + + if s.cfg.Chain.IsUtreexoViewActive() { + // This is not ideal but since bdk doesn't handle utreexo proofs yet, there's + // no way for us to broadcast it normally unless we make some undesired changes + // to the mempool code. Just send it off to mempool.space until we can do things + // properly. + _, err = sendTxToMempoolSpace(txHexString(tx.MsgTx()), s.cfg.ChainParams) + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: fmt.Sprintf("Failed to broadcast transaction to mempool.space. %v", err), + } + } else { + err = s.rpcProcessTx(&tx, true, false) + if err != nil { + return nil, err + } + } + + rebroadcasted = append(rebroadcasted, unconfirmedTx.Tx.Hash().String()) + } + + return rebroadcasted, nil +} + // handleReconsiderBlock implements the reconsiderblock command. func handleReconsiderBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*btcjson.ReconsiderBlockCmd) @@ -4151,6 +4424,20 @@ func handleSubmitBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) return nil, nil } +// handleUnusedAddress implements the unusedaddress command. +func handleUnusedAddress(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + index, address, err := s.cfg.BDKWallet.Wallet.UnusedAddress() + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: "Failed to retrieve unused address: " + err.Error(), + } + } + + result := btcjson.BDKAddressResult{Index: int(index), Address: address.String()} + return result, nil +} + // handleUptime implements the uptime command. func handleUptime(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { return time.Now().Unix() - s.cfg.StartupTime, nil @@ -4504,6 +4791,14 @@ func (s *rpcServer) NotifyNewTransactions(txns []*mempool.TxDesc) { // Potentially notify any getblocktemplate long poll clients // about stale block templates due to the new transaction. s.gbtWorkState.NotifyMempoolTx(s.cfg.TxMemPool.LastUpdated()) + + if s.cfg.BDKWallet != nil { + s.cfg.BDKWallet.NotifyNewTransactions(txns) + } + + if s.cfg.WatchOnlyWallet != nil { + s.cfg.WatchOnlyWallet.NotifyNewTransactions(txns) + } } } @@ -5248,6 +5543,9 @@ type rpcserverConfig struct { // WatchOnlyWallet keeps track of relevant utxos and its utreexo proof // for the given addresses and xpubs. WatchOnlyWallet *wallet.WatchOnlyWalletManager + + // BDKWallet is the underlying bdk wallet that is a part of this node. + BDKWallet *bdkwallet.Manager } // newRPCServer returns a new instance of the rpcServer struct. diff --git a/rpcserverhelp.go b/rpcserverhelp.go index 98cd068b..8333e6f7 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -34,6 +34,28 @@ var helpDescsEnUS = map[string]string{ "addnode-addr": "IP address and port of the peer to operate on", "addnode-subcmd": "'add' to add a persistent peer, 'remove' to remove a persistent peer, or 'onetry' to try a single connection to a peer", + // BalanceCmd help. + "balance--synopsis": "Retrieves the balance from the underlying bdkwallet.", + + // BalanceResult help. + "balanceresult-immature": "The coinbase balance that's not been confirmed 100 times and therefore can't be spent.", + "balanceresult-trustedpending": "The balance that's part of our change keychain.", + "balanceresult-untrustedpending": "The balance that's part of our public keychain.", + "balanceresult-confirmed": "The confirmed balance.", + + // Recipient help. + "recipient-amount": "The amount in satoshis to send to the recipient.", + "recipient-address": "The address of the recipient.", + + // CreateTransactionFromBDKWalletCmd help. + "createtransactionfrombdkwallet--synopsis": "Creates and returns a hex encoded transaction from the underlying bdk walllet that's ready to broadcast", + "createtransactionfrombdkwallet-feerate": "Feerate in satoshis that this tx will be paying", + "createtransactionfrombdkwallet-recipients": "List of recipients that this tx will be paying", + + // CreateTransactionFromBDKWalletResult help. + "createtransactionfrombdkwalletresult-txhash": "Txid of the transaction", + "createtransactionfrombdkwalletresult-rawbytes": "Hex-encoded bytes of the serialized transaction", + // NodeCmd help. "node--synopsis": "Attempts to add or remove a peer.", "node-subcmd": "'disconnect' to remove all matching non-persistent peers, 'remove' to remove a persistent peer, or 'connect' to connect to a peer", @@ -100,6 +122,10 @@ var helpDescsEnUS = map[string]string{ "txrawdecoderesult-vin": "The transaction inputs as JSON objects", "txrawdecoderesult-vout": "The transaction outputs as JSON objects", + // BDKAddressResult help. + "bdkaddressresult-index": "BIP86 index of the address", + "bdkaddressresult-address": "Taproot address that you can receive funds to", + // DecodeRawTransactionCmd help. "decoderawtransaction--synopsis": "Returns a JSON object representing the provided serialized, hex-encoded transaction.", "decoderawtransaction-hextx": "Serialized, hex-encoded transaction", @@ -124,6 +150,10 @@ var helpDescsEnUS = map[string]string{ "estimatefee--result0": "Estimated fee per kilobyte in satoshis for a block to " + "be mined in the next NumBlocks blocks.", + // FreshAddressCmd help. + "freshaddress--synopsis": "Returns an address of the next derivation index regardless of if the " + + "preivous derivation address has received funds or not.", + // GenerateCmd help "generate--synopsis": "Generates a set number of blocks (simnet or regtest only) and returns a JSON\n" + " array of their hashes.", @@ -451,6 +481,10 @@ var helpDescsEnUS = map[string]string{ // GetMiningInfoCmd help. "getmininginfo--synopsis": "Returns a JSON object containing mining-related information.", + // GetMnemonicWordsCmd help. + "getmnemonicwords--synopsis": "Returns the mnemonic words of the bdk wallet.", + "getmnemonicwords--result0": "12 mnemonic words of the bdk wallet.", + // GetNetworkHashPSCmd help. "getnetworkhashps--synopsis": "Returns the estimated network hashes per second for the block heights provided by the parameters.", "getnetworkhashps-blocks": "The number of blocks, or -1 for blocks since last difficulty change", @@ -600,6 +634,32 @@ var helpDescsEnUS = map[string]string{ "help--result0": "List of commands", "help--result1": "Help for specified command", + // ListBDKTransactionsCmd help. + "listbdktransactions--synopsis": "Returns a list of all the relevant transactions the bdk wallet is holding onto", + + // ListBDKTransactionsResult help. + "listbdktransactionsresult-txid": "The transaction hash of the relevant transaction", + "listbdktransactionsresult-rawbytes": "The consensus encoded transaction in hex.", + "listbdktransactionsresult-spent": "The sum of satoshis that was spent in this tx.", + "listbdktransactionsresult-received": "The sum of satoshis that was received in this tx.", + "listbdktransactionsresult-confirmations": "The amount of blockchain confirmations for this tx.", + + // ListBDKUTXOsCmd help. + "listbdkutxos--synopsis": "Returns a list of all the relevant utxos the bdk wallet is holding onto", + + // ListBDKUTXOsResult help. + "listbdkutxosresult-txid": "The txid of the relevant utxo.", + "listbdkutxosresult-vout": "The output index of the relevant utxo.", + "listbdkutxosresult-amount": "The amount in satoshis this utxo is worth.", + "listbdkutxosresult-scriptpubkey": "The script pubkey of the utxo.", + "listbdkutxosresult-ischange": "Whether or not this utxo is a change output.", + "listbdkutxosresult-derivationindex": "The derivation index of the wallet this utxo is located at.", + "listbdkutxosresult-confirmations": "The total amount of blockchain confirmations this utxo has.", + + // PeekAddressCmd help. + "peekaddress--synopsis": "Returns an address of the desired derivation index", + "peekaddress-index": "The desired derivation index you want to fetch the address at", + // PingCmd help. "ping--synopsis": "Queues a ping to be sent to each connected peer.\n" + "Ping times are provided by getpeerinfo via the pingtime and pingwait fields.", @@ -635,6 +695,10 @@ var helpDescsEnUS = map[string]string{ "Note that these are not purely hashes of txid:vout. The preimage also include Amount, PkScript, and other parts of the UTXO", "provewatchonlychaintipinclusionverboseresult-hex": "The raw hash of the entire chain-tip inclusion proof", + // RebroadcastUnconfirmedBDKTxs help. + "rebroadcastunconfirmedbdktxs--synopsis": "Rebroadcasts the unconfirmed txs in the bdk wallet to the network. Won't rebroadcast the txs already in this node's mempool.", + "rebroadcastunconfirmedbdktxs--result0": "List of txids of the rebroadcasted txs", + // SearchRawTransactionsCmd help. "searchrawtransactions--synopsis": "Returns raw data for transactions involving the passed address.\n" + "Returned transactions are pulled from both the database, and transactions currently in the mempool.\n" + @@ -791,6 +855,9 @@ var helpDescsEnUS = map[string]string{ "rescannedblock-hash": "Hash of the matching block.", "rescannedblock-transactions": "List of matching transactions, serialized and hex-encoded.", + // UnusedAddressCmd help. + "unusedaddress--synopsis": "Returns an address that never received funds from the bdkwallet.", + // Uptime help. "uptime--synopsis": "Returns the total uptime of the server.", "uptime--result0": "The number of seconds that the server has been running", @@ -815,11 +882,14 @@ var helpDescsEnUS = map[string]string{ // pointer to the type (or nil to indicate no return value). var rpcResultTypes = map[string][]interface{}{ "addnode": nil, + "balance": {(*btcjson.BalanceResult)(nil)}, "createrawtransaction": {(*string)(nil)}, + "createtransactionfrombdkwallet": {(*btcjson.CreateTransactionFromBDKWalletResult)(nil)}, "debuglevel": {(*string)(nil), (*string)(nil)}, "decoderawtransaction": {(*btcjson.TxRawDecodeResult)(nil)}, "decodescript": {(*btcjson.DecodeScriptResult)(nil)}, "estimatefee": {(*float64)(nil)}, + "freshaddress": {(*btcjson.BDKAddressResult)(nil)}, "generate": {(*[]string)(nil)}, "getaddednodeinfo": {(*[]string)(nil), (*[]btcjson.GetAddedNodeInfoResult)(nil)}, "getbestblock": {(*btcjson.GetBestBlockResult)(nil)}, @@ -842,6 +912,7 @@ var rpcResultTypes = map[string][]interface{}{ "getinfo": {(*btcjson.InfoChainResult)(nil)}, "getmempoolinfo": {(*btcjson.GetMempoolInfoResult)(nil)}, "getmininginfo": {(*btcjson.GetMiningInfoResult)(nil)}, + "getmnemonicwords": {(*[]string)(nil)}, "getnettotals": {(*btcjson.GetNetTotalsResult)(nil)}, "gettxtotals": {(*btcjson.GetTxTotalsResult)(nil)}, "getutreexoproof": {(*btcjson.GetUtreexoProofVerboseResult)(nil)}, @@ -857,9 +928,13 @@ var rpcResultTypes = map[string][]interface{}{ "node": nil, "help": {(*string)(nil), (*string)(nil)}, "invalidateblock": nil, + "listbdktransactions": {(*[]btcjson.ListBDKTransactionsResult)(nil)}, + "listbdkutxos": {(*[]btcjson.ListBDKUTXOsResult)(nil)}, + "peekaddress": {(*btcjson.BDKAddressResult)(nil)}, "ping": nil, "proveutxochaintipinclusion": {(*btcjson.ProveUtxoChainTipInclusionVerboseResult)(nil)}, "provewatchonlychaintipinclusion": {(*btcjson.ProveWatchOnlyChainTipInclusionVerboseResult)(nil)}, + "rebroadcastunconfirmedbdktxs": {(*[]string)(nil)}, "registeraddressestowatchonlywallet": nil, "reconsiderblock": nil, "searchrawtransactions": {(*string)(nil), (*[]btcjson.SearchRawTransactionsResult)(nil)}, @@ -868,6 +943,7 @@ var rpcResultTypes = map[string][]interface{}{ "signmessagewithprivkey": {(*string)(nil)}, "stop": {(*string)(nil)}, "submitblock": {nil, (*string)(nil)}, + "unusedaddress": {(*btcjson.BDKAddressResult)(nil)}, "uptime": {(*int64)(nil)}, "validateaddress": {(*btcjson.ValidateAddressChainResult)(nil)}, "verifychain": {(*bool)(nil)}, diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..b023ced5 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.72.0" +components = ["clippy", "rustfmt"] diff --git a/server.go b/server.go index f18da840..baa5fd04 100644 --- a/server.go +++ b/server.go @@ -23,6 +23,7 @@ import ( "time" "github.com/utreexo/utreexod/addrmgr" + "github.com/utreexo/utreexod/bdkwallet" "github.com/utreexo/utreexod/blockchain" "github.com/utreexo/utreexod/blockchain/indexers" "github.com/utreexo/utreexod/btcutil" @@ -267,6 +268,9 @@ type server struct { // the database and the watch only wallet and serves them to the connected client. electrumServer *electrum.ElectrumServer + // bdkWallet keeps track of a wallet + bdkWallet *bdkwallet.Manager + // cfCheckptCaches stores a cached slice of filter headers for cfcheckpt // messages for each filter type. cfCheckptCaches map[wire.FilterType][]cfHeaderKV @@ -1505,6 +1509,10 @@ func (s *server) AnnounceNewTransactions(txns []*mempool.TxDesc) { s.rpcServer.NotifyNewTransactions(txns) } + if s.bdkWallet != nil { + s.bdkWallet.NotifyNewTransactions(txns) + } + if s.watchOnlyWallet != nil { s.watchOnlyWallet.NotifyNewTransactions(txns) } @@ -3533,6 +3541,19 @@ func newServer(listenAddrs, agentBlacklist, agentWhitelist []string, } } + if !cfg.NoBdkWallet { + // Setup BDK wallet if it is enabled. + s.bdkWallet, err = bdkwallet.NewManager(bdkwallet.ManagerConfig{ + Chain: s.chain, + TxMemPool: s.txMemPool, + ChainParams: chainParams, + DataDir: cfg.DataDir, + }) + if err != nil { + return nil, err + } + } + if !cfg.DisableRPC { // Setup listeners for the configured RPC listen addresses and // TLS settings. @@ -3564,6 +3585,7 @@ func newServer(listenAddrs, agentBlacklist, agentWhitelist []string, FlatUtreexoProofIndex: s.flatUtreexoProofIndex, FeeEstimator: s.feeEstimator, WatchOnlyWallet: s.watchOnlyWallet, + BDKWallet: s.bdkWallet, }) if err != nil { return nil, err diff --git a/utreexod.go b/utreexod.go index d2b1cc0a..1b2528c8 100644 --- a/utreexod.go +++ b/utreexod.go @@ -16,6 +16,7 @@ import ( "runtime/pprof" "runtime/trace" + "github.com/utreexo/utreexod/bdkwallet" "github.com/utreexo/utreexod/blockchain" "github.com/utreexo/utreexod/blockchain/indexers" "github.com/utreexo/utreexod/database" @@ -349,6 +350,16 @@ func btcdMain(serverChan chan<- *server) error { btcdLog.Error(err) return err } + + // If the node had been run with wallet enabled but it now re-started with wallet disabled. + var walletDirExists bool + if walletDirExists, err = bdkwallet.DoesWalletDirExist(cfg.DataDir); err != nil { + btcdLog.Error(err) + return err + } + if walletDirExists { + cfg.NoBdkWallet = false + } } // Check if the user had been pruned before and do basic checks on indexers.