Skip to content

Commit

Permalink
rpc objects server for storage transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
cedricfung committed Apr 9, 2024
1 parent 232f6f2 commit 1717683
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 6 deletions.
8 changes: 4 additions & 4 deletions INSCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ type Deployment struct {
// at the same time as defined by the mode
//
// For MAO, the ratio will be 0.9, and each collectible will only cost
// 10% of the unit tokens, so only the inscribers have NFTs, but not the
// the treasury tokens, however they can combine to NFT when vacant.
// 10% of the unit tokens, so only the inscribers have NFTs, but not
// the treasury tokens, however they can occupy a vacant NFT.
Treasury *struct {
Ratio string `json:"ratio"`
Recipient string `json:"recipient"`
Expand Down Expand Up @@ -107,9 +107,9 @@ For an NFT inscription, the owner will release the NFT if they split or combine

## Occupy

Whoever receives a transaction with the exact unit amount of inscription tokens and the following extra will occupy the vacant or released NFT.
Whoever receives a transaction with the exact unit amount of inscription tokens at the first UTXO will occupy the vacant NFT.

The occupation transaction must reference the NFT initial inscription transaction hash.
The occupancy transaction must reference the NFT initial inscription transaction hash and include the following JSON extra.

```golang
type Occupation struct {
Expand Down
2 changes: 2 additions & 0 deletions config/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ metric = false
port = 6860
# whether respond the runtime of each RPC call
runtime = false
# enable the object server
object-server = false

[dev]
# enable the pprof web server with a valid TCP port number
Expand Down
5 changes: 3 additions & 2 deletions config/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ type Custom struct {
Metric bool `toml:"metric"`
} `toml:"p2p"`
RPC struct {
Port int `toml:"port"`
Runtime bool `toml:"runtime"`
Port int `toml:"port"`
Runtime bool `toml:"runtime"`
ObjectServer bool `toml:"object-server"`
} `toml:"rpc"`
Dev struct {
Port int `toml:"port"`
Expand Down
4 changes: 4 additions & 0 deletions rpc/internal/server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ func (impl *RPC) ServeHTTP(w http.ResponseWriter, r *http.Request) {
impl.renderInfo(rdr)
return
}
if strings.HasPrefix(r.URL.Path, "/objects/") && impl.custom.RPC.ObjectServer {
impl.handleObject(w, r, rdr)
return
}
if r.URL.Path != "/" || r.Method != "POST" {
rdr.RenderError(fmt.Errorf("bad request %s %s", r.Method, r.URL.Path))
return
Expand Down
103 changes: 103 additions & 0 deletions rpc/internal/server/object.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package server

import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"unicode/utf8"

"github.com/MixinNetwork/mixin/common"
"github.com/MixinNetwork/mixin/crypto"
)

func (impl *RPC) handleObject(w http.ResponseWriter, r *http.Request, rdr *Render) {
ps := strings.Split(r.URL.Path, "/")
if len(ps) < 3 || ps[1] != "objects" {
rdr.RenderError(fmt.Errorf("bad request %s %s", r.Method, r.URL.Path))
return
}
txHash, err := crypto.HashFromString(ps[2])
if err != nil {
rdr.RenderError(fmt.Errorf("bad request %s %s", r.Method, r.URL.Path))
return
}

tx, _, err := impl.Store.ReadTransaction(txHash)
if err != nil {
rdr.RenderError(err)
return
}
if tx == nil || tx.Asset != common.XINAssetId {
rdr.RenderError(fmt.Errorf("not found %s", r.URL.Path))
return
}

b := tx.Extra
w.Header().Set("Cache-Control", "max-age=31536000, public")
if len(tx.Extra) == 0 {
w.Header().Set("Content-Type", "text/plain")
} else if m := parseJSON(tx.Extra); m == nil {
w.Header().Set("Content-Type", decideContentType(tx.Extra))
} else if len(ps) < 4 {
w.Header().Set("Content-Type", "application/json")
} else {
v, mime := parseDataURI(fmt.Sprint(m[ps[3]]))
w.Header().Set("Content-Type", mime)
b = v
}

w.WriteHeader(http.StatusOK)
w.Write(b)
}

func parseDataURI(v string) ([]byte, string) {
ds := strings.Split(v, ",")
if len(ds) != 2 {
return []byte(v), "text/plain"
}
s := tryURLQueryUnescape(ds[1])

ms := strings.Split(ds[0], ";")
if len(ms) < 2 {
return []byte(s), "text/plain"
}
if ms[len(ms)-1] == "base64" {
b, _ := base64.StdEncoding.DecodeString(s)
s = string(b)
}
if ms[0] == "" || !utf8.ValidString(ms[0]) {
return []byte(s), decideContentType([]byte(s))
}
return []byte(s), ms[0]
}

func tryURLQueryUnescape(v string) string {
s, err := url.QueryUnescape(v)
if err != nil {
return v
}
return s
}

func decideContentType(extra []byte) string {
if utf8.ValidString(string(extra)) {
return "text/plain"
} else {
return "application/octet-stream"
}
}

func parseJSON(extra []byte) map[string]any {
if extra[0] != '{' && extra[0] != '[' {
return nil
}
var r map[string]any
err := json.Unmarshal(extra, &r)
if err != nil {
return nil
}
return r
}

0 comments on commit 1717683

Please sign in to comment.