From 5d93913c2860d074ff88953879176e55d41e2d1f Mon Sep 17 00:00:00 2001 From: sandy <18382255942@163.com> Date: Tue, 27 Feb 2024 09:57:44 +0800 Subject: [PATCH] feat(): add arns sdk --- arns/arns.go | 141 +++++++++++++++ arns/arns_test.go | 355 +++++++++++++++++++++++++++++++++++++ example/query_arns_test.go | 26 +++ 3 files changed, 522 insertions(+) create mode 100644 arns/arns.go create mode 100644 arns/arns_test.go create mode 100644 example/query_arns_test.go diff --git a/arns/arns.go b/arns/arns.go new file mode 100644 index 0000000..89a8e23 --- /dev/null +++ b/arns/arns.go @@ -0,0 +1,141 @@ +package arns + +import ( + "errors" + "fmt" + "github.com/tidwall/gjson" + "io/ioutil" + "net/http" + "strings" + "time" +) + +type ArNS struct { + DreUrl string + ArNSAddress string + HttpClient *http.Client +} + +func NewArNS(dreUrl string, arNSAddr string, timout time.Duration) *ArNS { + + // default timeout is 5s + if timout == 0 { + timout = 5 * time.Second + } + + httpClient := &http.Client{ + Timeout: timout, // Set the timeout for HTTP requests + } + return &ArNS{ + DreUrl: dreUrl, + ArNSAddress: arNSAddr, + HttpClient: httpClient, + } +} + +func (a *ArNS) QueryLatestRecord(domain string) (txId string, err error) { + spliteDomains := strings.Split(domain, "_") + if len(spliteDomains) > 2 { // todo now only support level-2 subdomain + return "", errors.New("current arseeding gw not support over level-2 subdomain") + } + rootDomain := spliteDomains[len(spliteDomains)-1] + // step1 query NameCA address + caAddress, err := a.QueryNameCa(rootDomain) + if err != nil { + return "", err + } + // step2 query latest txId + // Currently, only level-1 domain name resolution is queried + subdomain := spliteDomains[0] + if subdomain == rootDomain { + subdomain = "@" + } + txId, err = a.GetArNSTxID(caAddress, subdomain) + return +} + +func (a *ArNS) QueryNameCa(domain string) (caAddress string, err error) { + baseURL := a.DreUrl + "/contract/" + + // Construct the complete URL + url := baseURL + "?id=" + a.ArNSAddress + + // Make the HTTP request using the custom HTTP client + response, err := a.HttpClient.Get(url) + if err != nil { + return "", err + } + defer response.Body.Close() + + // Check the response status code + if response.StatusCode != http.StatusOK { + return "", fmt.Errorf("unexpected response status: %s", response.Status) + } + + // Read the response body + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return "", err + } + + value := gjson.Get(string(body), "state.records."+domain+".contractTxId") + + if !value.Exists() { + return "", fmt.Errorf("domain %s not exist", domain) + } + + return value.String(), nil + +} + +func (a *ArNS) GetArNSTxID(caAddress string, domain string) (txId string, err error) { + + baseURL := a.DreUrl + "/contract/" + + // Construct the complete URL + url := baseURL + "?id=" + caAddress + + // Make the HTTP request using the custom HTTP client + response, err := a.HttpClient.Get(url) + if err != nil { + return "", err + } + defer response.Body.Close() + + // Check the response status code + if response.StatusCode != http.StatusOK { + return "", fmt.Errorf("GetArNSTxID: unexpected response status: %s", response.Status) + } + + // Read the response body + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return "", err + } + + value := gjson.Get(string(body), "state.records."+domain+".transactionId") + + /** The ArNS interface will return two types of data for compatibility processing + data type 1: https://dre-1.warp.cc/contract?id=jr4P6Y_Olv3QGho0uo7p9DpvSn33mUC_XgJSKB3JDZ4 + records:{ + @:{ + transactionId:"wQk7txuMvlrlYlVozj6aeF7E9dlwar8nNtfs3iNTpbQ" + ttlSeconds:900 + } + } + data type 2: https://dre-3.warp.cc/contract?id=Vx4bW_bh7nXMyq-Jy24s9EiCyY_BXZuToshhSqabc9o + records:{ + @:"wQk7txuMvlrlYlVozj6aeF7E9dlwar8nNtfs3iNTpbQ" + } + */ + if !value.Exists() { + value = gjson.Get(string(body), "state.records."+domain) + } + + if !value.Exists() { + return "", fmt.Errorf("GetArNSTxID: domain %s not exist", domain) + } + + return value.String(), nil + +} diff --git a/arns/arns_test.go b/arns/arns_test.go new file mode 100644 index 0000000..0b1aa53 --- /dev/null +++ b/arns/arns_test.go @@ -0,0 +1,355 @@ +package arns + +import ( + "net/http" + "reflect" + "testing" + "time" +) + +func TestNewArNS(t *testing.T) { + type args struct { + dreUrl string + arNSAddr string + timout time.Duration + } + tests := []struct { + name string + args args + want *ArNS + }{ + { + name: "test", + args: args{ + dreUrl: "https://dre-3.warp.cc", + arNSAddr: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + timout: 3 * time.Second, + }, + want: &ArNS{ + DreUrl: "https://dre-3.warp.cc", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + HttpClient: &http.Client{Timeout: 3 * time.Second}, + }, + }, + + { + name: "test", + args: args{ + dreUrl: "https://dre-3.warp.cc", + arNSAddr: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + timout: 0, + }, + want: &ArNS{ + DreUrl: "https://dre-3.warp.cc", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + HttpClient: &http.Client{Timeout: 5 * time.Second}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewArNS(tt.args.dreUrl, tt.args.arNSAddr, tt.args.timout) + if got.DreUrl != tt.want.DreUrl { + t.Errorf("NewArNS() = %v, want %v", got.DreUrl, tt.want.DreUrl) + } + if got.ArNSAddress != tt.want.ArNSAddress { + t.Errorf("NewArNS() = %v, want %v", got.ArNSAddress, tt.want.ArNSAddress) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewArNS() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestArNS_GetArNSTxID(t *testing.T) { + type fields struct { + DreUrl string + ArNSAddress string + Timeout time.Duration + } + type args struct { + caAddress string + domain string + } + tests := []struct { + name string + fields fields + args args + wantTxId string + wantErr bool + }{ + { + // "arseeding":{"contractTxId":"jr4P6Y_Olv3QGho0uo7p9DpvSn33mUC_XgJSKB3JDZ4","endTimestamp":1720865538,"tier":"a27dbfe4-6992-4276-91fb-5b97ae8c3ffa"} + name: "test success", + fields: fields{ + DreUrl: "https://dre-3.warp.cc", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + caAddress: "jr4P6Y_Olv3QGho0uo7p9DpvSn33mUC_XgJSKB3JDZ4", + domain: "@", + }, + wantTxId: "wQk7txuMvlrlYlVozj6aeF7E9dlwar8nNtfs3iNTpbQ", + wantErr: false, + }, + + { + name: "test error", + fields: fields{ + DreUrl: "https://dre-3.warp.cc", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "arseeding", + }, + wantTxId: "", + wantErr: true, + }, + + { + name: "test error", + fields: fields{ + DreUrl: "", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "arweave", + }, + wantTxId: "", + wantErr: true, + }, + + { + name: "test not exist", + fields: fields{ + DreUrl: "https://dre-3.warp.cc", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "arseeding123456", + }, + wantTxId: "", + wantErr: true, + }, + + // https://httpbin.org/xxx + + { + name: "test 404", + fields: fields{ + DreUrl: "https://httpbin.org/xxx", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "arseeding123456", + }, + wantTxId: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := NewArNS(tt.fields.DreUrl, tt.fields.ArNSAddress, tt.fields.Timeout) + gotTxId, err := a.GetArNSTxID(tt.args.caAddress, tt.args.domain) + + t.Logf("name: %v", tt.name) + t.Logf("gotTxId: %v", gotTxId) + t.Logf("wantErr: %v", tt.wantErr) + if (err != nil) != tt.wantErr { + t.Errorf("GetArNSTxID() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotTxId != tt.wantTxId { + t.Errorf("GetArNSTxID() gotTxId = %v, want %v", gotTxId, tt.wantTxId) + } + }) + } +} + +func TestArNS_QueryLatestRecord(t *testing.T) { + type fields struct { + DreUrl string + ArNSAddress string + Timeout time.Duration + } + type args struct { + domain string + } + tests := []struct { + name string + fields fields + args args + wantTxId string + wantErr bool + }{ + { + name: "test success", + fields: fields{ + DreUrl: "https://dre-3.warp.cc", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "arseeding", + }, + wantTxId: "wQk7txuMvlrlYlVozj6aeF7E9dlwar8nNtfs3iNTpbQ", + wantErr: false, + }, + + { + name: "test init domain data success", + fields: fields{ + DreUrl: "https://dre-3.warp.cc", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "web3infra", + }, + wantTxId: "wQk7txuMvlrlYlVozj6aeF7E9dlwar8nNtfs3iNTpbQ", + wantErr: false, + }, + { + name: "test error", + fields: fields{ + DreUrl: "https://dre-3.warp.cc", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "null", + }, + wantTxId: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := NewArNS(tt.fields.DreUrl, tt.fields.ArNSAddress, tt.fields.Timeout) + gotTxId, err := a.QueryLatestRecord(tt.args.domain) + if (err != nil) != tt.wantErr { + t.Errorf("QueryLatestRecord() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotTxId != tt.wantTxId { + t.Errorf("QueryLatestRecord() gotTxId = %v, want %v", gotTxId, tt.wantTxId) + } + }) + } +} + +func TestArNS_QueryNameCa(t *testing.T) { + type fields struct { + DreUrl string + ArNSAddress string + Timeout time.Duration + } + type args struct { + domain string + } + tests := []struct { + name string + fields fields + args args + wantCaAddress string + wantErr bool + }{ + { + // "arseeding":{"contractTxId":"jr4P6Y_Olv3QGho0uo7p9DpvSn33mUC_XgJSKB3JDZ4","endTimestamp":1720865538,"tier":"a27dbfe4-6992-4276-91fb-5b97ae8c3ffa"} + name: "test success", + fields: fields{ + DreUrl: "https://dre-3.warp.cc", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "arseeding", + }, + wantCaAddress: "jr4P6Y_Olv3QGho0uo7p9DpvSn33mUC_XgJSKB3JDZ4", + wantErr: false, + }, + + { + name: "test init domain success", + fields: fields{ + DreUrl: "https://dre-3.warp.cc", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "web3infra", + }, + wantCaAddress: "Vx4bW_bh7nXMyq-Jy24s9EiCyY_BXZuToshhSqabc9o", + wantErr: false, + }, + + { + name: "test error", + fields: fields{ + DreUrl: "", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "arweave", + }, + wantCaAddress: "", + wantErr: true, + }, + + { + name: "test not exist", + fields: fields{ + DreUrl: "https://dre-3.warp.cc", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "arseeding123456", + }, + wantCaAddress: "", + wantErr: true, + }, + + // https://httpbin.org/xxx + + { + name: "test 404", + fields: fields{ + DreUrl: "https://httpbin.org/xxx", + ArNSAddress: "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U", + Timeout: 10 * time.Second, + }, + args: args{ + domain: "arseeding123456", + }, + wantCaAddress: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + t.Logf("test %s", tt.name) + t.Logf("CaAddr url %s", tt.fields.ArNSAddress) + t.Logf("wantErr: %v", tt.wantErr) + a := NewArNS(tt.fields.DreUrl, tt.fields.ArNSAddress, tt.fields.Timeout) + gotCaAddress, err := a.QueryNameCa(tt.args.domain) + t.Logf("gotCaAddress: %v", gotCaAddress) + if (err != nil) != tt.wantErr { + t.Errorf("QueryNameCa() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotCaAddress != tt.wantCaAddress { + t.Errorf("QueryNameCa() gotCaAddress = %v, want %v", gotCaAddress, tt.wantCaAddress) + } + }) + } +} diff --git a/example/query_arns_test.go b/example/query_arns_test.go new file mode 100644 index 0000000..d6176ea --- /dev/null +++ b/example/query_arns_test.go @@ -0,0 +1,26 @@ +package example + +import ( + "github.com/everFinance/goar/arns" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func Test_QueryArNS(t *testing.T) { + arID, err := queryArNS() + assert.NoError(t, err) + t.Log(arID) +} + +func queryArNS() (arID string, err error) { + dreUrl := "https://dre-3.warp.cc" + arNSAddress := "bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U" + timeout := 10 * time.Second + + domain := "cookbook_ao" + + a := arns.NewArNS(dreUrl, arNSAddress, timeout) + arID, err = a.QueryLatestRecord(domain) + return +}