diff --git a/rpc/http.go b/rpc/http.go index 8700fc557..743dc0a42 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -143,6 +143,13 @@ func (impl *RPC) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else { rdr.RenderData(tx) } + case "getwithdrawalclaim": + tx, err := readWithdrawal(impl.Store, call.Params) + if err != nil { + rdr.RenderError(err) + } else { + rdr.RenderData(tx) + } case "getutxo": utxo, err := getUTXO(impl.Store, call.Params) if err != nil { diff --git a/rpc/withdrawal.go b/rpc/withdrawal.go new file mode 100644 index 000000000..93a8e1b4b --- /dev/null +++ b/rpc/withdrawal.go @@ -0,0 +1,32 @@ +package rpc + +import ( + "encoding/hex" + "errors" + "fmt" + + "github.com/MixinNetwork/mixin/crypto" + "github.com/MixinNetwork/mixin/storage" +) + +func readWithdrawal(store storage.Store, params []any) (map[string]any, error) { + if len(params) != 1 { + return nil, errors.New("invalid params count") + } + hash, err := crypto.HashFromString(fmt.Sprint(params[1])) + if err != nil { + return nil, err + } + + ver, snap, err := store.ReadWithdrawalClaim(hash) + if err != nil || ver == nil { + return nil, err + } + + data := transactionToMap(ver) + data["hex"] = hex.EncodeToString(ver.Marshal()) + if len(snap) > 0 { + data["snapshot"] = snap + } + return data, nil +} diff --git a/storage/badger_graph.go b/storage/badger_graph.go index 46c4a9b2e..ff792e009 100644 --- a/storage/badger_graph.go +++ b/storage/badger_graph.go @@ -16,6 +16,7 @@ const ( graphPrefixGhost = "GHOST" // each output key should only be used once graphPrefixUTXO = "UTXO" // unspent outputs, including first consumed transaction hash graphPrefixDeposit = "DEPOSIT" + graphPrefixWithdrawal = "WITHDRAWAL" graphPrefixMint = "MINTUNIVERSAL" graphPrefixTransaction = "TRANSACTION" // raw transaction, may not be finalized yet, if finalized with first finalized snapshot hash graphPrefixFinalization = "FINALIZATION" // transaction finalization hack diff --git a/storage/badger_transaction.go b/storage/badger_transaction.go index f03e2d0a9..50d7d3b15 100644 --- a/storage/badger_transaction.go +++ b/storage/badger_transaction.go @@ -166,7 +166,7 @@ func finalizeTransaction(txn *badger.Txn, ver *common.VersionedTransaction, snap genesis := len(ver.Inputs[0].Genesis) > 0 for _, utxo := range ver.UnspentOutputs() { - err := writeUTXO(txn, utxo, ver.Extra, snap.Timestamp, genesis) + err := writeUTXO(txn, utxo, ver, snap.Timestamp, genesis) if err != nil { return err } @@ -175,7 +175,7 @@ func finalizeTransaction(txn *badger.Txn, ver *common.VersionedTransaction, snap return writeTotalInAsset(txn, ver) } -func writeUTXO(txn *badger.Txn, utxo *common.UTXOWithLock, extra []byte, timestamp uint64, genesis bool) error { +func writeUTXO(txn *badger.Txn, utxo *common.UTXOWithLock, ver *common.VersionedTransaction, timestamp uint64, genesis bool) error { for _, k := range utxo.Keys { err := lockGhostKey(txn, k, utxo.Hash, true) if err != nil { @@ -190,9 +190,9 @@ func writeUTXO(txn *badger.Txn, utxo *common.UTXOWithLock, extra []byte, timesta } var signer, payee crypto.Key - if len(extra) >= len(signer) { - copy(signer[:], extra) - copy(payee[:], extra[len(signer):]) + if len(ver.Extra) >= len(signer) { + copy(signer[:], ver.Extra) + copy(payee[:], ver.Extra[len(signer):]) } switch utxo.Type { case common.OutputTypeNodePledge: @@ -204,7 +204,9 @@ func writeUTXO(txn *badger.Txn, utxo *common.UTXOWithLock, extra []byte, timesta case common.OutputTypeNodeRemove: return writeNodeRemove(txn, signer, payee, utxo.Hash, timestamp) case common.OutputTypeCustodianUpdateNodes: - return writeCustodianNodes(txn, timestamp, utxo, extra, genesis) + return writeCustodianNodes(txn, timestamp, utxo, ver.Extra, genesis) + case common.OutputTypeWithdrawalClaim: + return writeWithdrawalClaim(txn, ver.References[0], ver.PayloadHash()) } return nil diff --git a/storage/badger_withdrawal.go b/storage/badger_withdrawal.go new file mode 100644 index 000000000..696fe648e --- /dev/null +++ b/storage/badger_withdrawal.go @@ -0,0 +1,49 @@ +package storage + +import ( + "encoding/hex" + + "github.com/MixinNetwork/mixin/common" + "github.com/MixinNetwork/mixin/crypto" + "github.com/dgraph-io/badger/v4" +) + +func (s *BadgerStore) ReadWithdrawalClaim(hash crypto.Hash) (*common.VersionedTransaction, string, error) { + txn := s.snapshotsDB.NewTransaction(false) + defer txn.Discard() + + key := graphWithdrawalClaimKey(hash) + item, err := txn.Get(key) + if err == badger.ErrKeyNotFound { + return nil, "", nil + } else if err != nil { + return nil, "", err + } + val, err := item.ValueCopy(nil) + if err != nil { + return nil, "", err + } + + var claim crypto.Hash + if len(val) != len(claim) { + panic(hex.EncodeToString(val)) + } + copy(claim[:], val) + return readTransactionAndFinalization(txn, claim) +} + +func writeWithdrawalClaim(txn *badger.Txn, hash, claim crypto.Hash) error { + tx, snap, err := readTransactionAndFinalization(txn, hash) + if err != nil { + return err + } + if tx == nil || len(snap) != 64 { + panic(claim.String()) + } + key := graphWithdrawalClaimKey(hash) + return txn.Set(key, claim[:]) +} + +func graphWithdrawalClaimKey(tx crypto.Hash) []byte { + return append([]byte(graphPrefixWithdrawal), tx[:]...) +} diff --git a/storage/interface.go b/storage/interface.go index 0dcda9053..e9d759b11 100644 --- a/storage/interface.go +++ b/storage/interface.go @@ -24,6 +24,7 @@ type Store interface { LockUTXOs(inputs []*common.Input, tx crypto.Hash, fork bool) error ReadDepositLock(deposit *common.DepositData) (crypto.Hash, error) LockDepositInput(deposit *common.DepositData, tx crypto.Hash, fork bool) error + ReadWithdrawalClaim(hash crypto.Hash) (*common.VersionedTransaction, string, error) ReadGhostKeyLock(key crypto.Key) (*crypto.Hash, error) LockGhostKeys(keys []*crypto.Key, tx crypto.Hash, fork bool) error ReadSnapshot(hash crypto.Hash) (*common.SnapshotWithTopologicalOrder, error)