├── .gitignore ├── go.mod ├── README.md ├── go.sum └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | active-address 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nknorg/active-address 2 | 3 | go 1.22.1 4 | 5 | require ( 6 | github.com/nknorg/nkn/v2 v2.2.1 7 | google.golang.org/protobuf v1.33.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.0 // indirect 12 | github.com/itchyny/base58-go v0.0.5 // indirect 13 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 14 | github.com/pbnjay/memory v0.0.0-20190104145345-974d429e7ae4 // indirect 15 | golang.org/x/crypto v0.21.0 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Count the number of active addresses within a block height range. 2 | 3 | An address is defined as active if it has made at least one transaction or received any token within the block height range. 4 | 5 | ## Usage 6 | 7 | Build: 8 | 9 | ```shell 10 | go build . 11 | ``` 12 | 13 | Count the active address: 14 | 15 | ```shell 16 | ./active-address -s -e -rpc 17 | ``` 18 | 19 | Add `-v` if you also want to print the number of transactions of each address. 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= 3 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 4 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 5 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 6 | github.com/itchyny/base58-go v0.0.5 h1:uv3ieMgCtuE9HtN0Gux375+GOApFnifLkyvSseHBaH0= 7 | github.com/itchyny/base58-go v0.0.5/go.mod h1:SrMWPE3DFuJJp1M/RUhu4fccp/y9AlB8AL3o3duPToU= 8 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 9 | github.com/nknorg/nkn/v2 v2.2.1 h1:PHV+3CKGofdgr8F8cNlOJvDe8XkAD8Pfj8iwsvW2+c0= 10 | github.com/nknorg/nkn/v2 v2.2.1/go.mod h1:S0TtUvuE+hnihwOInhxNkS/c8ybTZUttsF4Vi9RYBwI= 11 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 12 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 13 | github.com/pbnjay/memory v0.0.0-20190104145345-974d429e7ae4 h1:MfIUBZ1bz7TgvQLVa/yPJZOGeKEgs6eTKUjz3zB4B+U= 14 | github.com/pbnjay/memory v0.0.0-20190104145345-974d429e7ae4/go.mod h1:RMU2gJXhratVxBDTFeOdNhd540tG57lt9FIUV0YLvIQ= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 17 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 18 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 19 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 20 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 21 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 22 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 23 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 24 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 25 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "encoding/hex" 6 | "encoding/json" 7 | "errors" 8 | "flag" 9 | "fmt" 10 | "log" 11 | 12 | "github.com/nknorg/nkn/v2/api/httpjson/client" 13 | "github.com/nknorg/nkn/v2/common" 14 | "github.com/nknorg/nkn/v2/pb" 15 | "github.com/nknorg/nkn/v2/program" 16 | "google.golang.org/protobuf/proto" 17 | ) 18 | 19 | type GetBlockResp struct { 20 | Result struct { 21 | Header struct { 22 | SignerPk string `json:"signerPk"` 23 | } `json:"header"` 24 | Transactions []struct { 25 | PayloadData string `json:"payloadData"` 26 | Programs []struct { 27 | Code string `json:"code"` 28 | Parameter string `json:"parameter"` 29 | } `json:"programs"` 30 | TxType string `json:"txType"` 31 | } `json:"transactions"` 32 | } `json:"result"` 33 | } 34 | 35 | func main() { 36 | startHeight := flag.Int("s", 0, "Start block height (inclusive)") 37 | endHeight := flag.Int("e", 0, "End block height (inclusive)") 38 | rpcAddr := flag.String("rpc", "http://127.0.0.1:30003", "RPC address") 39 | verbose := flag.Bool("v", false, "Verbose: print transaction count of each address") 40 | 41 | flag.Parse() 42 | 43 | counter := make(map[string]int, 0) 44 | for height := *startHeight; height < *endHeight; height++ { 45 | if *verbose { 46 | log.Println(height) 47 | } 48 | err := countActiveAddrAtHeight(height, *rpcAddr, counter) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | } 53 | 54 | if *verbose { 55 | for addr, n := range counter { 56 | fmt.Printf("%v\t%v\n", addr, n) 57 | } 58 | } 59 | 60 | fmt.Printf("Active address: %v\n", len(counter)) 61 | } 62 | 63 | func countActiveAddrAtHeight(height int, rpcAddr string, counter map[string]int) error { 64 | resp, err := client.Call(rpcAddr, "getblock", 0, map[string]interface{}{"height": height}) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | v := &GetBlockResp{} 70 | err = json.Unmarshal(resp, v) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | if len(v.Result.Header.SignerPk) == 0 { 76 | return errors.New("no signer") 77 | } 78 | 79 | pk, err := hex.DecodeString(v.Result.Header.SignerPk) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | ph, err := program.CreateProgramHash(pk) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | blockDigner, err := ph.ToAddress() 90 | if err != nil { 91 | return err 92 | } 93 | 94 | counter[blockDigner]++ 95 | 96 | addrs := make([][]byte, 0) 97 | for _, tx := range v.Result.Transactions { 98 | buf, err := hex.DecodeString(tx.PayloadData) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | switch tx.TxType { 104 | case pb.PayloadType_name[int32(pb.PayloadType_SIG_CHAIN_TXN_TYPE)]: 105 | payload := &pb.SigChainTxn{} 106 | err = proto.Unmarshal(buf, payload) 107 | if err != nil { 108 | return err 109 | } 110 | addrs = append(addrs, payload.Submitter) 111 | case pb.PayloadType_name[int32(pb.PayloadType_TRANSFER_ASSET_TYPE)]: 112 | payload := &pb.TransferAsset{} 113 | err = proto.Unmarshal(buf, payload) 114 | if err != nil { 115 | return err 116 | } 117 | addrs = append(addrs, payload.Sender, payload.Recipient) 118 | case pb.PayloadType_name[int32(pb.PayloadType_COINBASE_TYPE)]: 119 | payload := &pb.Coinbase{} 120 | err = proto.Unmarshal(buf, payload) 121 | if err != nil { 122 | return err 123 | } 124 | addrs = append(addrs, payload.Sender, payload.Recipient) 125 | case pb.PayloadType_name[int32(pb.PayloadType_REGISTER_NAME_TYPE)]: 126 | payload := &pb.RegisterName{} 127 | err = proto.Unmarshal(buf, payload) 128 | if err != nil { 129 | return err 130 | } 131 | addrs = append(addrs, payload.Registrant) 132 | case pb.PayloadType_name[int32(pb.PayloadType_TRANSFER_NAME_TYPE)]: 133 | payload := &pb.TransferName{} 134 | err = proto.Unmarshal(buf, payload) 135 | if err != nil { 136 | return err 137 | } 138 | addrs = append(addrs, payload.Registrant) 139 | case pb.PayloadType_name[int32(pb.PayloadType_DELETE_NAME_TYPE)]: 140 | payload := &pb.DeleteName{} 141 | err = proto.Unmarshal(buf, payload) 142 | if err != nil { 143 | return err 144 | } 145 | addrs = append(addrs, payload.Registrant) 146 | case pb.PayloadType_name[int32(pb.PayloadType_SUBSCRIBE_TYPE)]: 147 | payload := &pb.Subscribe{} 148 | err = proto.Unmarshal(buf, payload) 149 | if err != nil { 150 | return err 151 | } 152 | addrs = append(addrs, payload.Subscriber) 153 | case pb.PayloadType_name[int32(pb.PayloadType_UNSUBSCRIBE_TYPE)]: 154 | payload := &pb.Unsubscribe{} 155 | err = proto.Unmarshal(buf, payload) 156 | if err != nil { 157 | return err 158 | } 159 | addrs = append(addrs, payload.Subscriber) 160 | case pb.PayloadType_name[int32(pb.PayloadType_GENERATE_ID_TYPE)]: 161 | payload := &pb.GenerateID{} 162 | err = proto.Unmarshal(buf, payload) 163 | if err != nil { 164 | return err 165 | } 166 | if len(payload.Sender) > 0 { 167 | addrs = append(addrs, payload.Sender) 168 | } 169 | addrs = append(addrs, payload.PublicKey) 170 | case pb.PayloadType_name[int32(pb.PayloadType_NANO_PAY_TYPE)]: 171 | payload := &pb.NanoPay{} 172 | err = proto.Unmarshal(buf, payload) 173 | if err != nil { 174 | return err 175 | } 176 | if len(payload.Sender) > 0 { 177 | addrs = append(addrs, payload.Sender) 178 | } 179 | addrs = append(addrs, payload.Sender, payload.Recipient) 180 | default: 181 | log.Printf("Unknown txn type: %v", tx.TxType) 182 | continue 183 | } 184 | } 185 | 186 | for _, v := range addrs { 187 | var ph common.Uint160 188 | if len(v) == common.UINT160SIZE { 189 | ph = common.BytesToUint160(v) 190 | } else if len(v) == ed25519.PublicKeySize { 191 | ph, err = program.CreateProgramHash(v) 192 | if err != nil { 193 | return err 194 | } 195 | } else { 196 | log.Printf("Unknown size: %v", len(v)) 197 | continue 198 | } 199 | addr, err := ph.ToAddress() 200 | if err != nil { 201 | return err 202 | } 203 | counter[addr]++ 204 | } 205 | 206 | return nil 207 | } 208 | --------------------------------------------------------------------------------